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.Utils" is
5 used in many other places and this is more IO oriented.
11 Copyright (C) 2009, 2010, 2011, 2012 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
34 , Ganeti.HTools.CLI.parseOpts
88 import Data.Char (toUpper)
89 import Data.Maybe (fromMaybe)
90 import System.Console.GetOpt
92 import Text.Printf (printf)
94 import qualified Ganeti.HTools.Container as Container
95 import qualified Ganeti.HTools.Node as Node
96 import qualified Ganeti.Path as Path
97 import Ganeti.HTools.Types
98 import Ganeti.BasicTypes
99 import Ganeti.Common as Common
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 :: Maybe DiskTemplate -- ^ Override for the disk template
110 , optSpindleUse :: Maybe Int -- ^ Override for the spindle usage
111 , optDynuFile :: Maybe FilePath -- ^ Optional file with dynamic use data
112 , optEvacMode :: Bool -- ^ Enable evacuation mode
113 , optExInst :: [String] -- ^ Instances to be excluded
114 , optExTags :: Maybe [String] -- ^ Tags to use for exclusion
115 , optExecJobs :: Bool -- ^ Execute the commands via Luxi
116 , optGroup :: Maybe GroupID -- ^ The UUID of the group to process
117 , optIAllocSrc :: Maybe FilePath -- ^ The iallocation spec
118 , optSelInst :: [String] -- ^ Instances to be excluded
119 , optLuxi :: Maybe FilePath -- ^ Collect data from Luxi
120 , optMachineReadable :: Bool -- ^ Output machine-readable format
121 , optMaster :: String -- ^ Collect data from RAPI
122 , optMaxLength :: Int -- ^ Stop after this many steps
123 , optMcpu :: Maybe Double -- ^ Override max cpu ratio for nodes
124 , optMdsk :: Double -- ^ Max disk usage ratio for nodes
125 , optMinGain :: Score -- ^ Min gain we aim for in a step
126 , optMinGainLim :: Score -- ^ Limit below which we apply mingain
127 , optMinScore :: Score -- ^ The minimum score we aim for
128 , optNoHeaders :: Bool -- ^ Do not show a header line
129 , optNoSimulation :: Bool -- ^ Skip the rebalancing dry-run
130 , optNodeSim :: [String] -- ^ Cluster simulation mode
131 , optOffline :: [String] -- ^ Names of offline nodes
132 , optOutPath :: FilePath -- ^ Path to the output directory
133 , optSaveCluster :: Maybe FilePath -- ^ Save cluster state to this file
134 , optShowCmds :: Maybe FilePath -- ^ Whether to show the command list
135 , optShowHelp :: Bool -- ^ Just show the help
136 , optShowComp :: Bool -- ^ Just show the completion info
137 , optShowInsts :: Bool -- ^ Whether to show the instance map
138 , optShowNodes :: Maybe [String] -- ^ Whether to show node status
139 , optShowVer :: Bool -- ^ Just show the program version
140 , optStdSpec :: Maybe RSpec -- ^ Requested standard specs
141 , optTestCount :: Maybe Int -- ^ Optional test count override
142 , optTieredSpec :: Maybe RSpec -- ^ Requested specs for tiered mode
143 , optReplay :: Maybe String -- ^ Unittests: RNG state
144 , optVerbose :: Int -- ^ Verbosity level
147 -- | Default values for the command line options.
148 defaultOptions :: Options
149 defaultOptions = Options
150 { optDataFile = Nothing
151 , optDiskMoves = True
152 , optInstMoves = True
153 , optDiskTemplate = Nothing
154 , optSpindleUse = Nothing
155 , optDynuFile = Nothing
156 , optEvacMode = False
158 , optExTags = Nothing
159 , optExecJobs = False
161 , optIAllocSrc = Nothing
164 , optMachineReadable = False
168 , optMdsk = defReservedDiskRatio
170 , optMinGainLim = 1e-1
172 , optNoHeaders = False
173 , optNoSimulation = False
177 , optSaveCluster = Nothing
178 , optShowCmds = Nothing
179 , optShowHelp = False
180 , optShowComp = False
181 , optShowInsts = False
182 , optShowNodes = Nothing
184 , optStdSpec = Nothing
185 , optTestCount = Nothing
186 , optTieredSpec = Nothing
187 , optReplay = Nothing
191 -- | Abrreviation for the option type.
192 type OptType = GenericOptType Options
194 instance StandardOptions Options where
195 helpRequested = optShowHelp
196 verRequested = optShowVer
197 compRequested = optShowComp
198 requestHelp o = o { optShowHelp = True }
199 requestVer o = o { optShowVer = True }
200 requestComp o = o { optShowComp = True }
202 -- * Helper functions
204 parseISpecString :: String -> String -> Result RSpec
205 parseISpecString descr inp = do
206 let sp = sepSplit ',' inp
207 err = Bad ("Invalid " ++ descr ++ " specification: '" ++ inp ++
208 "', expected disk,ram,cpu")
209 when (length sp /= 3) err
210 prs <- mapM (\(fn, val) -> fn val) $
211 zip [ annotateResult (descr ++ " specs disk") . parseUnit
212 , annotateResult (descr ++ " specs memory") . parseUnit
213 , tryRead (descr ++ " specs cpus")
216 [dsk, ram, cpu] -> return $ RSpec cpu ram dsk
219 -- | Disk template choices.
220 optComplDiskTemplate :: OptCompletion
221 optComplDiskTemplate = OptComplChoices $
222 map diskTemplateToRaw [minBound..maxBound]
224 -- * Command line options
228 (Option "t" ["text-data"]
229 (ReqArg (\ f o -> Ok o { optDataFile = Just f }) "FILE")
230 "the cluster data FILE",
233 oDiskMoves :: OptType
235 (Option "" ["no-disk-moves"]
236 (NoArg (\ opts -> Ok opts { optDiskMoves = False}))
237 "disallow disk moves from the list of allowed instance changes,\
238 \ thus allowing only the 'cheap' failover/migrate operations",
241 oDiskTemplate :: OptType
243 (Option "" ["disk-template"]
244 (reqWithConversion diskTemplateFromRaw
245 (\dt opts -> Ok opts { optDiskTemplate = Just dt })
246 "TEMPLATE") "select the desired disk template",
247 optComplDiskTemplate)
249 oSpindleUse :: OptType
251 (Option "" ["spindle-use"]
252 (reqWithConversion (tryRead "parsing spindle-use")
255 fail "Invalid value of the spindle-use (expected >= 0)"
256 return $ opts { optSpindleUse = Just su })
257 "SPINDLES") "select how many virtual spindle instances use\
258 \ [default read from cluster]",
263 (Option "" ["select-instances"]
264 (ReqArg (\ f opts -> Ok opts { optSelInst = sepSplit ',' f }) "INSTS")
265 "only select given instances for any moves",
266 OptComplManyInstances)
268 oInstMoves :: OptType
270 (Option "" ["no-instance-moves"]
271 (NoArg (\ opts -> Ok opts { optInstMoves = False}))
272 "disallow instance (primary node) moves from the list of allowed,\
273 \ instance changes, thus allowing only slower, but sometimes\
274 \ safer, drbd secondary changes",
279 (Option "U" ["dynu-file"]
280 (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
281 "Import dynamic utilisation data from the given FILE",
286 (Option "E" ["evac-mode"]
287 (NoArg (\opts -> Ok opts { optEvacMode = True }))
288 "enable evacuation mode, where the algorithm only moves \
289 \ instances away from offline and drained nodes",
294 (Option "" ["exclude-instances"]
295 (ReqArg (\ f opts -> Ok opts { optExInst = sepSplit ',' f }) "INSTS")
296 "exclude given instances from any moves",
297 OptComplManyInstances)
301 (Option "" ["exclusion-tags"]
302 (ReqArg (\ f opts -> Ok opts { optExTags = Just $ sepSplit ',' f })
303 "TAG,...") "Enable instance exclusion based on given tag prefix",
309 (NoArg (\ opts -> Ok opts { optExecJobs = True}))
310 "execute the suggested moves via Luxi (only available when using\
311 \ it for data gathering)",
316 (Option "G" ["group"]
317 (ReqArg (\ f o -> Ok o { optGroup = Just f }) "ID")
318 "the ID of the group to balance",
321 oIAllocSrc :: OptType
323 (Option "I" ["ialloc-src"]
324 (ReqArg (\ f opts -> Ok opts { optIAllocSrc = Just f }) "FILE")
325 "Specify an iallocator spec as the cluster data source",
328 oLuxiSocket :: OptType
331 (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
332 fromMaybe Path.defaultLuxiSocket) "SOCKET")
333 "collect data via Luxi, optionally using the given SOCKET path",
336 oMachineReadable :: OptType
338 (Option "" ["machine-readable"]
339 (OptArg (\ f opts -> do
340 flag <- parseYesNo True f
341 return $ opts { optMachineReadable = flag }) "CHOICE")
342 "enable machine readable output (pass either 'yes' or 'no' to\
343 \ explicitly control the flag, or without an argument defaults to\
349 (Option "" ["max-cpu"]
350 (reqWithConversion (tryRead "parsing max-cpu")
353 fail "Invalid value of the max-cpu ratio, expected >0"
354 return $ opts { optMcpu = Just mcpu }) "RATIO")
355 "maximum virtual-to-physical cpu ratio for nodes (from 0\
356 \ upwards) [default read from cluster]",
359 oMaxSolLength :: OptType
361 (Option "l" ["max-length"]
362 (reqWithConversion (tryRead "max solution length")
363 (\i opts -> Ok opts { optMaxLength = i }) "N")
364 "cap the solution at this many balancing or allocation \
365 \ rounds (useful for very unbalanced clusters or empty \
371 (Option "" ["min-disk"]
372 (reqWithConversion (tryRead "min free disk space")
373 (\n opts -> Ok opts { optMdsk = n }) "RATIO")
374 "minimum free disk space for nodes (between 0 and 1) [0]",
379 (Option "g" ["min-gain"]
380 (reqWithConversion (tryRead "min gain")
381 (\g opts -> Ok opts { optMinGain = g }) "DELTA")
382 "minimum gain to aim for in a balancing step before giving up",
385 oMinGainLim :: OptType
387 (Option "" ["min-gain-limit"]
388 (reqWithConversion (tryRead "min gain limit")
389 (\g opts -> Ok opts { optMinGainLim = g }) "SCORE")
390 "minimum cluster score for which we start checking the min-gain",
395 (Option "e" ["min-score"]
396 (reqWithConversion (tryRead "min score")
397 (\e opts -> Ok opts { optMinScore = e }) "EPSILON")
398 "mininum score to aim for",
401 oNoHeaders :: OptType
403 (Option "" ["no-headers"]
404 (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
405 "do not show a header line",
408 oNoSimulation :: OptType
410 (Option "" ["no-simulation"]
411 (NoArg (\opts -> Ok opts {optNoSimulation = True}))
412 "do not perform rebalancing simulation",
417 (Option "" ["simulate"]
418 (ReqArg (\ f o -> Ok o { optNodeSim = f:optNodeSim o }) "SPEC")
419 "simulate an empty cluster, given as\
420 \ 'alloc_policy,num_nodes,disk,ram,cpu'",
423 oOfflineNode :: OptType
425 (Option "O" ["offline"]
426 (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
427 "set node as offline",
430 oOutputDir :: OptType
432 (Option "d" ["output-dir"]
433 (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
434 "directory in which to write output files",
437 oPrintCommands :: OptType
439 (Option "C" ["print-commands"]
440 (OptArg ((\ f opts -> Ok opts { optShowCmds = Just f }) .
443 "print the ganeti command list for reaching the solution,\
444 \ if an argument is passed then write the commands to a\
445 \ file named as such",
448 oPrintInsts :: OptType
450 (Option "" ["print-instances"]
451 (NoArg (\ opts -> Ok opts { optShowInsts = True }))
452 "print the final instance map",
455 oPrintNodes :: OptType
457 (Option "p" ["print-nodes"]
458 (OptArg ((\ f opts ->
459 let (prefix, realf) = case f of
460 '+':rest -> (["+"], rest)
462 splitted = prefix ++ sepSplit ',' realf
463 in Ok opts { optShowNodes = Just splitted }) .
464 fromMaybe []) "FIELDS")
465 "print the final node list",
470 (Option "q" ["quiet"]
471 (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
472 "decrease the verbosity level",
475 oRapiMaster :: OptType
477 (Option "m" ["master"]
478 (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
479 "collect data via RAPI at the given ADDRESS",
482 oSaveCluster :: OptType
485 (ReqArg (\ f opts -> Ok opts { optSaveCluster = Just f }) "FILE")
486 "Save cluster state at the end of the processing to FILE",
491 (Option "" ["standard-alloc"]
492 (ReqArg (\ inp opts -> do
493 tspec <- parseISpecString "standard" inp
494 return $ opts { optStdSpec = Just tspec } )
496 "enable standard specs allocation, given as 'disk,ram,cpu'",
499 oTieredSpec :: OptType
501 (Option "" ["tiered-alloc"]
502 (ReqArg (\ inp opts -> do
503 tspec <- parseISpecString "tiered" inp
504 return $ opts { optTieredSpec = Just tspec } )
506 "enable tiered specs allocation, given as 'disk,ram,cpu'",
511 (Option "v" ["verbose"]
512 (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
513 "increase the verbosity level",
516 -- | Generic options.
517 genericOpts :: [GenericOptType Options]
518 genericOpts = [ oShowVer
525 -- | Wrapper over 'Common.parseOpts' with our custom options.
526 parseOpts :: [String] -- ^ The command line arguments
527 -> String -- ^ The program name
528 -> [OptType] -- ^ The supported command line options
529 -> [ArgCompletion] -- ^ The supported command line arguments
530 -> IO (Options, [String]) -- ^ The resulting options and leftover
532 parseOpts = Common.parseOpts defaultOptions
535 -- | A shell script template for autogenerated scripts.
538 printf "#!/bin/sh\n\n\
539 \# Auto-generated script for executing cluster rebalancing\n\n\
540 \# To stop, touch the file /tmp/stop-htools\n\n\
543 \ if [ -f /tmp/stop-htools ]; then\n\
544 \ echo 'Stop requested, exiting'\n\
549 -- | Optionally print the node list.
550 maybePrintNodes :: Maybe [String] -- ^ The field list
551 -> String -- ^ Informational message
552 -> ([String] -> String) -- ^ Function to generate the listing
554 maybePrintNodes Nothing _ _ = return ()
555 maybePrintNodes (Just fields) msg fn = do
557 hPutStrLn stderr (msg ++ " status:")
558 hPutStrLn stderr $ fn fields
560 -- | Optionally print the instance list.
561 maybePrintInsts :: Bool -- ^ Whether to print the instance list
562 -> String -- ^ Type of the instance map (e.g. initial)
563 -> String -- ^ The instance data
565 maybePrintInsts do_print msg instdata =
568 hPutStrLn stderr $ msg ++ " instance map:"
569 hPutStr stderr instdata
571 -- | Function to display warning messages from parsing the cluster
573 maybeShowWarnings :: [String] -- ^ The warning messages
575 maybeShowWarnings fix_msgs =
576 unless (null fix_msgs) $ do
577 hPutStrLn stderr "Warning: cluster has inconsistent data:"
578 hPutStrLn stderr . unlines . map (printf " - %s") $ fix_msgs
580 -- | Format a list of key, value as a shell fragment.
581 printKeys :: String -- ^ Prefix to printed variables
582 -> [(String, String)] -- ^ List of (key, value) pairs to be printed
586 printf "%s_%s=%s\n" prefix (map toUpper k) (ensureQuoted v))
588 -- | Prints the final @OK@ marker in machine readable output.
589 printFinal :: String -- ^ Prefix to printed variable
590 -> Bool -- ^ Whether output should be machine readable;
591 -- note: if not, there is nothing to print
593 printFinal prefix True =
594 -- this should be the final entry
595 printKeys prefix [("OK", "1")]
597 printFinal _ False = return ()
599 -- | Potentially set the node as offline based on passed offline list.
600 setNodeOffline :: [Ndx] -> Node.Node -> Node.Node
601 setNodeOffline offline_indices n =
602 if Node.idx n `elem` offline_indices
603 then Node.setOffline n True
606 -- | Set node properties based on command line options.
607 setNodeStatus :: Options -> Node.List -> IO Node.List
608 setNodeStatus opts fixed_nl = do
609 let offline_passed = optOffline opts
610 all_nodes = Container.elems fixed_nl
611 offline_lkp = map (lookupName (map Node.name all_nodes)) offline_passed
612 offline_wrong = filter (not . goodLookupResult) offline_lkp
613 offline_names = map lrContent offline_lkp
614 offline_indices = map Node.idx $
615 filter (\n -> Node.name n `elem` offline_names)
620 unless (null offline_wrong) .
621 exitErr $ printf "wrong node name(s) set as offline: %s\n"
622 (commaJoin (map lrContent offline_wrong))
623 let setMCpuFn = case m_cpu of
625 Just new_mcpu -> flip Node.setMcpu new_mcpu
626 let nm = Container.map (setNodeOffline offline_indices .
627 flip Node.setMdsk m_dsk .