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
79 import Data.Maybe (fromMaybe)
80 import qualified Data.Version
81 import System.Console.GetOpt
85 import Text.Printf (printf)
87 import qualified Ganeti.HTools.Version as Version(version)
88 import qualified Ganeti.Constants as C
89 import Ganeti.HTools.Types
90 import Ganeti.HTools.Utils
94 -- | The default value for the luxi socket.
96 -- This is re-exported from the "Ganeti.Constants" module.
97 defaultLuxiSocket :: FilePath
98 defaultLuxiSocket = C.masterSocket
102 -- | Command line options structure.
103 data Options = Options
104 { optDataFile :: Maybe FilePath -- ^ Path to the cluster data file
105 , optDiskMoves :: Bool -- ^ Allow disk moves
106 , optInstMoves :: Bool -- ^ Allow instance moves
107 , optDiskTemplate :: DiskTemplate -- ^ The requested disk template
108 , optDynuFile :: Maybe FilePath -- ^ Optional file with dynamic use data
109 , optEvacMode :: Bool -- ^ Enable evacuation mode
110 , optExInst :: [String] -- ^ Instances to be excluded
111 , optExTags :: Maybe [String] -- ^ Tags to use for exclusion
112 , optExecJobs :: Bool -- ^ Execute the commands via Luxi
113 , optGroup :: Maybe GroupID -- ^ The UUID of the group to process
114 , optSelInst :: [String] -- ^ Instances to be excluded
115 , optISpec :: RSpec -- ^ Requested instance specs
116 , optLuxi :: Maybe FilePath -- ^ Collect data from Luxi
117 , optMaster :: String -- ^ Collect data from RAPI
118 , optMaxLength :: Int -- ^ Stop after this many steps
119 , optMcpu :: Double -- ^ Max cpu ratio for nodes
120 , optMdsk :: Double -- ^ Max disk usage ratio for nodes
121 , optMinGain :: Score -- ^ Min gain we aim for in a step
122 , optMinGainLim :: Score -- ^ Limit below which we apply mingain
123 , optMinScore :: Score -- ^ The minimum score we aim for
124 , optNoHeaders :: Bool -- ^ Do not show a header line
125 , optNodeSim :: [String] -- ^ Cluster simulation mode
126 , optOffline :: [String] -- ^ Names of offline nodes
127 , optOneline :: Bool -- ^ Switch output to a single line
128 , optOutPath :: FilePath -- ^ Path to the output directory
129 , optSaveCluster :: Maybe FilePath -- ^ Save cluster state to this file
130 , optShowCmds :: Maybe FilePath -- ^ Whether to show the command list
131 , optShowHelp :: Bool -- ^ Just show the help
132 , optShowInsts :: Bool -- ^ Whether to show the instance map
133 , optShowNodes :: Maybe [String] -- ^ Whether to show node status
134 , optShowVer :: Bool -- ^ Just show the program version
135 , optTieredSpec :: Maybe RSpec -- ^ Requested specs for tiered mode
136 , optReplay :: Maybe String -- ^ Unittests: RNG state
137 , optVerbose :: Int -- ^ Verbosity level
140 -- | Default values for the command line options.
141 defaultOptions :: Options
142 defaultOptions = Options
143 { optDataFile = Nothing
144 , optDiskMoves = True
145 , optInstMoves = True
146 , optDiskTemplate = DTDrbd8
147 , optDynuFile = Nothing
148 , optEvacMode = False
150 , optExTags = Nothing
151 , optExecJobs = False
154 , optISpec = RSpec 1 4096 102400
158 , optMcpu = defVcpuRatio
159 , optMdsk = defReservedDiskRatio
161 , optMinGainLim = 1e-1
163 , optNoHeaders = False
168 , optSaveCluster = Nothing
169 , optShowCmds = Nothing
170 , optShowHelp = False
171 , optShowInsts = False
172 , optShowNodes = Nothing
174 , optTieredSpec = Nothing
175 , optReplay = Nothing
179 -- | Abrreviation for the option type.
180 type OptType = OptDescr (Options -> Result Options)
182 -- * Command line options
185 oDataFile = Option "t" ["text-data"]
186 (ReqArg (\ f o -> Ok o { optDataFile = Just f }) "FILE")
187 "the cluster data FILE"
189 oDiskMoves :: OptType
190 oDiskMoves = Option "" ["no-disk-moves"]
191 (NoArg (\ opts -> Ok opts { optDiskMoves = False}))
192 "disallow disk moves from the list of allowed instance changes,\
193 \ thus allowing only the 'cheap' failover/migrate operations"
195 oDiskTemplate :: OptType
196 oDiskTemplate = Option "" ["disk-template"]
197 (ReqArg (\ t opts -> do
199 return $ opts { optDiskTemplate = dt }) "TEMPLATE")
200 "select the desired disk template"
203 oSelInst = Option "" ["select-instances"]
204 (ReqArg (\ f opts -> Ok opts { optSelInst = sepSplit ',' f }) "INSTS")
205 "only select given instances for any moves"
207 oInstMoves :: OptType
208 oInstMoves = Option "" ["no-instance-moves"]
209 (NoArg (\ opts -> Ok opts { optInstMoves = False}))
210 "disallow instance (primary node) moves from the list of allowed,\
211 \ instance changes, thus allowing only slower, but sometimes\
212 \ safer, drbd secondary changes"
215 oDynuFile = Option "U" ["dynu-file"]
216 (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
217 "Import dynamic utilisation data from the given FILE"
220 oEvacMode = Option "E" ["evac-mode"]
221 (NoArg (\opts -> Ok opts { optEvacMode = True }))
222 "enable evacuation mode, where the algorithm only moves \
223 \ instances away from offline and drained nodes"
226 oExInst = Option "" ["exclude-instances"]
227 (ReqArg (\ f opts -> Ok opts { optExInst = sepSplit ',' f }) "INSTS")
228 "exclude given instances from any moves"
231 oExTags = Option "" ["exclusion-tags"]
232 (ReqArg (\ f opts -> Ok opts { optExTags = Just $ sepSplit ',' f })
233 "TAG,...") "Enable instance exclusion based on given tag prefix"
236 oExecJobs = Option "X" ["exec"]
237 (NoArg (\ opts -> Ok opts { optExecJobs = True}))
238 "execute the suggested moves via Luxi (only available when using\
239 \ it for data gathering)"
242 oGroup = Option "G" ["group"]
243 (ReqArg (\ f o -> Ok o { optGroup = Just f }) "ID")
244 "the ID of the group to balance"
247 oIDisk = Option "" ["disk"]
249 let ospec = optISpec opts
250 nspec = ospec { rspecDsk = read d }
251 in Ok opts { optISpec = nspec }) "DISK")
252 "disk size for instances"
255 oIMem = Option "" ["memory"]
257 let ospec = optISpec opts
258 nspec = ospec { rspecMem = read m }
259 in Ok opts { optISpec = nspec }) "MEMORY")
260 "memory size for instances"
263 oIVcpus = Option "" ["vcpus"]
265 let ospec = optISpec opts
266 nspec = ospec { rspecCpu = read p }
267 in Ok opts { optISpec = nspec }) "NUM")
268 "number of virtual cpus for instances"
270 oLuxiSocket :: OptType
271 oLuxiSocket = Option "L" ["luxi"]
272 (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
273 fromMaybe defaultLuxiSocket) "SOCKET")
274 "collect data via Luxi, optionally using the given SOCKET path"
277 oMaxCpu = Option "" ["max-cpu"]
278 (ReqArg (\ n opts -> Ok opts { optMcpu = read n }) "RATIO")
279 "maximum virtual-to-physical cpu ratio for nodes (from 1\
282 oMaxSolLength :: OptType
283 oMaxSolLength = Option "l" ["max-length"]
284 (ReqArg (\ i opts -> Ok opts { optMaxLength = read i }) "N")
285 "cap the solution at this many moves (useful for very\
286 \ unbalanced clusters)"
289 oMinDisk = Option "" ["min-disk"]
290 (ReqArg (\ n opts -> Ok opts { optMdsk = read n }) "RATIO")
291 "minimum free disk space for nodes (between 0 and 1) [0]"
294 oMinGain = Option "g" ["min-gain"]
295 (ReqArg (\ g opts -> Ok opts { optMinGain = read g }) "DELTA")
296 "minimum gain to aim for in a balancing step before giving up"
298 oMinGainLim :: OptType
299 oMinGainLim = Option "" ["min-gain-limit"]
300 (ReqArg (\ g opts -> Ok opts { optMinGainLim = read g }) "SCORE")
301 "minimum cluster score for which we start checking the min-gain"
304 oMinScore = Option "e" ["min-score"]
305 (ReqArg (\ e opts -> Ok opts { optMinScore = read e }) "EPSILON")
306 "mininum score to aim for"
308 oNoHeaders :: OptType
309 oNoHeaders = Option "" ["no-headers"]
310 (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
311 "do not show a header line"
314 oNodeSim = Option "" ["simulate"]
315 (ReqArg (\ f o -> Ok o { optNodeSim = f:optNodeSim o }) "SPEC")
316 "simulate an empty cluster, given as 'num_nodes,disk,ram,cpu'"
318 oOfflineNode :: OptType
319 oOfflineNode = Option "O" ["offline"]
320 (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
321 "set node as offline"
324 oOneline = Option "o" ["oneline"]
325 (NoArg (\ opts -> Ok opts { optOneline = True }))
326 "print the ganeti command list for reaching the solution"
328 oOutputDir :: OptType
329 oOutputDir = Option "d" ["output-dir"]
330 (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
331 "directory in which to write output files"
333 oPrintCommands :: OptType
334 oPrintCommands = Option "C" ["print-commands"]
335 (OptArg ((\ f opts -> Ok opts { optShowCmds = Just f }) .
338 "print the ganeti command list for reaching the solution,\
339 \ if an argument is passed then write the commands to a\
340 \ file named as such"
342 oPrintInsts :: OptType
343 oPrintInsts = Option "" ["print-instances"]
344 (NoArg (\ opts -> Ok opts { optShowInsts = True }))
345 "print the final instance map"
347 oPrintNodes :: OptType
348 oPrintNodes = Option "p" ["print-nodes"]
349 (OptArg ((\ f opts ->
350 let (prefix, realf) = case f of
351 '+':rest -> (["+"], rest)
353 splitted = prefix ++ sepSplit ',' realf
354 in Ok opts { optShowNodes = Just splitted }) .
355 fromMaybe []) "FIELDS")
356 "print the final node list"
359 oQuiet = Option "q" ["quiet"]
360 (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
361 "decrease the verbosity level"
363 oRapiMaster :: OptType
364 oRapiMaster = Option "m" ["master"]
365 (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
366 "collect data via RAPI at the given ADDRESS"
368 oSaveCluster :: OptType
369 oSaveCluster = Option "S" ["save"]
370 (ReqArg (\ f opts -> Ok opts { optSaveCluster = Just f }) "FILE")
371 "Save cluster state at the end of the processing to FILE"
374 oShowHelp = Option "h" ["help"]
375 (NoArg (\ opts -> Ok opts { optShowHelp = True}))
379 oShowVer = Option "V" ["version"]
380 (NoArg (\ opts -> Ok opts { optShowVer = True}))
381 "show the version of the program"
383 oTieredSpec :: OptType
384 oTieredSpec = Option "" ["tiered-alloc"]
385 (ReqArg (\ inp opts -> do
386 let sp = sepSplit ',' inp
387 prs <- mapM (tryRead "tiered specs") sp
390 [dsk, ram, cpu] -> return $ RSpec cpu ram dsk
391 _ -> Bad $ "Invalid specification: " ++ inp ++
392 ", expected disk,ram,cpu"
393 return $ opts { optTieredSpec = Just tspec } )
395 "enable tiered specs allocation, given as 'disk,ram,cpu'"
398 oReplay = Option "" ["replay"]
399 (ReqArg (\ stat opts -> Ok opts { optReplay = Just stat } ) "STATE")
400 "Pre-seed the random number generator with STATE"
403 oVerbose = Option "v" ["verbose"]
404 (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
405 "increase the verbosity level"
410 usageHelp :: String -> [OptType] -> String
412 usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
413 progname Version.version progname)
415 -- | Command line parser, using the 'Options' structure.
416 parseOpts :: [String] -- ^ The command line arguments
417 -> String -- ^ The program name
418 -> [OptType] -- ^ The supported command line options
419 -> IO (Options, [String]) -- ^ The resulting options and leftover
421 parseOpts argv progname options =
422 case getOpt Permute options argv of
425 let (pr, args) = (foldM (flip id) defaultOptions o, n)
428 hPutStrLn stderr "Error while parsing command\
431 exitWith $ ExitFailure 1
432 Ok val -> return val)
433 when (optShowHelp po) $ do
434 putStr $ usageHelp progname options
436 when (optShowVer po) $ do
437 printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
438 progname Version.version
439 compilerName (Data.Version.showVersion compilerVersion)
444 hPutStrLn stderr $ "Command line error: " ++ concat errs
445 hPutStrLn stderr $ usageHelp progname options
446 exitWith $ ExitFailure 2
448 -- | A shell script template for autogenerated scripts.
451 printf "#!/bin/sh\n\n\
452 \# Auto-generated script for executing cluster rebalancing\n\n\
453 \# To stop, touch the file /tmp/stop-htools\n\n\
456 \ if [ -f /tmp/stop-htools ]; then\n\
457 \ echo 'Stop requested, exiting'\n\
462 -- | Optionally print the node list.
463 maybePrintNodes :: Maybe [String] -- ^ The field list
464 -> String -- ^ Informational message
465 -> ([String] -> String) -- ^ Function to generate the listing
467 maybePrintNodes Nothing _ _ = return ()
468 maybePrintNodes (Just fields) msg fn = do
470 hPutStrLn stderr (msg ++ " status:")
471 hPutStrLn stderr $ fn fields
474 -- | Optionally print the instance list.
475 maybePrintInsts :: Bool -- ^ Whether to print the instance list
476 -> String -- ^ Type of the instance map (e.g. initial)
477 -> String -- ^ The instance data
479 maybePrintInsts do_print msg instdata =
482 hPutStrLn stderr $ msg ++ " instance map:"
483 hPutStr stderr instdata