Move part of the loader pipeline to ClusterData
[ganeti-local] / Ganeti / HTools / CLI.hs
index 8dad35d..bc2dad0 100644 (file)
@@ -8,7 +8,7 @@ and this is more IO oriented.
 
 {-
 
-Copyright (C) 2009 Google Inc.
+Copyright (C) 2009, 2010 Google Inc.
 
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -32,35 +32,42 @@ module Ganeti.HTools.CLI
     , OptType
     , parseOpts
     , shTemplate
+    , defaultLuxiSocket
     -- * The options
-    , oPrintNodes
-    , oPrintInsts
-    , oPrintCommands
-    , oOneline
-    , oNoHeaders
-    , oOutputDir
-    , oNodeFile
-    , oInstFile
-    , oNodeSim
-    , oRapiMaster
-    , oLuxiSocket
+    , oDataFile
+    , oDiskMoves
+    , oDynuFile
+    , oEvacMode
+    , oExInst
+    , oExTags
     , oExecJobs
-    , oMaxSolLength
-    , oVerbose
-    , oQuiet
-    , oOfflineNode
-    , oMinScore
-    , oIMem
+    , oGroup
     , oIDisk
-    , oIVcpus
+    , oIMem
     , oINodes
+    , oIVcpus
+    , oLuxiSocket
     , oMaxCpu
+    , oMaxSolLength
     , oMinDisk
-    , oDiskMoves
-    , oDynuFile
-    , oTieredSpec
-    , oShowVer
+    , oMinGain
+    , oMinGainLim
+    , oMinScore
+    , oNoHeaders
+    , oNodeSim
+    , oOfflineNode
+    , oOneline
+    , oOutputDir
+    , oPrintCommands
+    , oPrintInsts
+    , oPrintNodes
+    , oQuiet
+    , oRapiMaster
+    , oSaveCluster
     , oShowHelp
+    , oShowVer
+    , oTieredSpec
+    , oVerbose
     ) where
 
 import Data.Maybe (fromMaybe)
@@ -82,163 +89,128 @@ defaultLuxiSocket = "/var/run/ganeti/socket/ganeti-master"
 
 -- | Command line options structure.
 data Options = Options
