1 {-| Implementation of command-line functions.
3 This module holds the common command-line related functions for the
4 binaries, separated into this module since "Ganeti.HTools.Utils" is
5 used in many other places and this is more IO oriented.
11 Copyright (C) 2009, 2010, 2011 Google Inc.
13 This program is free software; you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation; either version 2 of the License, or
16 (at your option) any later version.
18 This program is distributed in the hope that it will be useful, but
19 WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 General Public License for more details.
23 You should have received a copy of the GNU General Public License
24 along with this program; if not, write to the Free Software
25 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
30 module Ganeti.HTools.CLI
81 import Data.Maybe (fromMaybe)
82 import qualified Data.Version
83 import System.Console.GetOpt
87 import Text.Printf (printf)
89 import qualified Ganeti.HTools.Version as Version(version)
90 import qualified Ganeti.Constants as C
91 import Ganeti.HTools.Types
92 import Ganeti.HTools.Utils
96 -- | The default value for the luxi socket.
98 -- This is re-exported from the "Ganeti.Constants" module.
99 defaultLuxiSocket :: FilePath
100 defaultLuxiSocket = C.masterSocket
104 -- | Command line options structure.
105 data Options = Options
106 { optDataFile :: Maybe FilePath -- ^ Path to the cluster data file
107 , optDiskMoves :: Bool -- ^ Allow disk moves
108 , optInstMoves :: Bool -- ^ Allow instance moves
109 , optDiskTemplate :: DiskTemplate -- ^ The requested disk template
110 , optDynuFile :: Maybe FilePath -- ^ Optional file with dynamic use data
111 , optEvacMode :: Bool -- ^ Enable evacuation mode
112 , optExInst :: [String] -- ^ Instances to be excluded
113 , optExTags :: Maybe [String] -- ^ Tags to use for exclusion
114 , optExecJobs :: Bool -- ^ Execute the commands via Luxi
115 , optGroup :: Maybe GroupID -- ^ The UUID of the group to process
116 , optSelInst :: [String] -- ^ Instances to be excluded
117 , optISpec :: RSpec -- ^ Requested instance specs
118 , optLuxi :: Maybe FilePath -- ^ Collect data from Luxi
119 , optMachineReadable :: Bool -- ^ Output machine-readable format
120 , optMaster :: String -- ^ Collect data from RAPI
121 , optMaxLength :: Int -- ^ Stop after this many steps
122 , optMcpu :: Double -- ^ Max cpu ratio for nodes
123 , optMdsk :: Double -- ^ Max disk usage ratio for nodes
124 , optMinGain :: Score -- ^ Min gain we aim for in a step
125 , optMinGainLim :: Score -- ^ Limit below which we apply mingain
126 , optMinScore :: Score -- ^ The minimum score we aim for
127 , optNoHeaders :: Bool -- ^ Do not show a header line
128 , optNodeSim :: [String] -- ^ Cluster simulation mode
129 , optOffline :: [String] -- ^ Names of offline nodes
130 , optOneline :: Bool -- ^ Switch output to a single line
131 , optOutPath :: FilePath -- ^ Path to the output directory
132 , optSaveCluster :: Maybe FilePath -- ^ Save cluster state to this file
133 , optShowCmds :: Maybe FilePath -- ^ Whether to show the command list
134 , optShowHelp :: Bool -- ^ Just show the help
135 , optShowInsts :: Bool -- ^ Whether to show the instance map
136 , optShowNodes :: Maybe [String] -- ^ Whether to show node status
137 , optShowVer :: Bool -- ^ Just show the program version
138 , optTieredSpec :: Maybe RSpec -- ^ Requested specs for tiered mode
139 , optReplay :: Maybe String -- ^ Unittests: RNG state
140 , optVerbose :: Int -- ^ Verbosity level
143 -- | Default values for the command line options.
144 defaultOptions :: Options
145 defaultOptions = Options
146 { optDataFile = Nothing
147 , optDiskMoves = True
148 , optInstMoves = True
149 , optDiskTemplate = DTDrbd8
150 , optDynuFile = Nothing
151 , optEvacMode = False
153 , optExTags = Nothing
154 , optExecJobs = False
157 , optISpec = RSpec 1 4096 102400
159 , optMachineReadable = False
162 , optMcpu = defVcpuRatio
163 , optMdsk = defReservedDiskRatio
165 , optMinGainLim = 1e-1
167 , optNoHeaders = False
172 , optSaveCluster = Nothing
173 , optShowCmds = Nothing
174 , optShowHelp = False
175 , optShowInsts = False
176 , optShowNodes = Nothing
178 , optTieredSpec = Nothing
179 , optReplay = Nothing
183 -- | Abrreviation for the option type.
184 type OptType = OptDescr (Options -> Result Options)
186 -- * Command line options
189 oDataFile = Option "t" ["text-data"]
190 (ReqArg (\ f o -> Ok o { optDataFile = Just f }) "FILE")
191 "the cluster data FILE"
193 oDiskMoves :: OptType
194 oDiskMoves = Option "" ["no-disk-moves"]
195 (NoArg (\ opts -> Ok opts { optDiskMoves = False}))
196 "disallow disk moves from the list of allowed instance changes,\
197 \ thus allowing only the 'cheap' failover/migrate operations"
199 oDiskTemplate :: OptType
200 oDiskTemplate = Option "" ["disk-template"]
201 (ReqArg (\ t opts -> do
203 return $ opts { optDiskTemplate = dt }) "TEMPLATE")
204 "select the desired disk template"
207 oSelInst = Option "" ["select-instances"]
208 (ReqArg (\ f opts -> Ok opts { optSelInst = sepSplit ',' f }) "INSTS")
209 "only select given instances for any moves"
211 oInstMoves :: OptType
212 oInstMoves = Option "" ["no-instance-moves"]
213 (NoArg (\ opts -> Ok opts { optInstMoves = False}))
214 "disallow instance (primary node) moves from the list of allowed,\
215 \ instance changes, thus allowing only slower, but sometimes\
216 \ safer, drbd secondary changes"
219 oDynuFile = Option "U" ["dynu-file"]
220 (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
221 "Import dynamic utilisation data from the given FILE"
224 oEvacMode = Option "E" ["evac-mode"]
225 (NoArg (\opts -> Ok opts { optEvacMode = True }))
226 "enable evacuation mode, where the algorithm only moves \
227 \ instances away from offline and drained nodes"
230 oExInst = Option "" ["exclude-instances"]
231 (ReqArg (\ f opts -> Ok opts { optExInst = sepSplit ',' f }) "INSTS")
232 "exclude given instances from any moves"
235 oExTags = Option "" ["exclusion-tags"]
236 (ReqArg (\ f opts -> Ok opts { optExTags = Just $ sepSplit ',' f })
237 "TAG,...") "Enable instance exclusion based on given tag prefix"
240 oExecJobs = Option "X" ["exec"]
241 (NoArg (\ opts -> Ok opts { optExecJobs = True}))
242 "execute the suggested moves via Luxi (only available when using\
243 \ it for data gathering)"
246 oGroup = Option "G" ["group"]
247 (ReqArg (\ f o -> Ok o { optGroup = Just f }) "ID")
248 "the ID of the group to balance"
251 oIDisk = Option "" ["disk"]
252 (ReqArg (\ d opts -> do
253 dsk <- annotateResult "--disk option" (parseUnit d)
254 let ospec = optISpec opts
255 nspec = ospec { rspecDsk = dsk }
256 return $ opts { optISpec = nspec }) "DISK")
257 "disk size for instances"
260 oIMem = Option "" ["memory"]
261 (ReqArg (\ m opts -> do
262 mem <- annotateResult "--memory option" (parseUnit m)
263 let ospec = optISpec opts
264 nspec = ospec { rspecMem = mem }
265 return $ opts { optISpec = nspec }) "MEMORY")
266 "memory size for instances"
269 oIVcpus = Option "" ["vcpus"]
270 (ReqArg (\ p opts -> do
271 vcpus <- tryRead "--vcpus option" p
272 let ospec = optISpec opts
273 nspec = ospec { rspecCpu = vcpus }
274 return $ opts { optISpec = nspec }) "NUM")
275 "number of virtual cpus for instances"
277 oLuxiSocket :: OptType
278 oLuxiSocket = Option "L" ["luxi"]
279 (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
280 fromMaybe defaultLuxiSocket) "SOCKET")
281 "collect data via Luxi, optionally using the given SOCKET path"
283 oMachineReadable :: OptType
284 oMachineReadable = Option "" ["machine-readable"]
285 (OptArg (\ f opts -> do
286 flag <- parseYesNo True f
287 return $ opts { optMachineReadable = flag }) "CHOICE")
288 "enable machine readable output (pass either 'yes' or 'no' to\
289 \ explicitely control the flag, or without an argument defaults to\
293 oMaxCpu = Option "" ["max-cpu"]
294 (ReqArg (\ n opts -> Ok opts { optMcpu = read n }) "RATIO")
295 "maximum virtual-to-physical cpu ratio for nodes (from 1\
298 oMaxSolLength :: OptType
299 oMaxSolLength = Option "l" ["max-length"]
300 (ReqArg (\ i opts -> Ok opts { optMaxLength = read i }) "N")
301 "cap the solution at this many moves (useful for very\
302 \ unbalanced clusters)"
305 oMinDisk = Option "" ["min-disk"]
306 (ReqArg (\ n opts -> Ok opts { optMdsk = read n }) "RATIO")
307 "minimum free disk space for nodes (between 0 and 1) [0]"
310 oMinGain = Option "g" ["min-gain"]
311 (ReqArg (\ g opts -> Ok opts { optMinGain = read g }) "DELTA")
312 "minimum gain to aim for in a balancing step before giving up"
314 oMinGainLim :: OptType
315 oMinGainLim = Option "" ["min-gain-limit"]
316 (ReqArg (\ g opts -> Ok opts { optMinGainLim = read g }) "SCORE")
317 "minimum cluster score for which we start checking the min-gain"
320 oMinScore = Option "e" ["min-score"]
321 (ReqArg (\ e opts -> Ok opts { optMinScore = read e }) "EPSILON")
322 "mininum score to aim for"
324 oNoHeaders :: OptType
325 oNoHeaders = Option "" ["no-headers"]
326 (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
327 "do not show a header line"
330 oNodeSim = Option "" ["simulate"]
331 (ReqArg (\ f o -> Ok o { optNodeSim = f:optNodeSim o }) "SPEC")
332 "simulate an empty cluster, given as 'num_nodes,disk,ram,cpu'"
334 oOfflineNode :: OptType
335 oOfflineNode = Option "O" ["offline"]
336 (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
337 "set node as offline"
340 oOneline = Option "o" ["oneline"]
341 (NoArg (\ opts -> Ok opts { optOneline = True }))
342 "print the ganeti command list for reaching the solution"
344 oOutputDir :: OptType
345 oOutputDir = Option "d" ["output-dir"]
346 (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
347 "directory in which to write output files"
349 oPrintCommands :: OptType
350 oPrintCommands = Option "C" ["print-commands"]
351 (OptArg ((\ f opts -> Ok opts { optShowCmds = Just f }) .
354 "print the ganeti command list for reaching the solution,\
355 \ if an argument is passed then write the commands to a\
356 \ file named as such"
358 oPrintInsts :: OptType
359 oPrintInsts = Option "" ["print-instances"]
360 (NoArg (\ opts -> Ok opts { optShowInsts = True }))
361 "print the final instance map"
363 oPrintNodes :: OptType
364 oPrintNodes = Option "p" ["print-nodes"]
365 (OptArg ((\ f opts ->
366 let (prefix, realf) = case f of
367 '+':rest -> (["+"], rest)
369 splitted = prefix ++ sepSplit ',' realf
370 in Ok opts { optShowNodes = Just splitted }) .
371 fromMaybe []) "FIELDS")
372 "print the final node list"
375 oQuiet = Option "q" ["quiet"]
376 (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
377 "decrease the verbosity level"
379 oRapiMaster :: OptType
380 oRapiMaster = Option "m" ["master"]
381 (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
382 "collect data via RAPI at the given ADDRESS"
384 oSaveCluster :: OptType
385 oSaveCluster = Option "S" ["save"]
386 (ReqArg (\ f opts -> Ok opts { optSaveCluster = Just f }) "FILE")
387 "Save cluster state at the end of the processing to FILE"
390 oShowHelp = Option "h" ["help"]
391 (NoArg (\ opts -> Ok opts { optShowHelp = True}))
395 oShowVer = Option "V" ["version"]
396 (NoArg (\ opts -> Ok opts { optShowVer = True}))
397 "show the version of the program"
399 oTieredSpec :: OptType
400 oTieredSpec = Option "" ["tiered-alloc"]
401 (ReqArg (\ inp opts -> do
402 let sp = sepSplit ',' inp
403 prs <- mapM (\(fn, val) -> fn val) $
404 zip [ annotateResult "tiered specs memory" .
406 , annotateResult "tiered specs disk" .
408 , tryRead "tiered specs cpus"
412 [dsk, ram, cpu] -> return $ RSpec cpu ram dsk
413 _ -> Bad $ "Invalid specification: " ++ inp ++
414 ", expected disk,ram,cpu"
415 return $ opts { optTieredSpec = Just tspec } )
417 "enable tiered specs allocation, given as 'disk,ram,cpu'"
420 oReplay = Option "" ["replay"]
421 (ReqArg (\ stat opts -> Ok opts { optReplay = Just stat } ) "STATE")
422 "Pre-seed the random number generator with STATE"
425 oVerbose = Option "v" ["verbose"]
426 (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
427 "increase the verbosity level"
431 -- | Helper for parsing a yes\/no command line flag.
432 parseYesNo :: Bool -- ^ Default whalue (when we get a @Nothing@)
433 -> Maybe String -- ^ Parameter value
434 -> Result Bool -- ^ Resulting boolean value
435 parseYesNo v Nothing = return v
436 parseYesNo _ (Just "yes") = return True
437 parseYesNo _ (Just "no") = return False
438 parseYesNo _ (Just s) = fail $ "Invalid choice '" ++ s ++
439 "', pass one of 'yes' or 'no'"
442 usageHelp :: String -> [OptType] -> String
444 usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
445 progname Version.version progname)
447 -- | Command line parser, using the 'Options' structure.
448 parseOpts :: [String] -- ^ The command line arguments
449 -> String -- ^ The program name
450 -> [OptType] -- ^ The supported command line options
451 -> IO (Options, [String]) -- ^ The resulting options and leftover
453 parseOpts argv progname options =
454 case getOpt Permute options argv of
457 let (pr, args) = (foldM (flip id) defaultOptions o, n)
460 hPutStrLn stderr "Error while parsing command\
463 exitWith $ ExitFailure 1
464 Ok val -> return val)
465 when (optShowHelp po) $ do
466 putStr $ usageHelp progname options
468 when (optShowVer po) $ do
469 printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
470 progname Version.version
471 compilerName (Data.Version.showVersion compilerVersion)
476 hPutStrLn stderr $ "Command line error: " ++ concat errs
477 hPutStrLn stderr $ usageHelp progname options
478 exitWith $ ExitFailure 2
480 -- | A shell script template for autogenerated scripts.
483 printf "#!/bin/sh\n\n\
484 \# Auto-generated script for executing cluster rebalancing\n\n\
485 \# To stop, touch the file /tmp/stop-htools\n\n\
488 \ if [ -f /tmp/stop-htools ]; then\n\
489 \ echo 'Stop requested, exiting'\n\
494 -- | Optionally print the node list.
495 maybePrintNodes :: Maybe [String] -- ^ The field list
496 -> String -- ^ Informational message
497 -> ([String] -> String) -- ^ Function to generate the listing
499 maybePrintNodes Nothing _ _ = return ()
500 maybePrintNodes (Just fields) msg fn = do
502 hPutStrLn stderr (msg ++ " status:")
503 hPutStrLn stderr $ fn fields
506 -- | Optionally print the instance list.
507 maybePrintInsts :: Bool -- ^ Whether to print the instance list
508 -> String -- ^ Type of the instance map (e.g. initial)
509 -> String -- ^ The instance data
511 maybePrintInsts do_print msg instdata =
514 hPutStrLn stderr $ msg ++ " instance map:"
515 hPutStr stderr instdata
517 -- | Function to display warning messages from parsing the cluster
519 maybeShowWarnings :: [String] -- ^ The warning messages
521 maybeShowWarnings fix_msgs =
522 unless (null fix_msgs) $ do
523 hPutStrLn stderr "Warning: cluster has inconsistent data:"
524 hPutStrLn stderr . unlines . map (printf " - %s") $ fix_msgs