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 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
67 import Data.Maybe (fromMaybe)
68 import qualified Data.Version
70 import System.Console.GetOpt
74 import Text.Printf (printf)
76 import qualified Ganeti.HTools.Version as Version(version)
77 import Ganeti.HTools.Types
78 import Ganeti.HTools.Utils
80 -- | The default value for the luxi socket
81 defaultLuxiSocket :: FilePath
82 defaultLuxiSocket = "/var/run/ganeti/socket/ganeti-master"
84 -- | Command line options structure.
85 data Options = Options
86 { optDataFile :: Maybe FilePath -- ^ Path to the cluster data file
87 , optDiskMoves :: Bool -- ^ Allow disk moves
88 , optDynuFile :: Maybe FilePath -- ^ Optional file with dynamic use data
89 , optEvacMode :: Bool -- ^ Enable evacuation mode
90 , optExTags :: Maybe [String] -- ^ Tags to use for exclusion
91 , optExecJobs :: Bool -- ^ Execute the commands via Luxi
92 , optINodes :: Int -- ^ Nodes required for an instance
93 , optISpec :: RSpec -- ^ Requested instance specs
94 , optLuxi :: Maybe FilePath -- ^ Collect data from Luxi
95 , optMaster :: String -- ^ Collect data from RAPI
96 , optMaxLength :: Int -- ^ Stop after this many steps
97 , optMcpu :: Double -- ^ Max cpu ratio for nodes
98 , optMdsk :: Double -- ^ Max disk usage ratio for nodes
99 , optMinScore :: Score -- ^ The minimum score we aim for
100 , optNoHeaders :: Bool -- ^ Do not show a header line
101 , optNodeSim :: Maybe String -- ^ Cluster simulation mode
102 , optOffline :: [String] -- ^ Names of offline nodes
103 , optOneline :: Bool -- ^ Switch output to a single line
104 , optOutPath :: FilePath -- ^ Path to the output directory
105 , optShowCmds :: Maybe FilePath -- ^ Whether to show the command list
106 , optShowHelp :: Bool -- ^ Just show the help
107 , optShowInsts :: Bool -- ^ Whether to show the instance map
108 , optShowNodes :: Maybe [String] -- ^ Whether to show node status
109 , optShowVer :: Bool -- ^ Just show the program version
110 , optTieredSpec :: Maybe RSpec -- ^ Requested specs for tiered mode
111 , optVerbose :: Int -- ^ Verbosity level
114 -- | Default values for the command line options.
115 defaultOptions :: Options
116 defaultOptions = Options
117 { optDataFile = Nothing
118 , optDiskMoves = True
119 , optDynuFile = Nothing
120 , optEvacMode = False
121 , optExTags = Nothing
122 , optExecJobs = False
124 , optISpec = RSpec 1 4096 102400
131 , optNoHeaders = False
132 , optNodeSim = Nothing
136 , optShowCmds = Nothing
137 , optShowHelp = False
138 , optShowInsts = False
139 , optShowNodes = Nothing
141 , optTieredSpec = Nothing
145 -- | Abrreviation for the option type
146 type OptType = OptDescr (Options -> Result Options)
149 oDataFile = Option "t" ["text-data"]
150 (ReqArg (\ f o -> Ok o { optDataFile = Just f }) "FILE")
151 "the cluster data FILE"
153 oDiskMoves :: OptType
154 oDiskMoves = Option "" ["no-disk-moves"]
155 (NoArg (\ opts -> Ok opts { optDiskMoves = False}))
156 "disallow disk moves from the list of allowed instance changes,\
157 \ thus allowing only the 'cheap' failover/migrate operations"
160 oDynuFile = Option "U" ["dynu-file"]
161 (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
162 "Import dynamic utilisation data from the given FILE"
165 oEvacMode = Option "E" ["evac-mode"]
166 (NoArg (\opts -> Ok opts { optEvacMode = True }))
167 "enable evacuation mode, where the algorithm only moves \
168 \ instances away from offline and drained nodes"
171 oExTags = Option "" ["exclusion-tags"]
172 (ReqArg (\ f opts -> Ok opts { optExTags = Just $ sepSplit ',' f })
173 "TAG,...") "Enable instance exclusion based on given tag prefix"
176 oExecJobs = Option "X" ["exec"]
177 (NoArg (\ opts -> Ok opts { optExecJobs = True}))
178 "execute the suggested moves via Luxi (only available when using\
179 \ it for data gathering)"
182 oIDisk = Option "" ["disk"]
184 let ospec = optISpec opts
185 nspec = ospec { rspecDsk = read d }
186 in Ok opts { optISpec = nspec }) "DISK")
187 "disk size for instances"
190 oIMem = Option "" ["memory"]
192 let ospec = optISpec opts
193 nspec = ospec { rspecMem = read m }
194 in Ok opts { optISpec = nspec }) "MEMORY")
195 "memory size for instances"
198 oINodes = Option "" ["req-nodes"]
199 (ReqArg (\ n opts -> Ok opts { optINodes = read n }) "NODES")
200 "number of nodes for the new instances (1=plain, 2=mirrored)"
203 oIVcpus = Option "" ["vcpus"]
205 let ospec = optISpec opts
206 nspec = ospec { rspecCpu = read p }
207 in Ok opts { optISpec = nspec }) "NUM")
208 "number of virtual cpus for instances"
210 oLuxiSocket :: OptType
211 oLuxiSocket = Option "L" ["luxi"]
212 (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
213 fromMaybe defaultLuxiSocket) "SOCKET")
214 "collect data via Luxi, optionally using the given SOCKET path"
217 oMaxCpu = Option "" ["max-cpu"]
218 (ReqArg (\ n opts -> Ok opts { optMcpu = read n }) "RATIO")
219 "maximum virtual-to-physical cpu ratio for nodes"
221 oMaxSolLength :: OptType
222 oMaxSolLength = Option "l" ["max-length"]
223 (ReqArg (\ i opts -> Ok opts { optMaxLength = read i }) "N")
224 "cap the solution at this many moves (useful for very\
225 \ unbalanced clusters)"
228 oMinDisk = Option "" ["min-disk"]
229 (ReqArg (\ n opts -> Ok opts { optMdsk = read n }) "RATIO")
230 "minimum free disk space for nodes (between 0 and 1)"
233 oMinScore = Option "e" ["min-score"]
234 (ReqArg (\ e opts -> Ok opts { optMinScore = read e }) "EPSILON")
235 " mininum score to aim for"
237 oNoHeaders :: OptType
238 oNoHeaders = Option "" ["no-headers"]
239 (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
240 "do not show a header line"
243 oNodeSim = Option "" ["simulate"]
244 (ReqArg (\ f o -> Ok o { optNodeSim = Just f }) "SPEC")
245 "simulate an empty cluster, given as 'num_nodes,disk,ram,cpu'"
247 oOfflineNode :: OptType
248 oOfflineNode = Option "O" ["offline"]
249 (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
250 "set node as offline"
253 oOneline = Option "o" ["oneline"]
254 (NoArg (\ opts -> Ok opts { optOneline = True }))
255 "print the ganeti command list for reaching the solution"
257 oOutputDir :: OptType
258 oOutputDir = Option "d" ["output-dir"]
259 (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
260 "directory in which to write output files"
262 oPrintCommands :: OptType
263 oPrintCommands = Option "C" ["print-commands"]
264 (OptArg ((\ f opts -> Ok opts { optShowCmds = Just f }) .
267 "print the ganeti command list for reaching the solution,\
268 \ if an argument is passed then write the commands to a\
269 \ file named as such"
271 oPrintInsts :: OptType
272 oPrintInsts = Option "" ["print-instances"]
273 (NoArg (\ opts -> Ok opts { optShowInsts = True }))
274 "print the final instance map"
276 oPrintNodes :: OptType
277 oPrintNodes = Option "p" ["print-nodes"]
278 (OptArg ((\ f opts ->
279 let splitted = sepSplit ',' f
280 in Ok opts { optShowNodes = Just splitted }) .
281 fromMaybe []) "FIELDS")
282 "print the final node list"
285 oQuiet = Option "q" ["quiet"]
286 (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
287 "decrease the verbosity level"
289 oRapiMaster :: OptType
290 oRapiMaster = Option "m" ["master"]
291 (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
292 "collect data via RAPI at the given ADDRESS"
295 oShowHelp = Option "h" ["help"]
296 (NoArg (\ opts -> Ok opts { optShowHelp = True}))
300 oShowVer = Option "V" ["version"]
301 (NoArg (\ opts -> Ok opts { optShowVer = True}))
302 "show the version of the program"
304 oTieredSpec :: OptType
305 oTieredSpec = Option "" ["tiered-alloc"]
306 (ReqArg (\ inp opts -> do
307 let sp = sepSplit ',' inp
308 prs <- mapM (tryRead "tiered specs") sp
311 [dsk, ram, cpu] -> return $ RSpec cpu ram dsk
312 _ -> Bad $ "Invalid specification: " ++ inp
313 return $ opts { optTieredSpec = Just tspec } )
315 "enable tiered specs allocation, given as 'disk,ram,cpu'"
318 oVerbose = Option "v" ["verbose"]
319 (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
320 "increase the verbosity level"
323 usageHelp :: String -> [OptType] -> String
325 usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
326 progname Version.version progname)
328 -- | Command line parser, using the 'options' structure.
329 parseOpts :: [String] -- ^ The command line arguments
330 -> String -- ^ The program name
331 -> [OptType] -- ^ The supported command line options
332 -> IO (Options, [String]) -- ^ The resulting options and leftover
334 parseOpts argv progname options =
335 case getOpt Permute options argv of
338 let (pr, args) = (foldM (flip id) defaultOptions o, n)
341 hPutStrLn stderr "Error while parsing command\
344 exitWith $ ExitFailure 1
345 Ok val -> return val)
346 when (optShowHelp po) $ do
347 putStr $ usageHelp progname options
349 when (optShowVer po) $ do
350 printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
351 progname Version.version
352 compilerName (Data.Version.showVersion compilerVersion)
357 hPutStrLn stderr $ "Command line error: " ++ concat errs
358 hPutStrLn stderr $ usageHelp progname options
359 exitWith $ ExitFailure 2
361 -- | A shell script template for autogenerated scripts.
364 printf "#!/bin/sh\n\n\
365 \# Auto-generated script for executing cluster rebalancing\n\n\
366 \# To stop, touch the file /tmp/stop-htools\n\n\
369 \ if [ -f /tmp/stop-htools ]; then\n\
370 \ echo 'Stop requested, exiting'\n\