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 :: Maybe [String] -- ^ 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 , optDataFile :: Maybe FilePath -- ^ Path to the cluster data file
92 , optNodeSim :: Maybe String -- ^ Cluster simulation mode
93 , optMaxLength :: Int -- ^ Stop after this many steps
94 , optMaster :: String -- ^ Collect data from RAPI
95 , optLuxi :: Maybe FilePath -- ^ Collect data from Luxi
96 , optExecJobs :: Bool -- ^ Execute the commands via Luxi
97 , optOffline :: [String] -- ^ Names of offline nodes
98 , optINodes :: Int -- ^ Nodes required for an instance
99 , optISpec :: RSpec -- ^ Requested instance specs
100 , optTieredSpec :: Maybe RSpec -- ^ Requested specs for tiered mode
101 , optMinScore :: Score -- ^ The minimum score we aim for
102 , optMcpu :: Double -- ^ Max cpu ratio for nodes
103 , optMdsk :: Double -- ^ Max disk usage ratio for nodes
104 , optDiskMoves :: Bool -- ^ Allow disk moves
105 , optDynuFile :: Maybe FilePath -- ^ Optional file with dynamic use data
106 , optExTags :: Maybe [String] -- ^ Tags to use for exclusion
107 , optVerbose :: Int -- ^ Verbosity level
108 , optShowVer :: Bool -- ^ Just show the program version
109 , optShowHelp :: Bool -- ^ Just show the help
112 -- | Default values for the command line options.
113 defaultOptions :: Options
114 defaultOptions = Options
115 { optShowNodes = Nothing
116 , optShowInsts = False
117 , optShowCmds = Nothing
119 , optNoHeaders = False
121 , optDataFile = Nothing
122 , optNodeSim = Nothing
126 , optExecJobs = False
129 , optISpec = RSpec 1 4096 102400
130 , optTieredSpec = Nothing
134 , optDiskMoves = True
135 , optDynuFile = Nothing
136 , optExTags = Nothing
139 , optShowHelp = False
142 -- | Abrreviation for the option type
143 type OptType = OptDescr (Options -> Result Options)
145 oPrintNodes :: OptType
146 oPrintNodes = Option "p" ["print-nodes"]
147 (OptArg ((\ f opts ->
148 let splitted = sepSplit ',' f
149 in Ok opts { optShowNodes = Just splitted }) .
150 fromMaybe []) "FIELDS")
151 "print the final node list"
153 oPrintInsts :: OptType
154 oPrintInsts = Option "" ["print-instances"]
155 (NoArg (\ opts -> Ok opts { optShowInsts = True }))
156 "print the final instance map"
158 oPrintCommands :: OptType
159 oPrintCommands = Option "C" ["print-commands"]
160 (OptArg ((\ f opts -> Ok opts { optShowCmds = Just f }) .
163 "print the ganeti command list for reaching the solution,\
164 \ if an argument is passed then write the commands to a\
165 \ file named as such"
168 oOneline = Option "o" ["oneline"]
169 (NoArg (\ opts -> Ok opts { optOneline = True }))
170 "print the ganeti command list for reaching the solution"
172 oNoHeaders :: OptType
173 oNoHeaders = Option "" ["no-headers"]
174 (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
175 "do not show a header line"
177 oOutputDir :: OptType
178 oOutputDir = Option "d" ["output-dir"]
179 (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
180 "directory in which to write output files"
183 oDataFile = Option "t" ["text-data"]
184 (ReqArg (\ f o -> Ok o { optDataFile = Just f }) "FILE")
185 "the cluster data FILE"
188 oNodeSim = Option "" ["simulate"]
189 (ReqArg (\ f o -> Ok o { optNodeSim = Just f }) "SPEC")
190 "simulate an empty cluster, given as 'num_nodes,disk,ram,cpu'"
192 oRapiMaster :: OptType
193 oRapiMaster = Option "m" ["master"]
194 (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
195 "collect data via RAPI at the given ADDRESS"
197 oLuxiSocket :: OptType
198 oLuxiSocket = Option "L" ["luxi"]
199 (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
200 fromMaybe defaultLuxiSocket) "SOCKET")
201 "collect data via Luxi, optionally using the given SOCKET path"
204 oExecJobs = Option "X" ["exec"]
205 (NoArg (\ opts -> Ok opts { optExecJobs = True}))
206 "execute the suggested moves via Luxi (only available when using\
207 \ it for data gathering"
210 oVerbose = Option "v" ["verbose"]
211 (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
212 "increase the verbosity level"
215 oQuiet = Option "q" ["quiet"]
216 (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
217 "decrease the verbosity level"
219 oOfflineNode :: OptType
220 oOfflineNode = Option "O" ["offline"]
221 (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
222 "set node as offline"
224 oMaxSolLength :: OptType
225 oMaxSolLength = Option "l" ["max-length"]
226 (ReqArg (\ i opts -> Ok opts { optMaxLength = read i }) "N")
227 "cap the solution at this many moves (useful for very\
228 \ unbalanced clusters)"
231 oMinScore = Option "e" ["min-score"]
232 (ReqArg (\ e opts -> Ok opts { optMinScore = read e }) "EPSILON")
233 " mininum score to aim for"
236 oIMem = Option "" ["memory"]
238 let ospec = optISpec opts
239 nspec = ospec { rspecMem = read m }
240 in Ok opts { optISpec = nspec }) "MEMORY")
241 "memory size for instances"
244 oIDisk = Option "" ["disk"]
246 let ospec = optISpec opts
247 nspec = ospec { rspecDsk = read d }
248 in Ok opts { optISpec = nspec }) "DISK")
249 "disk size for instances"
252 oIVcpus = Option "" ["vcpus"]
254 let ospec = optISpec opts
255 nspec = ospec { rspecCpu = read p }
256 in Ok opts { optISpec = nspec }) "NUM")
257 "number of virtual cpus for instances"
260 oINodes = Option "" ["req-nodes"]
261 (ReqArg (\ n opts -> Ok opts { optINodes = read n }) "NODES")
262 "number of nodes for the new instances (1=plain, 2=mirrored)"
265 oMaxCpu = Option "" ["max-cpu"]
266 (ReqArg (\ n opts -> Ok opts { optMcpu = read n }) "RATIO")
267 "maximum virtual-to-physical cpu ratio for nodes"
270 oMinDisk = Option "" ["min-disk"]
271 (ReqArg (\ n opts -> Ok opts { optMdsk = read n }) "RATIO")
272 "minimum free disk space for nodes (between 0 and 1)"
274 oDiskMoves :: OptType
275 oDiskMoves = Option "" ["no-disk-moves"]
276 (NoArg (\ opts -> Ok opts { optDiskMoves = False}))
277 "disallow disk moves from the list of allowed instance changes,\
278 \ thus allowing only the 'cheap' failover/migrate operations"
281 oDynuFile = Option "U" ["dynu-file"]
282 (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
283 "Import dynamic utilisation data from the given FILE"
286 oExTags = Option "" ["exclusion-tags"]
287 (ReqArg (\ f opts -> Ok opts { optExTags = Just $ sepSplit ',' f })
288 "TAG,...") "Enable instance exclusion based on given tag prefix"
290 oTieredSpec :: OptType
291 oTieredSpec = Option "" ["tiered-alloc"]
292 (ReqArg (\ inp opts -> do
293 let sp = sepSplit ',' inp
294 prs <- mapM (tryRead "tiered specs") sp
297 [dsk, ram, cpu] -> return $ RSpec cpu ram dsk
298 _ -> Bad $ "Invalid specification: " ++ inp
299 return $ opts { optTieredSpec = Just tspec } )
301 "enable tiered specs allocation, given as 'disk,ram,cpu'"
304 oShowVer = Option "V" ["version"]
305 (NoArg (\ opts -> Ok opts { optShowVer = True}))
306 "show the version of the program"
309 oShowHelp = Option "h" ["help"]
310 (NoArg (\ opts -> Ok opts { optShowHelp = True}))
314 usageHelp :: String -> [OptType] -> String
316 usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
317 progname Version.version progname)
319 -- | Command line parser, using the 'options' structure.
320 parseOpts :: [String] -- ^ The command line arguments
321 -> String -- ^ The program name
322 -> [OptType] -- ^ The supported command line options
323 -> IO (Options, [String]) -- ^ The resulting options and leftover
325 parseOpts argv progname options =
326 case getOpt Permute options argv of
329 let (pr, args) = (foldM (flip id) defaultOptions o, n)
332 hPutStrLn stderr "Error while parsing command\
335 exitWith $ ExitFailure 1
336 Ok val -> return val)
337 when (optShowHelp po) $ do
338 putStr $ usageHelp progname options
340 when (optShowVer po) $ do
341 printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
342 progname Version.version
343 compilerName (Data.Version.showVersion compilerVersion)
348 hPutStrLn stderr $ "Command line error: " ++ concat errs
349 hPutStrLn stderr $ usageHelp progname options
350 exitWith $ ExitFailure 2
352 -- | A shell script template for autogenerated scripts.
355 printf "#!/bin/sh\n\n\
356 \# Auto-generated script for executing cluster rebalancing\n\n\
357 \# To stop, touch the file /tmp/stop-htools\n\n\
360 \ if [ -f /tmp/stop-htools ]; then\n\
361 \ echo 'Stop requested, exiting'\n\