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
72 import Data.Maybe (fromMaybe)
73 import qualified Data.Version
75 import System.Console.GetOpt
79 import Text.Printf (printf)
81 import qualified Ganeti.HTools.Version as Version(version)
82 import Ganeti.HTools.Types
83 import Ganeti.HTools.Utils
85 -- | The default value for the luxi socket
86 defaultLuxiSocket :: FilePath
87 defaultLuxiSocket = "/var/run/ganeti/socket/ganeti-master"
89 -- | Command line options structure.
90 data Options = Options
91 { optDataFile :: Maybe FilePath -- ^ Path to the cluster data file
92 , optDiskMoves :: Bool -- ^ Allow disk moves
93 , optDynuFile :: Maybe FilePath -- ^ Optional file with dynamic use data
94 , optEvacMode :: Bool -- ^ Enable evacuation mode
95 , optExInst :: [String] -- ^ Instances to be excluded
96 , optExTags :: Maybe [String] -- ^ Tags to use for exclusion
97 , optExecJobs :: Bool -- ^ Execute the commands via Luxi
98 , optINodes :: Int -- ^ Nodes required for an instance
99 , optISpec :: RSpec -- ^ Requested instance specs
100 , optLuxi :: Maybe FilePath -- ^ Collect data from Luxi
101 , optMaster :: String -- ^ Collect data from RAPI
102 , optMaxLength :: Int -- ^ Stop after this many steps
103 , optMcpu :: Double -- ^ Max cpu ratio for nodes
104 , optMdsk :: Double -- ^ Max disk usage ratio for nodes
105 , optMinGain :: Score -- ^ Min gain we aim for in a step
106 , optMinGainLim :: Score -- ^ Limit below which we apply mingain
107 , optMinScore :: Score -- ^ The minimum score we aim for
108 , optNoHeaders :: Bool -- ^ Do not show a header line
109 , optNodeSim :: Maybe String -- ^ Cluster simulation mode
110 , optOffline :: [String] -- ^ Names of offline nodes
111 , optOneline :: Bool -- ^ Switch output to a single line
112 , optOutPath :: FilePath -- ^ Path to the output directory
113 , optSaveCluster :: Maybe FilePath -- ^ Save cluster state to this file
114 , optShowCmds :: Maybe FilePath -- ^ Whether to show the command list
115 , optShowHelp :: Bool -- ^ Just show the help
116 , optShowInsts :: Bool -- ^ Whether to show the instance map
117 , optShowNodes :: Maybe [String] -- ^ Whether to show node status
118 , optShowVer :: Bool -- ^ Just show the program version
119 , optTieredSpec :: Maybe RSpec -- ^ Requested specs for tiered mode
120 , optVerbose :: Int -- ^ Verbosity level
123 -- | Default values for the command line options.
124 defaultOptions :: Options
125 defaultOptions = Options
126 { optDataFile = Nothing
127 , optDiskMoves = True
128 , optDynuFile = Nothing
129 , optEvacMode = False
131 , optExTags = Nothing
132 , optExecJobs = False
134 , optISpec = RSpec 1 4096 102400
138 , optMcpu = defVcpuRatio
139 , optMdsk = defReservedDiskRatio
141 , optMinGainLim = 1e-1
143 , optNoHeaders = False
144 , optNodeSim = Nothing
148 , optSaveCluster = Nothing
149 , optShowCmds = Nothing
150 , optShowHelp = False
151 , optShowInsts = False
152 , optShowNodes = Nothing
154 , optTieredSpec = Nothing
158 -- | Abrreviation for the option type
159 type OptType = OptDescr (Options -> Result Options)
162 oDataFile = Option "t" ["text-data"]
163 (ReqArg (\ f o -> Ok o { optDataFile = Just f }) "FILE")
164 "the cluster data FILE"
166 oDiskMoves :: OptType
167 oDiskMoves = Option "" ["no-disk-moves"]
168 (NoArg (\ opts -> Ok opts { optDiskMoves = False}))
169 "disallow disk moves from the list of allowed instance changes,\
170 \ thus allowing only the 'cheap' failover/migrate operations"
173 oDynuFile = Option "U" ["dynu-file"]
174 (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
175 "Import dynamic utilisation data from the given FILE"
178 oEvacMode = Option "E" ["evac-mode"]
179 (NoArg (\opts -> Ok opts { optEvacMode = True }))
180 "enable evacuation mode, where the algorithm only moves \
181 \ instances away from offline and drained nodes"
184 oExInst = Option "" ["exclude-instances"]
185 (ReqArg (\ f opts -> Ok opts { optExInst = sepSplit ',' f }) "INSTS")
186 "exclude given instances from any moves"
189 oExTags = Option "" ["exclusion-tags"]
190 (ReqArg (\ f opts -> Ok opts { optExTags = Just $ sepSplit ',' f })
191 "TAG,...") "Enable instance exclusion based on given tag prefix"
194 oExecJobs = Option "X" ["exec"]
195 (NoArg (\ opts -> Ok opts { optExecJobs = True}))
196 "execute the suggested moves via Luxi (only available when using\
197 \ it for data gathering)"
200 oIDisk = Option "" ["disk"]
202 let ospec = optISpec opts
203 nspec = ospec { rspecDsk = read d }
204 in Ok opts { optISpec = nspec }) "DISK")
205 "disk size for instances"
208 oIMem = Option "" ["memory"]
210 let ospec = optISpec opts
211 nspec = ospec { rspecMem = read m }
212 in Ok opts { optISpec = nspec }) "MEMORY")
213 "memory size for instances"
216 oINodes = Option "" ["req-nodes"]
217 (ReqArg (\ n opts -> Ok opts { optINodes = read n }) "NODES")
218 "number of nodes for the new instances (1=plain, 2=mirrored)"
221 oIVcpus = Option "" ["vcpus"]
223 let ospec = optISpec opts
224 nspec = ospec { rspecCpu = read p }
225 in Ok opts { optISpec = nspec }) "NUM")
226 "number of virtual cpus for instances"
228 oLuxiSocket :: OptType
229 oLuxiSocket = Option "L" ["luxi"]
230 (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
231 fromMaybe defaultLuxiSocket) "SOCKET")
232 "collect data via Luxi, optionally using the given SOCKET path"
235 oMaxCpu = Option "" ["max-cpu"]
236 (ReqArg (\ n opts -> Ok opts { optMcpu = read n }) "RATIO")
237 "maximum virtual-to-physical cpu ratio for nodes (from 1\
240 oMaxSolLength :: OptType
241 oMaxSolLength = Option "l" ["max-length"]
242 (ReqArg (\ i opts -> Ok opts { optMaxLength = read i }) "N")
243 "cap the solution at this many moves (useful for very\
244 \ unbalanced clusters)"
247 oMinDisk = Option "" ["min-disk"]
248 (ReqArg (\ n opts -> Ok opts { optMdsk = read n }) "RATIO")
249 "minimum free disk space for nodes (between 0 and 1) [0]"
252 oMinGain = Option "g" ["min-gain"]
253 (ReqArg (\ g opts -> Ok opts { optMinGain = read g }) "DELTA")
254 "minimum gain to aim for in a balancing step before giving up"
256 oMinGainLim :: OptType
257 oMinGainLim = Option "" ["min-gain-limit"]
258 (ReqArg (\ g opts -> Ok opts { optMinGainLim = read g }) "SCORE")
259 "minimum cluster score for which we start checking the min-gain"
262 oMinScore = Option "e" ["min-score"]
263 (ReqArg (\ e opts -> Ok opts { optMinScore = read e }) "EPSILON")
264 "mininum score to aim for"
266 oNoHeaders :: OptType
267 oNoHeaders = Option "" ["no-headers"]
268 (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
269 "do not show a header line"
272 oNodeSim = Option "" ["simulate"]
273 (ReqArg (\ f o -> Ok o { optNodeSim = Just f }) "SPEC")
274 "simulate an empty cluster, given as 'num_nodes,disk,ram,cpu'"
276 oOfflineNode :: OptType
277 oOfflineNode = Option "O" ["offline"]
278 (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
279 "set node as offline"
282 oOneline = Option "o" ["oneline"]
283 (NoArg (\ opts -> Ok opts { optOneline = True }))
284 "print the ganeti command list for reaching the solution"
286 oOutputDir :: OptType
287 oOutputDir = Option "d" ["output-dir"]
288 (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
289 "directory in which to write output files"
291 oPrintCommands :: OptType
292 oPrintCommands = Option "C" ["print-commands"]
293 (OptArg ((\ f opts -> Ok opts { optShowCmds = Just f }) .
296 "print the ganeti command list for reaching the solution,\
297 \ if an argument is passed then write the commands to a\
298 \ file named as such"
300 oPrintInsts :: OptType
301 oPrintInsts = Option "" ["print-instances"]
302 (NoArg (\ opts -> Ok opts { optShowInsts = True }))
303 "print the final instance map"
305 oPrintNodes :: OptType
306 oPrintNodes = Option "p" ["print-nodes"]
307 (OptArg ((\ f opts ->
308 let (prefix, realf) = case f of
309 '+':rest -> (["+"], rest)
311 splitted = prefix ++ sepSplit ',' realf
312 in Ok opts { optShowNodes = Just splitted }) .
313 fromMaybe []) "FIELDS")
314 "print the final node list"
317 oQuiet = Option "q" ["quiet"]
318 (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
319 "decrease the verbosity level"
321 oRapiMaster :: OptType
322 oRapiMaster = Option "m" ["master"]
323 (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
324 "collect data via RAPI at the given ADDRESS"
326 oSaveCluster :: OptType
327 oSaveCluster = Option "S" ["save"]
328 (ReqArg (\ f opts -> Ok opts { optSaveCluster = Just f }) "FILE")
329 "Save cluster state at the end of the processing to FILE"
332 oShowHelp = Option "h" ["help"]
333 (NoArg (\ opts -> Ok opts { optShowHelp = True}))
337 oShowVer = Option "V" ["version"]
338 (NoArg (\ opts -> Ok opts { optShowVer = True}))
339 "show the version of the program"
341 oTieredSpec :: OptType
342 oTieredSpec = Option "" ["tiered-alloc"]
343 (ReqArg (\ inp opts -> do
344 let sp = sepSplit ',' inp
345 prs <- mapM (tryRead "tiered specs") sp
348 [dsk, ram, cpu] -> return $ RSpec cpu ram dsk
349 _ -> Bad $ "Invalid specification: " ++ inp
350 return $ opts { optTieredSpec = Just tspec } )
352 "enable tiered specs allocation, given as 'disk,ram,cpu'"
355 oVerbose = Option "v" ["verbose"]
356 (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
357 "increase the verbosity level"
360 usageHelp :: String -> [OptType] -> String
362 usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
363 progname Version.version progname)
365 -- | Command line parser, using the 'options' structure.
366 parseOpts :: [String] -- ^ The command line arguments
367 -> String -- ^ The program name
368 -> [OptType] -- ^ The supported command line options
369 -> IO (Options, [String]) -- ^ The resulting options and leftover
371 parseOpts argv progname options =
372 case getOpt Permute options argv of
375 let (pr, args) = (foldM (flip id) defaultOptions o, n)
378 hPutStrLn stderr "Error while parsing command\
381 exitWith $ ExitFailure 1
382 Ok val -> return val)
383 when (optShowHelp po) $ do
384 putStr $ usageHelp progname options
386 when (optShowVer po) $ do
387 printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
388 progname Version.version
389 compilerName (Data.Version.showVersion compilerVersion)
394 hPutStrLn stderr $ "Command line error: " ++ concat errs
395 hPutStrLn stderr $ usageHelp progname options
396 exitWith $ ExitFailure 2
398 -- | A shell script template for autogenerated scripts.
401 printf "#!/bin/sh\n\n\
402 \# Auto-generated script for executing cluster rebalancing\n\n\
403 \# To stop, touch the file /tmp/stop-htools\n\n\
406 \ if [ -f /tmp/stop-htools ]; then\n\
407 \ echo 'Stop requested, exiting'\n\