-    { optShowNodes   :: Bool           -- ^ Whether to show node status
-    , optShowInsts   :: Bool           -- ^ Whether to show the instance map
-    , optShowCmds    :: Maybe FilePath -- ^ Whether to show the command list
-    , optOneline     :: Bool           -- ^ Switch output to a single line
-    , optOutPath     :: FilePath       -- ^ Path to the output directory
-    , optNoHeaders   :: Bool           -- ^ Do not show a header line
-    , optNodeFile    :: FilePath       -- ^ Path to the nodes file
-    , optNodeSet     :: Bool           -- ^ The nodes have been set by options
-    , optInstFile    :: FilePath       -- ^ Path to the instances file
-    , optInstSet     :: Bool           -- ^ The insts have been set by options
-    , optNodeSim     :: Maybe String   -- ^ Cluster simulation mode
-    , optMaxLength   :: Int            -- ^ Stop after this many steps
-    , optMaster      :: String         -- ^ Collect data from RAPI
-    , optLuxi        :: Maybe FilePath -- ^ Collect data from Luxi
+    { optDataFile    :: Maybe FilePath -- ^ Path to the cluster data file
+    , optDiskMoves   :: Bool           -- ^ Allow disk moves
+    , optDynuFile    :: Maybe FilePath -- ^ Optional file with dynamic use data
+    , optEvacMode    :: Bool           -- ^ Enable evacuation mode
+    , optExInst      :: [String]       -- ^ Instances to be excluded
+    , optExTags      :: Maybe [String] -- ^ Tags to use for exclusion
     , optExecJobs    :: Bool           -- ^ Execute the commands via Luxi
-    , optOffline     :: [String]       -- ^ Names of offline nodes
+    , optGroup       :: Maybe GroupID  -- ^ The UUID of the group to process
     , optINodes      :: Int            -- ^ Nodes required for an instance
     , optISpec       :: RSpec          -- ^ Requested instance specs
-    , optTieredSpec  :: Maybe RSpec    -- ^ Requested specs for tiered mode
-    , optMinScore    :: Score          -- ^ The minimum score we aim for
+    , optLuxi        :: Maybe FilePath -- ^ Collect data from Luxi
+    , optMaster      :: String         -- ^ Collect data from RAPI
+    , optMaxLength   :: Int            -- ^ Stop after this many steps
     , optMcpu        :: Double         -- ^ Max cpu ratio for nodes
     , optMdsk        :: Double         -- ^ Max disk usage ratio for nodes
-    , optDiskMoves   :: Bool           -- ^ Allow disk moves
-    , optDynuFile    :: Maybe FilePath -- ^ Optional file with dynamic use data
-    , optVerbose     :: Int            -- ^ Verbosity level
-    , optShowVer     :: Bool           -- ^ Just show the program version
+    , optMinGain     :: Score          -- ^ Min gain we aim for in a step
+    , optMinGainLim  :: Score          -- ^ Limit below which we apply mingain
+    , optMinScore    :: Score          -- ^ The minimum score we aim for
+    , optNoHeaders   :: Bool           -- ^ Do not show a header line
+    , optNodeSim     :: [String]       -- ^ Cluster simulation mode
+    , optOffline     :: [String]       -- ^ Names of offline nodes
+    , optOneline     :: Bool           -- ^ Switch output to a single line
+    , optOutPath     :: FilePath       -- ^ Path to the output directory
+    , optSaveCluster :: Maybe FilePath -- ^ Save cluster state to this file
+    , optShowCmds    :: Maybe FilePath -- ^ Whether to show the command list
     , optShowHelp    :: Bool           -- ^ Just show the help
+    , optShowInsts   :: Bool           -- ^ Whether to show the instance map
+    , optShowNodes   :: Maybe [String] -- ^ Whether to show node status
+    , optShowVer     :: Bool           -- ^ Just show the program version
+    , optTieredSpec  :: Maybe RSpec    -- ^ Requested specs for tiered mode
+    , optVerbose     :: Int            -- ^ Verbosity level
     } deriving Show
 
 -- | Default values for the command line options.
 defaultOptions :: Options
 defaultOptions  = Options
- { optShowNodes   = False
- , optShowInsts   = False
- , optShowCmds    = Nothing
- , optOneline     = False
- , optNoHeaders   = False
- , optOutPath     = "."
- , optNodeFile    = "nodes"
- , optNodeSet     = False
- , optInstFile    = "instances"
- , optInstSet     = False
- , optNodeSim     = Nothing
- , optMaxLength   = -1
- , optMaster      = ""
- , optLuxi        = Nothing
+ { optDataFile    = Nothing
+ , optDiskMoves   = True
+ , optDynuFile    = Nothing
+ , optEvacMode    = False
+ , optExInst      = []
+ , optExTags      = Nothing
  , optExecJobs    = False
- , optOffline     = []
+ , optGroup       = Nothing
  , optINodes      = 2
  , optISpec       = RSpec 1 4096 102400
- , optTieredSpec  = Nothing
+ , optLuxi        = Nothing
+ , optMaster      = ""
+ , optMaxLength   = -1
+ , optMcpu        = defVcpuRatio
+ , optMdsk        = defReservedDiskRatio
+ , optMinGain     = 1e-2
+ , optMinGainLim  = 1e-1
  , optMinScore    = 1e-9
- , optMcpu        = -1
- , optMdsk        = -1
- , optDiskMoves   = True
- , optDynuFile    = Nothing
- , optVerbose     = 1
- , optShowVer     = False
+ , optNoHeaders   = False
+ , optNodeSim     = []
+ , optOffline     = []
+ , optOneline     = False
+ , optOutPath     = "."
+ , optSaveCluster = Nothing
+ , optShowCmds    = Nothing
  , optShowHelp    = False
+ , optShowInsts   = False
+ , optShowNodes   = Nothing
+ , optShowVer     = False
+ , optTieredSpec  = Nothing
+ , optVerbose     = 1
  }
 
 -- | Abrreviation for the option type
 type OptType = OptDescr (Options -> Result Options)
 
-oPrintNodes :: OptType
-oPrintNodes = Option "p" ["print-nodes"]
-              (NoArg (\ opts -> Ok opts { optShowNodes = True }))
-              "print the final node list"
-
-oPrintInsts :: OptType
-oPrintInsts = Option "" ["print-instances"]
-              (NoArg (\ opts -> Ok opts { optShowInsts = True }))
-              "print the final instance map"
+oDataFile :: OptType
+oDataFile = Option "t" ["text-data"]
+            (ReqArg (\ f o -> Ok o { optDataFile = Just f }) "FILE")
+            "the cluster data FILE"
 
-oPrintCommands :: OptType
-oPrintCommands = Option "C" ["print-commands"]
-                 (OptArg ((\ f opts -> Ok opts { optShowCmds = Just f }) .
-                          fromMaybe "-")
-                  "FILE")
-                 "print the ganeti command list for reaching the solution,\
-                 \ if an argument is passed then write the commands to a\
-                 \ file named as such"
-
-oOneline :: OptType
-oOneline = Option "o" ["oneline"]
-           (NoArg (\ opts -> Ok opts { optOneline = True }))
-           "print the ganeti command list for reaching the solution"
-
-oNoHeaders :: OptType
-oNoHeaders = Option "" ["no-headers"]
-             (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
-             "do not show a header line"
-
-oOutputDir :: OptType
-oOutputDir = Option "d" ["output-dir"]
-             (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
-             "directory in which to write output files"
-
-oNodeFile :: OptType
-oNodeFile = Option "n" ["nodes"]
-            (ReqArg (\ f o -> Ok o { optNodeFile = f,
-                                     optNodeSet = True }) "FILE")
-            "the node list FILE"
+oDiskMoves :: OptType
+oDiskMoves = Option "" ["no-disk-moves"]
+             (NoArg (\ opts -> Ok opts { optDiskMoves = False}))
+             "disallow disk moves from the list of allowed instance changes,\
+             \ thus allowing only the 'cheap' failover/migrate operations"
 
-oInstFile :: OptType
-oInstFile = Option "i" ["instances"]
-            (ReqArg (\ f o -> Ok o { optInstFile = f,
-                                     optInstSet = True }) "FILE")
-            "the instance list FILE"
+oDynuFile :: OptType
+oDynuFile = Option "U" ["dynu-file"]
+            (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
+            "Import dynamic utilisation data from the given FILE"
 
-oNodeSim :: OptType
-oNodeSim = Option "" ["simulate"]
-            (ReqArg (\ f o -> Ok o { optNodeSim = Just f }) "SPEC")
-            "simulate an empty cluster, given as 'num_nodes,disk,memory,cpus'"
+oEvacMode :: OptType
+oEvacMode = Option "E" ["evac-mode"]
+            (NoArg (\opts -> Ok opts { optEvacMode = True }))
+            "enable evacuation mode, where the algorithm only moves \
+            \ instances away from offline and drained nodes"
 
-oRapiMaster :: OptType
-oRapiMaster = Option "m" ["master"]
-              (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
-              "collect data via RAPI at the given ADDRESS"
+oExInst :: OptType
+oExInst = Option "" ["exclude-instances"]
+          (ReqArg (\ f opts -> Ok opts { optExInst = sepSplit ',' f }) "INSTS")
+          "exclude given instances  from any moves"
 
-oLuxiSocket :: OptType
-oLuxiSocket = Option "L" ["luxi"]
-              (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
-                       fromMaybe defaultLuxiSocket) "SOCKET")
-              "collect data via Luxi, optionally using the given SOCKET path"
+oExTags :: OptType
+oExTags = Option "" ["exclusion-tags"]
+            (ReqArg (\ f opts -> Ok opts { optExTags = Just $ sepSplit ',' f })
+             "TAG,...") "Enable instance exclusion based on given tag prefix"
 
 oExecJobs :: OptType
 oExecJobs = Option "X" ["exec"]
              (NoArg (\ opts -> Ok opts { optExecJobs = True}))
              "execute the suggested moves via Luxi (only available when using\
-             \ it for data gathering"
+             \ it for data gathering)"
 
-oVerbose :: OptType
-oVerbose = Option "v" ["verbose"]
-           (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
-           "increase the verbosity level"
+oGroup :: OptType
+oGroup = Option "G" ["group"]
+            (ReqArg (\ f o -> Ok o { optGroup = Just f }) "ID")
+            "the ID of the group to balance"
 
-oQuiet :: OptType
-oQuiet = Option "q" ["quiet"]
-         (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
-         "decrease the verbosity level"
-
-oOfflineNode :: OptType
-oOfflineNode = Option "O" ["offline"]
-               (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
-               "set node as offline"
-
-oMaxSolLength :: OptType
-oMaxSolLength = Option "l" ["max-length"]
-                (ReqArg (\ i opts -> Ok opts { optMaxLength = read i }) "N")
-                "cap the solution at this many moves (useful for very\
-                \ unbalanced clusters)"
-
-oMinScore :: OptType
-oMinScore = Option "e" ["min-score"]
-            (ReqArg (\ e opts -> Ok opts { optMinScore = read e }) "EPSILON")
-            " mininum score to aim for"
+oIDisk :: OptType
+oIDisk = Option "" ["disk"]
+         (ReqArg (\ d opts ->
+                     let ospec = optISpec opts
+                         nspec = ospec { rspecDsk = read d }
+                     in Ok opts { optISpec = nspec }) "DISK")
+         "disk size for instances"
 
 oIMem :: OptType
 oIMem = Option "" ["memory"]
@@ -248,13 +220,10 @@ oIMem = Option "" ["memory"]
                      in Ok opts { optISpec = nspec }) "MEMORY")
         "memory size for instances"
 
-oIDisk :: OptType
-oIDisk = Option "" ["disk"]
-         (ReqArg (\ d opts ->
-                     let ospec = optISpec opts
-                         nspec = ospec { rspecDsk = read d }
-                     in Ok opts { optISpec = nspec }) "DISK")
-         "disk size for instances"
+oINodes :: OptType
+oINodes = Option "" ["req-nodes"]
+          (ReqArg (\ n opts -> Ok opts { optINodes = read n }) "NODES")
+          "number of nodes for the new instances (1=plain, 2=mirrored)"
 
 oIVcpus :: OptType
 oIVcpus = Option "" ["vcpus"]
@@ -264,31 +233,118 @@ oIVcpus = Option "" ["vcpus"]
                        in Ok opts { optISpec = nspec }) "NUM")
           "number of virtual cpus for instances"
 
-oINodes :: OptType
-oINodes = Option "" ["req-nodes"]
-          (ReqArg (\ n opts -> Ok opts { optINodes = read n }) "NODES")
-          "number of nodes for the new instances (1=plain, 2=mirrored)"
+oLuxiSocket :: OptType
+oLuxiSocket = Option "L" ["luxi"]
+              (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
+                       fromMaybe defaultLuxiSocket) "SOCKET")
+              "collect data via Luxi, optionally using the given SOCKET path"
 
 oMaxCpu :: OptType
 oMaxCpu = Option "" ["max-cpu"]
           (ReqArg (\ n opts -> Ok opts { optMcpu = read n }) "RATIO")
-          "maximum virtual-to-physical cpu ratio for nodes"
+          "maximum virtual-to-physical cpu ratio for nodes (from 1\
+          \ upwards) [64]"
+
+oMaxSolLength :: OptType
+oMaxSolLength = Option "l" ["max-length"]
+                (ReqArg (\ i opts -> Ok opts { optMaxLength = read i }) "N")
+                "cap the solution at this many moves (useful for very\
+                \ unbalanced clusters)"
 
 oMinDisk :: OptType
 oMinDisk = Option "" ["min-disk"]
            (ReqArg (\ n opts -> Ok opts { optMdsk = read n }) "RATIO")
-           "minimum free disk space for nodes (between 0 and 1)"
+           "minimum free disk space for nodes (between 0 and 1) [0]"
 
-oDiskMoves :: OptType
-oDiskMoves = Option "" ["no-disk-moves"]
-             (NoArg (\ opts -> Ok opts { optDiskMoves = False}))
-             "disallow disk moves from the list of allowed instance changes,\
-             \ thus allowing only the 'cheap' failover/migrate operations"
+oMinGain :: OptType
+oMinGain = Option "g" ["min-gain"]
+            (ReqArg (\ g opts -> Ok opts { optMinGain = read g }) "DELTA")
+            "minimum gain to aim for in a balancing step before giving up"
 
-oDynuFile :: OptType
-oDynuFile = Option "U" ["dynu-file"]
-            (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
-            "Import dynamic utilisation data from the given FILE"
+oMinGainLim :: OptType
+oMinGainLim = Option "" ["min-gain-limit"]
+            (ReqArg (\ g opts -> Ok opts { optMinGainLim = read g }) "SCORE")
+            "minimum cluster score for which we start checking the min-gain"
+
+oMinScore :: OptType
+oMinScore = Option "e" ["min-score"]
+            (ReqArg (\ e opts -> Ok opts { optMinScore = read e }) "EPSILON")
+            "mininum score to aim for"
+
+oNoHeaders :: OptType
+oNoHeaders = Option "" ["no-headers"]
+             (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
+             "do not show a header line"
+
+oNodeSim :: OptType
+oNodeSim = Option "" ["simulate"]
+            (ReqArg (\ f o -> Ok o { optNodeSim = f:optNodeSim o }) "SPEC")
+            "simulate an empty cluster, given as 'num_nodes,disk,ram,cpu'"
+
+oOfflineNode :: OptType
+oOfflineNode = Option "O" ["offline"]
+               (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
+               "set node as offline"
+
+oOneline :: OptType
+oOneline = Option "o" ["oneline"]
+           (NoArg (\ opts -> Ok opts { optOneline = True }))
+           "print the ganeti command list for reaching the solution"
+
+oOutputDir :: OptType
+oOutputDir = Option "d" ["output-dir"]
+             (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
+             "directory in which to write output files"
+
+oPrintCommands :: OptType
+oPrintCommands = Option "C" ["print-commands"]
+                 (OptArg ((\ f opts -> Ok opts { optShowCmds = Just f }) .
+                          fromMaybe "-")
+                  "FILE")
+                 "print the ganeti command list for reaching the solution,\
+                 \ if an argument is passed then write the commands to a\
+                 \ file named as such"
+
+oPrintInsts :: OptType
+oPrintInsts = Option "" ["print-instances"]
+              (NoArg (\ opts -> Ok opts { optShowInsts = True }))
+              "print the final instance map"
+
+oPrintNodes :: OptType
+oPrintNodes = Option "p" ["print-nodes"]
+              (OptArg ((\ f opts ->
+                            let (prefix, realf) = case f of
+                                  '+':rest -> (["+"], rest)
+                                  _ -> ([], f)
+                                splitted = prefix ++ sepSplit ',' realf
+                            in Ok opts { optShowNodes = Just splitted }) .
+                       fromMaybe []) "FIELDS")
+              "print the final node list"
+
+oQuiet :: OptType
+oQuiet = Option "q" ["quiet"]
+         (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
+         "decrease the verbosity level"
+
+oRapiMaster :: OptType
+oRapiMaster = Option "m" ["master"]
+              (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
+              "collect data via RAPI at the given ADDRESS"
+
+oSaveCluster :: OptType
+oSaveCluster = Option "S" ["save"]
+            (ReqArg (\ f opts -> Ok opts { optSaveCluster = Just f }) "FILE")
+            "Save cluster state at the end of the processing to FILE"
+
+oShowHelp :: OptType
+oShowHelp = Option "h" ["help"]
+            (NoArg (\ opts -> Ok opts { optShowHelp = True}))
+            "show help"
+
+oShowVer :: OptType
+oShowVer = Option "V" ["version"]
+           (NoArg (\ opts -> Ok opts { optShowVer = True}))
+           "show the version of the program"
 
 oTieredSpec :: OptType
 oTieredSpec = Option "" ["tiered-alloc"]
@@ -297,22 +353,17 @@ oTieredSpec = Option "" ["tiered-alloc"]
                           prs <- mapM (tryRead "tiered specs") sp
                           tspec <-
                               case prs of
-                                [cpu, ram, dsk] -> return $ RSpec cpu ram dsk
-                                _ -> Bad $ "Invalid specification: " ++ inp
+                                [dsk, ram, cpu] -> return $ RSpec cpu ram dsk
+                                _ -> Bad $ "Invalid specification: " ++ inp ++
+                                     ", expected disk,ram,cpu"
                           return $ opts { optTieredSpec = Just tspec } )
               "TSPEC")
-             "enable tiered specs allocation, where we decrease the instance\
-             \ spec on failure to allocate and restart the allocation process"
-
-oShowVer :: OptType
-oShowVer = Option "V" ["version"]
-           (NoArg (\ opts -> Ok opts { optShowVer = True}))
-           "show the version of the program"
+             "enable tiered specs allocation, given as 'disk,ram,cpu'"
 
-oShowHelp :: OptType
-oShowHelp = Option "h" ["help"]
-            (NoArg (\ opts -> Ok opts { optShowHelp = True}))
-            "show help"
+oVerbose :: OptType
+oVerbose = Option "v" ["verbose"]
+           (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
+           "increase the verbosity level"
 
 -- | Usage info
 usageHelp :: String -> [OptType] -> String
@@ -345,7 +396,7 @@ parseOpts argv progname options =
               printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
                      progname Version.version
                      compilerName (Data.Version.showVersion compilerVersion)
-                     os arch
+                     os arch :: IO ()
               exitWith ExitSuccess
             return (po, args)
       (_, _, errs) -> do