1 {-| Implementation of command-line functions.
3 This module holds the common cli-related functions for the binaries,
4 separated into this module since Utils.hs is used in many other places
5 and this is more IO oriented.
11 Copyright (C) 2009, 2010 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
73 import Data.Maybe (fromMaybe)
74 import qualified Data.Version
76 import System.Console.GetOpt
80 import Text.Printf (printf)
82 import qualified Ganeti.HTools.Version as Version(version)
83 import Ganeti.HTools.Types
84 import Ganeti.HTools.Utils
86 -- | The default value for the luxi socket
87 defaultLuxiSocket :: FilePath
88 defaultLuxiSocket = "/var/run/ganeti/socket/ganeti-master"
90 -- | Command line options structure.
91 data Options = Options
92 { optDataFile :: Maybe FilePath -- ^ Path to the cluster data file
93 , optDiskMoves :: Bool -- ^ Allow disk moves
94 , optDynuFile :: Maybe FilePath -- ^ Optional file with dynamic use data
95 , optEvacMode :: Bool -- ^ Enable evacuation mode
96 , optExInst :: [String] -- ^ Instances to be excluded
97 , optExTags :: Maybe [String] -- ^ Tags to use for exclusion
98 , optExecJobs :: Bool -- ^ Execute the commands via Luxi
99 , optGroup :: Maybe GroupID -- ^ The UUID of the group to process
100 , optINodes :: Int -- ^ Nodes required for an instance
101 , optISpec :: RSpec -- ^ Requested instance specs
102 , optLuxi :: Maybe FilePath -- ^ Collect data from Luxi
103 , optMaster :: String -- ^ Collect data from RAPI
104 , optMaxLength :: Int -- ^ Stop after this many steps
105 , optMcpu :: Double -- ^ Max cpu ratio for nodes
106 , optMdsk :: Double -- ^ Max disk usage ratio for nodes
107 , optMinGain :: Score -- ^ Min gain we aim for in a step
108 , optMinGainLim :: Score -- ^ Limit below which we apply mingain
109 , optMinScore :: Score -- ^ The minimum score we aim for
110 , optNoHeaders :: Bool -- ^ Do not show a header line
111 , optNodeSim :: Maybe String -- ^ Cluster simulation mode
112 , optOffline :: [String] -- ^ Names of offline nodes
113 , optOneline :: Bool -- ^ Switch output to a single line
114 , optOutPath :: FilePath -- ^ Path to the output directory
115 , optSaveCluster :: Maybe FilePath -- ^ Save cluster state to this file
116 , optShowCmds :: Maybe FilePath -- ^ Whether to show the command list
117 , optShowHelp :: Bool -- ^ Just show the help
118 , optShowInsts :: Bool -- ^ Whether to show the instance map
119 , optShowNodes :: Maybe [String] -- ^ Whether to show node status
120 , optShowVer :: Bool -- ^ Just show the program version
121 , optTieredSpec :: Maybe RSpec -- ^ Requested specs for tiered mode
122 , optVerbose :: Int -- ^ Verbosity level
125 -- | Default values for the command line options.
126 defaultOptions :: Options
127 defaultOptions = Options
128 { optDataFile = Nothing
129 , optDiskMoves = True
130 , optDynuFile = Nothing
131 , optEvacMode = False
133 , optExTags = Nothing
134 , optExecJobs = False
137 , optISpec = RSpec 1 4096 102400
141 , optMcpu = defVcpuRatio
142 , optMdsk = defReservedDiskRatio
144 , optMinGainLim = 1e-1
146 , optNoHeaders = False
147 , optNodeSim = Nothing
151 , optSaveCluster = Nothing
152 , optShowCmds = Nothing
153 , optShowHelp = False
154 , optShowInsts = False
155 , optShowNodes = Nothing
157 , optTieredSpec = Nothing
161 -- | Abrreviation for the option type
162 type OptType = OptDescr (Options -> Result Options)
165 oDataFile = Option "t" ["text-data"]
166 (ReqArg (\ f o -> Ok o { optDataFile = Just f }) "FILE")
167 "the cluster data FILE"
169 oDiskMoves :: OptType
170 oDiskMoves = Option "" ["no-disk-moves"]
171 (NoArg (\ opts -> Ok opts { optDiskMoves = False}))
172 "disallow disk moves from the list of allowed instance changes,\
173 \ thus allowing only the 'cheap' failover/migrate operations"
176 oDynuFile = Option "U" ["dynu-file"]
177 (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
178 "Import dynamic utilisation data from the given FILE"
181 oEvacMode = Option "E" ["evac-mode"]
182 (NoArg (\opts -> Ok opts { optEvacMode = True }))
183 "enable evacuation mode, where the algorithm only moves \
184 \ instances away from offline and drained nodes"
187 oExInst = Option "" ["exclude-instances"]
188 (ReqArg (\ f opts -> Ok opts { optExInst = sepSplit ',' f }) "INSTS")
189 "exclude given instances from any moves"
192 oExTags = Option "" ["exclusion-tags"]
193 (ReqArg (\ f opts -> Ok opts { optExTags = Just $ sepSplit ',' f })
194 "TAG,...") "Enable instance exclusion based on given tag prefix"
197 oExecJobs = Option "X" ["exec"]
198 (NoArg (\ opts -> Ok opts { optExecJobs = True}))
199 "execute the suggested moves via Luxi (only available when using\
200 \ it for data gathering)"
203 oGroup = Option "G" ["group"]
204 (ReqArg (\ f o -> Ok o { optGroup = Just f }) "ID")
205 "the ID of the group to balance"
208 oIDisk = Option "" ["disk"]
210 let ospec = optISpec opts
211 nspec = ospec { rspecDsk = read d }
212 in Ok opts { optISpec = nspec }) "DISK")
213 "disk size for instances"
216 oIMem = Option "" ["memory"]
218 let ospec = optISpec opts
219 nspec = ospec { rspecMem = read m }
220 in Ok opts { optISpec = nspec }) "MEMORY")
221 "memory size for instances"
224 oINodes = Option "" ["req-nodes"]
225 (ReqArg (\ n opts -> Ok opts { optINodes = read n }) "NODES")
226 "number of nodes for the new instances (1=plain, 2=mirrored)"
229 oIVcpus = Option "" ["vcpus"]
231 let ospec = optISpec opts
232 nspec = ospec { rspecCpu = read p }
233 in Ok opts { optISpec = nspec }) "NUM")
234 "number of virtual cpus for instances"
236 oLuxiSocket :: OptType
237 oLuxiSocket = Option "L" ["luxi"]
238 (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
239 fromMaybe defaultLuxiSocket) "SOCKET")
240 "collect data via Luxi, optionally using the given SOCKET path"
243 oMaxCpu = Option "" ["max-cpu"]
244 (ReqArg (\ n opts -> Ok opts { optMcpu = read n }) "RATIO")
245 "maximum virtual-to-physical cpu ratio for nodes (from 1\
248 oMaxSolLength :: OptType
249 oMaxSolLength = Option "l" ["max-length"]
250 (ReqArg (\ i opts -> Ok opts { optMaxLength = read i }) "N")
251 "cap the solution at this many moves (useful for very\
252 \ unbalanced clusters)"
255 oMinDisk = Option "" ["min-disk"]
256 (ReqArg (\ n opts -> Ok opts { optMdsk = read n }) "RATIO")
257 "minimum free disk space for nodes (between 0 and 1) [0]"
260 oMinGain = Option "g" ["min-gain"]
261 (ReqArg (\ g opts -> Ok opts { optMinGain = read g }) "DELTA")
262 "minimum gain to aim for in a balancing step before giving up"
264 oMinGainLim :: OptType
265 oMinGainLim = Option "" ["min-gain-limit"]
266 (ReqArg (\ g opts -> Ok opts { optMinGainLim = read g }) "SCORE")
267 "minimum cluster score for which we start checking the min-gain"
270 oMinScore = Option "e" ["min-score"]
271 (ReqArg (\ e opts -> Ok opts { optMinScore = read e }) "EPSILON")
272 "mininum score to aim for"
274 oNoHeaders :: OptType
275 oNoHeaders = Option "" ["no-headers"]
276 (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
277 "do not show a header line"
280 oNodeSim = Option "" ["simulate"]
281 (ReqArg (\ f o -> Ok o { optNodeSim = Just f }) "SPEC")
282 "simulate an empty cluster, given as 'num_nodes,disk,ram,cpu'"
284 oOfflineNode :: OptType
285 oOfflineNode = Option "O" ["offline"]
286 (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
287 "set node as offline"
290 oOneline = Option "o" ["oneline"]
291 (NoArg (\ opts -> Ok opts { optOneline = True }))
292 "print the ganeti command list for reaching the solution"
294 oOutputDir :: OptType
295 oOutputDir = Option "d" ["output-dir"]
296 (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
297 "directory in which to write output files"
299 oPrintCommands :: OptType
300 oPrintCommands = Option "C" ["print-commands"]
301 (OptArg ((\ f opts -> Ok opts { optShowCmds = Just f }) .
304 "print the ganeti command list for reaching the solution,\
305 \ if an argument is passed then write the commands to a\
306 \ file named as such"
308 oPrintInsts :: OptType
309 oPrintInsts = Option "" ["print-instances"]
310 (NoArg (\ opts -> Ok opts { optShowInsts = True }))
311 "print the final instance map"
313 oPrintNodes :: OptType
314 oPrintNodes = Option "p" ["print-nodes"]
315 (OptArg ((\ f opts ->
316 let (prefix, realf) = case f of
317 '+':rest -> (["+"], rest)
319 splitted = prefix ++ sepSplit ',' realf
320 in Ok opts { optShowNodes = Just splitted }) .
321 fromMaybe []) "FIELDS")
322 "print the final node list"
325 oQuiet = Option "q" ["quiet"]
326 (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
327 "decrease the verbosity level"
329 oRapiMaster :: OptType
330 oRapiMaster = Option "m" ["master"]
331 (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
332 "collect data via RAPI at the given ADDRESS"
334 oSaveCluster :: OptType
335 oSaveCluster = Option "S" ["save"]
336 (ReqArg (\ f opts -> Ok opts { optSaveCluster = Just f }) "FILE")
337 "Save cluster state at the end of the processing to FILE"
340 oShowHelp = Option "h" ["help"]
341 (NoArg (\ opts -> Ok opts { optShowHelp = True}))
345 oShowVer = Option "V" ["version"]
346 (NoArg (\ opts -> Ok opts { optShowVer = True}))
347 "show the version of the program"
349 oTieredSpec :: OptType
350 oTieredSpec = Option "" ["tiered-alloc"]
351 (ReqArg (\ inp opts -> do
352 let sp = sepSplit ',' inp
353 prs <- mapM (tryRead "tiered specs") sp
356 [dsk, ram, cpu] -> return $ RSpec cpu ram dsk
357 _ -> Bad $ "Invalid specification: " ++ inp ++
358 ", expected disk,ram,cpu"
359 return $ opts { optTieredSpec = Just tspec } )
361 "enable tiered specs allocation, given as 'disk,ram,cpu'"
364 oVerbose = Option "v" ["verbose"]
365 (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
366 "increase the verbosity level"
369 usageHelp :: String -> [OptType] -> String
371 usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
372 progname Version.version progname)
374 -- | Command line parser, using the 'options' structure.
375 parseOpts :: [String] -- ^ The command line arguments
376 -> String -- ^ The program name
377 -> [OptType] -- ^ The supported command line options
378 -> IO (Options, [String]) -- ^ The resulting options and leftover
380 parseOpts argv progname options =
381 case getOpt Permute options argv of
384 let (pr, args) = (foldM (flip id) defaultOptions o, n)
387 hPutStrLn stderr "Error while parsing command\
390 exitWith $ ExitFailure 1
391 Ok val -> return val)
392 when (optShowHelp po) $ do
393 putStr $ usageHelp progname options
395 when (optShowVer po) $ do
396 printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
397 progname Version.version
398 compilerName (Data.Version.showVersion compilerVersion)
403 hPutStrLn stderr $ "Command line error: " ++ concat errs
404 hPutStrLn stderr $ usageHelp progname options
405 exitWith $ ExitFailure 2
407 -- | A shell script template for autogenerated scripts.
410 printf "#!/bin/sh\n\n\
411 \# Auto-generated script for executing cluster rebalancing\n\n\
412 \# To stop, touch the file /tmp/stop-htools\n\n\
415 \ if [ -f /tmp/stop-htools ]; then\n\
416 \ echo 'Stop requested, exiting'\n\