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
66 import Data.Maybe (fromMaybe)
67 import qualified Data.Version
69 import System.Console.GetOpt
73 import Text.Printf (printf)
75 import qualified Ganeti.HTools.Version as Version(version)
76 import Ganeti.HTools.Types
77 import Ganeti.HTools.Utils
79 -- | The default value for the luxi socket
80 defaultLuxiSocket :: FilePath
81 defaultLuxiSocket = "/var/run/ganeti/socket/ganeti-master"
83 -- | Command line options structure.
84 data Options = Options
85 { optShowNodes :: Bool -- ^ Whether to show node status
86 , optShowInsts :: Bool -- ^ Whether to show the instance map
87 , optShowCmds :: Maybe FilePath -- ^ Whether to show the command list
88 , optOneline :: Bool -- ^ Switch output to a single line
89 , optOutPath :: FilePath -- ^ Path to the output directory
90 , optNoHeaders :: Bool -- ^ Do not show a header line
91 , optNodeFile :: FilePath -- ^ Path to the nodes file
92 , optNodeSet :: Bool -- ^ The nodes have been set by options
93 , optInstFile :: FilePath -- ^ Path to the instances file
94 , optInstSet :: Bool -- ^ The insts have been set by options
95 , optNodeSim :: Maybe String -- ^ Cluster simulation mode
96 , optMaxLength :: Int -- ^ Stop after this many steps
97 , optMaster :: String -- ^ Collect data from RAPI
98 , optLuxi :: Maybe FilePath -- ^ Collect data from Luxi
99 , optExecJobs :: Bool -- ^ Execute the commands via Luxi
100 , optOffline :: [String] -- ^ Names of offline nodes
101 , optINodes :: Int -- ^ Nodes required for an instance
102 , optISpec :: RSpec -- ^ Requested instance specs
103 , optTieredSpec :: Maybe RSpec -- ^ Requested specs for tiered mode
104 , optMinScore :: Score -- ^ The minimum score we aim for
105 , optMcpu :: Double -- ^ Max cpu ratio for nodes
106 , optMdsk :: Double -- ^ Max disk usage ratio for nodes
107 , optDiskMoves :: Bool -- ^ Allow disk moves
108 , optDynuFile :: Maybe FilePath -- ^ Optional file with dynamic use data
109 , optVerbose :: Int -- ^ Verbosity level
110 , optShowVer :: Bool -- ^ Just show the program version
111 , optShowHelp :: Bool -- ^ Just show the help
114 -- | Default values for the command line options.
115 defaultOptions :: Options
116 defaultOptions = Options
117 { optShowNodes = False
118 , optShowInsts = False
119 , optShowCmds = Nothing
121 , optNoHeaders = False
123 , optNodeFile = "nodes"
125 , optInstFile = "instances"
127 , optNodeSim = Nothing
131 , optExecJobs = False
134 , optISpec = RSpec 1 4096 102400
135 , optTieredSpec = Nothing
139 , optDiskMoves = True
140 , optDynuFile = Nothing
143 , optShowHelp = False
146 -- | Abrreviation for the option type
147 type OptType = OptDescr (Options -> Result Options)
149 oPrintNodes :: OptType
150 oPrintNodes = Option "p" ["print-nodes"]
151 (NoArg (\ opts -> Ok opts { optShowNodes = True }))
152 "print the final node list"
154 oPrintInsts :: OptType
155 oPrintInsts = Option "" ["print-instances"]
156 (NoArg (\ opts -> Ok opts { optShowInsts = True }))
157 "print the final instance map"
159 oPrintCommands :: OptType
160 oPrintCommands = Option "C" ["print-commands"]
161 (OptArg ((\ f opts -> Ok opts { optShowCmds = Just f }) .
164 "print the ganeti command list for reaching the solution,\
165 \ if an argument is passed then write the commands to a\
166 \ file named as such"
169 oOneline = Option "o" ["oneline"]
170 (NoArg (\ opts -> Ok opts { optOneline = True }))
171 "print the ganeti command list for reaching the solution"
173 oNoHeaders :: OptType
174 oNoHeaders = Option "" ["no-headers"]
175 (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
176 "do not show a header line"
178 oOutputDir :: OptType
179 oOutputDir = Option "d" ["output-dir"]
180 (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
181 "directory in which to write output files"
184 oNodeFile = Option "n" ["nodes"]
185 (ReqArg (\ f o -> Ok o { optNodeFile = f,
186 optNodeSet = True }) "FILE")
190 oInstFile = Option "i" ["instances"]
191 (ReqArg (\ f o -> Ok o { optInstFile = f,
192 optInstSet = True }) "FILE")
193 "the instance list FILE"
196 oNodeSim = Option "" ["simulate"]
197 (ReqArg (\ f o -> Ok o { optNodeSim = Just f }) "SPEC")
198 "simulate an empty cluster, given as 'num_nodes,disk,ram,cpu'"
200 oRapiMaster :: OptType
201 oRapiMaster = Option "m" ["master"]
202 (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
203 "collect data via RAPI at the given ADDRESS"
205 oLuxiSocket :: OptType
206 oLuxiSocket = Option "L" ["luxi"]
207 (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
208 fromMaybe defaultLuxiSocket) "SOCKET")
209 "collect data via Luxi, optionally using the given SOCKET path"
212 oExecJobs = Option "X" ["exec"]
213 (NoArg (\ opts -> Ok opts { optExecJobs = True}))
214 "execute the suggested moves via Luxi (only available when using\
215 \ it for data gathering"
218 oVerbose = Option "v" ["verbose"]
219 (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
220 "increase the verbosity level"
223 oQuiet = Option "q" ["quiet"]
224 (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
225 "decrease the verbosity level"
227 oOfflineNode :: OptType
228 oOfflineNode = Option "O" ["offline"]
229 (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
230 "set node as offline"
232 oMaxSolLength :: OptType
233 oMaxSolLength = Option "l" ["max-length"]
234 (ReqArg (\ i opts -> Ok opts { optMaxLength = read i }) "N")
235 "cap the solution at this many moves (useful for very\
236 \ unbalanced clusters)"
239 oMinScore = Option "e" ["min-score"]
240 (ReqArg (\ e opts -> Ok opts { optMinScore = read e }) "EPSILON")
241 " mininum score to aim for"
244 oIMem = Option "" ["memory"]
246 let ospec = optISpec opts
247 nspec = ospec { rspecMem = read m }
248 in Ok opts { optISpec = nspec }) "MEMORY")
249 "memory size for instances"
252 oIDisk = Option "" ["disk"]
254 let ospec = optISpec opts
255 nspec = ospec { rspecDsk = read d }
256 in Ok opts { optISpec = nspec }) "DISK")
257 "disk size for instances"
260 oIVcpus = Option "" ["vcpus"]
262 let ospec = optISpec opts
263 nspec = ospec { rspecCpu = read p }
264 in Ok opts { optISpec = nspec }) "NUM")
265 "number of virtual cpus for instances"
268 oINodes = Option "" ["req-nodes"]
269 (ReqArg (\ n opts -> Ok opts { optINodes = read n }) "NODES")
270 "number of nodes for the new instances (1=plain, 2=mirrored)"
273 oMaxCpu = Option "" ["max-cpu"]
274 (ReqArg (\ n opts -> Ok opts { optMcpu = read n }) "RATIO")
275 "maximum virtual-to-physical cpu ratio for nodes"
278 oMinDisk = Option "" ["min-disk"]
279 (ReqArg (\ n opts -> Ok opts { optMdsk = read n }) "RATIO")
280 "minimum free disk space for nodes (between 0 and 1)"
282 oDiskMoves :: OptType
283 oDiskMoves = Option "" ["no-disk-moves"]
284 (NoArg (\ opts -> Ok opts { optDiskMoves = False}))
285 "disallow disk moves from the list of allowed instance changes,\
286 \ thus allowing only the 'cheap' failover/migrate operations"
289 oDynuFile = Option "U" ["dynu-file"]
290 (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
291 "Import dynamic utilisation data from the given FILE"
293 oTieredSpec :: OptType
294 oTieredSpec = Option "" ["tiered-alloc"]
295 (ReqArg (\ inp opts -> do
296 let sp = sepSplit ',' inp
297 prs <- mapM (tryRead "tiered specs") sp
300 [dsk, ram, cpu] -> return $ RSpec cpu ram dsk
301 _ -> Bad $ "Invalid specification: " ++ inp
302 return $ opts { optTieredSpec = Just tspec } )
304 "enable tiered specs allocation, given as 'disk,ram,cpu'"
307 oShowVer = Option "V" ["version"]
308 (NoArg (\ opts -> Ok opts { optShowVer = True}))
309 "show the version of the program"
312 oShowHelp = Option "h" ["help"]
313 (NoArg (\ opts -> Ok opts { optShowHelp = True}))
317 usageHelp :: String -> [OptType] -> String
319 usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
320 progname Version.version progname)
322 -- | Command line parser, using the 'options' structure.
323 parseOpts :: [String] -- ^ The command line arguments
324 -> String -- ^ The program name
325 -> [OptType] -- ^ The supported command line options
326 -> IO (Options, [String]) -- ^ The resulting options and leftover
328 parseOpts argv progname options =
329 case getOpt Permute options argv of
332 let (pr, args) = (foldM (flip id) defaultOptions o, n)
335 hPutStrLn stderr "Error while parsing command\
338 exitWith $ ExitFailure 1
339 Ok val -> return val)
340 when (optShowHelp po) $ do
341 putStr $ usageHelp progname options
343 when (optShowVer po) $ do
344 printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
345 progname Version.version
346 compilerName (Data.Version.showVersion compilerVersion)
351 hPutStrLn stderr $ "Command line error: " ++ concat errs
352 hPutStrLn stderr $ usageHelp progname options
353 exitWith $ ExitFailure 2
355 -- | A shell script template for autogenerated scripts.
358 printf "#!/bin/sh\n\n\
359 \# Auto-generated script for executing cluster rebalancing\n\n\
360 \# To stop, touch the file /tmp/stop-htools\n\n\
363 \ if [ -f /tmp/stop-htools ]; then\n\
364 \ echo 'Stop requested, exiting'\n\