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 { optShowNodes :: Maybe [String] -- ^ Whether to show node status
87 , optShowInsts :: Bool -- ^ Whether to show the instance map
88 , optShowCmds :: Maybe FilePath -- ^ Whether to show the command list
89 , optOneline :: Bool -- ^ Switch output to a single line
90 , optOutPath :: FilePath -- ^ Path to the output directory
91 , optNoHeaders :: Bool -- ^ Do not show a header line
92 , optNodeFile :: FilePath -- ^ Path to the nodes file
93 , optNodeSet :: Bool -- ^ The nodes have been set by options
94 , optInstFile :: FilePath -- ^ Path to the instances file
95 , optInstSet :: Bool -- ^ The insts have been set by options
96 , optNodeSim :: Maybe String -- ^ Cluster simulation mode
97 , optMaxLength :: Int -- ^ Stop after this many steps
98 , optMaster :: String -- ^ Collect data from RAPI
99 , optLuxi :: Maybe FilePath -- ^ Collect data from Luxi
100 , optExecJobs :: Bool -- ^ Execute the commands via Luxi
101 , optOffline :: [String] -- ^ Names of offline nodes
102 , optINodes :: Int -- ^ Nodes required for an instance
103 , optISpec :: RSpec -- ^ Requested instance specs
104 , optTieredSpec :: Maybe RSpec -- ^ Requested specs for tiered mode
105 , optMinScore :: Score -- ^ The minimum score we aim for
106 , optMcpu :: Double -- ^ Max cpu ratio for nodes
107 , optMdsk :: Double -- ^ Max disk usage ratio for nodes
108 , optDiskMoves :: Bool -- ^ Allow disk moves
109 , optDynuFile :: Maybe FilePath -- ^ Optional file with dynamic use data
110 , optExTags :: Maybe [String] -- ^ Tags to use for exclusion
111 , optVerbose :: Int -- ^ Verbosity level
112 , optShowVer :: Bool -- ^ Just show the program version
113 , optShowHelp :: Bool -- ^ Just show the help
116 -- | Default values for the command line options.
117 defaultOptions :: Options
118 defaultOptions = Options
119 { optShowNodes = Nothing
120 , optShowInsts = False
121 , optShowCmds = Nothing
123 , optNoHeaders = False
125 , optNodeFile = "nodes"
127 , optInstFile = "instances"
129 , optNodeSim = Nothing
133 , optExecJobs = False
136 , optISpec = RSpec 1 4096 102400
137 , optTieredSpec = Nothing
141 , optDiskMoves = True
142 , optDynuFile = Nothing
143 , optExTags = Nothing
146 , optShowHelp = False
149 -- | Abrreviation for the option type
150 type OptType = OptDescr (Options -> Result Options)
152 oPrintNodes :: OptType
153 oPrintNodes = Option "p" ["print-nodes"]
154 (OptArg ((\ f opts ->
155 let splitted = sepSplit ',' f
156 in Ok opts { optShowNodes = Just splitted }) .
157 fromMaybe []) "FIELDS")
158 "print the final node list"
160 oPrintInsts :: OptType
161 oPrintInsts = Option "" ["print-instances"]
162 (NoArg (\ opts -> Ok opts { optShowInsts = True }))
163 "print the final instance map"
165 oPrintCommands :: OptType
166 oPrintCommands = Option "C" ["print-commands"]
167 (OptArg ((\ f opts -> Ok opts { optShowCmds = Just f }) .
170 "print the ganeti command list for reaching the solution,\
171 \ if an argument is passed then write the commands to a\
172 \ file named as such"
175 oOneline = Option "o" ["oneline"]
176 (NoArg (\ opts -> Ok opts { optOneline = True }))
177 "print the ganeti command list for reaching the solution"
179 oNoHeaders :: OptType
180 oNoHeaders = Option "" ["no-headers"]
181 (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
182 "do not show a header line"
184 oOutputDir :: OptType
185 oOutputDir = Option "d" ["output-dir"]
186 (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
187 "directory in which to write output files"
190 oNodeFile = Option "n" ["nodes"]
191 (ReqArg (\ f o -> Ok o { optNodeFile = f,
192 optNodeSet = True }) "FILE")
196 oInstFile = Option "i" ["instances"]
197 (ReqArg (\ f o -> Ok o { optInstFile = f,
198 optInstSet = True }) "FILE")
199 "the instance list FILE"
202 oNodeSim = Option "" ["simulate"]
203 (ReqArg (\ f o -> Ok o { optNodeSim = Just f }) "SPEC")
204 "simulate an empty cluster, given as 'num_nodes,disk,ram,cpu'"
206 oRapiMaster :: OptType
207 oRapiMaster = Option "m" ["master"]
208 (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
209 "collect data via RAPI at the given ADDRESS"
211 oLuxiSocket :: OptType
212 oLuxiSocket = Option "L" ["luxi"]
213 (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
214 fromMaybe defaultLuxiSocket) "SOCKET")
215 "collect data via Luxi, optionally using the given SOCKET path"
218 oExecJobs = Option "X" ["exec"]
219 (NoArg (\ opts -> Ok opts { optExecJobs = True}))
220 "execute the suggested moves via Luxi (only available when using\
221 \ it for data gathering"
224 oVerbose = Option "v" ["verbose"]
225 (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
226 "increase the verbosity level"
229 oQuiet = Option "q" ["quiet"]
230 (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
231 "decrease the verbosity level"
233 oOfflineNode :: OptType
234 oOfflineNode = Option "O" ["offline"]
235 (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
236 "set node as offline"
238 oMaxSolLength :: OptType
239 oMaxSolLength = Option "l" ["max-length"]
240 (ReqArg (\ i opts -> Ok opts { optMaxLength = read i }) "N")
241 "cap the solution at this many moves (useful for very\
242 \ unbalanced clusters)"
245 oMinScore = Option "e" ["min-score"]
246 (ReqArg (\ e opts -> Ok opts { optMinScore = read e }) "EPSILON")
247 " mininum score to aim for"
250 oIMem = Option "" ["memory"]
252 let ospec = optISpec opts
253 nspec = ospec { rspecMem = read m }
254 in Ok opts { optISpec = nspec }) "MEMORY")
255 "memory size for instances"
258 oIDisk = Option "" ["disk"]
260 let ospec = optISpec opts
261 nspec = ospec { rspecDsk = read d }
262 in Ok opts { optISpec = nspec }) "DISK")
263 "disk size for instances"
266 oIVcpus = Option "" ["vcpus"]
268 let ospec = optISpec opts
269 nspec = ospec { rspecCpu = read p }
270 in Ok opts { optISpec = nspec }) "NUM")
271 "number of virtual cpus for instances"
274 oINodes = Option "" ["req-nodes"]
275 (ReqArg (\ n opts -> Ok opts { optINodes = read n }) "NODES")
276 "number of nodes for the new instances (1=plain, 2=mirrored)"
279 oMaxCpu = Option "" ["max-cpu"]
280 (ReqArg (\ n opts -> Ok opts { optMcpu = read n }) "RATIO")
281 "maximum virtual-to-physical cpu ratio for nodes"
284 oMinDisk = Option "" ["min-disk"]
285 (ReqArg (\ n opts -> Ok opts { optMdsk = read n }) "RATIO")
286 "minimum free disk space for nodes (between 0 and 1)"
288 oDiskMoves :: OptType
289 oDiskMoves = Option "" ["no-disk-moves"]
290 (NoArg (\ opts -> Ok opts { optDiskMoves = False}))
291 "disallow disk moves from the list of allowed instance changes,\
292 \ thus allowing only the 'cheap' failover/migrate operations"
295 oDynuFile = Option "U" ["dynu-file"]
296 (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
297 "Import dynamic utilisation data from the given FILE"
300 oExTags = Option "" ["exclusion-tags"]
301 (ReqArg (\ f opts -> Ok opts { optExTags = Just $ sepSplit ',' f })
302 "TAG,...") "Enable instance exclusion based on given tag prefix"
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 oShowVer = Option "V" ["version"]
319 (NoArg (\ opts -> Ok opts { optShowVer = True}))
320 "show the version of the program"
323 oShowHelp = Option "h" ["help"]
324 (NoArg (\ opts -> Ok opts { optShowHelp = True}))
328 usageHelp :: String -> [OptType] -> String
330 usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
331 progname Version.version progname)
333 -- | Command line parser, using the 'options' structure.
334 parseOpts :: [String] -- ^ The command line arguments
335 -> String -- ^ The program name
336 -> [OptType] -- ^ The supported command line options
337 -> IO (Options, [String]) -- ^ The resulting options and leftover
339 parseOpts argv progname options =
340 case getOpt Permute options argv of
343 let (pr, args) = (foldM (flip id) defaultOptions o, n)
346 hPutStrLn stderr "Error while parsing command\
349 exitWith $ ExitFailure 1
350 Ok val -> return val)
351 when (optShowHelp po) $ do
352 putStr $ usageHelp progname options
354 when (optShowVer po) $ do
355 printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
356 progname Version.version
357 compilerName (Data.Version.showVersion compilerVersion)
362 hPutStrLn stderr $ "Command line error: " ++ concat errs
363 hPutStrLn stderr $ usageHelp progname options
364 exitWith $ ExitFailure 2
366 -- | A shell script template for autogenerated scripts.
369 printf "#!/bin/sh\n\n\
370 \# Auto-generated script for executing cluster rebalancing\n\n\
371 \# To stop, touch the file /tmp/stop-htools\n\n\
374 \ if [ -f /tmp/stop-htools ]; then\n\
375 \ echo 'Stop requested, exiting'\n\