IAlloc: read group uuid from the input message
[ganeti-local] / Ganeti / HTools / CLI.hs
1 {-| Implementation of command-line functions.
2
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.
6
7 -}
8
9 {-
10
11 Copyright (C) 2009, 2010 Google Inc.
12
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.
17
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.
22
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
26 02110-1301, USA.
27
28 -}
29
30 module Ganeti.HTools.CLI
31     ( Options(..)
32     , OptType
33     , parseOpts
34     , shTemplate
35     , defaultLuxiSocket
36     -- * The options
37     , oDataFile
38     , oDiskMoves
39     , oDynuFile
40     , oEvacMode
41     , oExInst
42     , oExTags
43     , oExecJobs
44     , oIDisk
45     , oIMem
46     , oINodes
47     , oIVcpus
48     , oLuxiSocket
49     , oMaxCpu
50     , oMaxSolLength
51     , oMinDisk
52     , oMinGain
53     , oMinGainLim
54     , oMinScore
55     , oNoHeaders
56     , oNodeSim
57     , oOfflineNode
58     , oOneline
59     , oOutputDir
60     , oPrintCommands
61     , oPrintInsts
62     , oPrintNodes
63     , oQuiet
64     , oRapiMaster
65     , oSaveCluster
66     , oShowHelp
67     , oShowVer
68     , oTieredSpec
69     , oVerbose
70     ) where
71
72 import Data.Maybe (fromMaybe)
73 import qualified Data.Version
74 import Monad
75 import System.Console.GetOpt
76 import System.IO
77 import System.Info
78 import System
79 import Text.Printf (printf)
80
81 import qualified Ganeti.HTools.Version as Version(version)
82 import Ganeti.HTools.Types
83 import Ganeti.HTools.Utils
84
85 -- | The default value for the luxi socket
86 defaultLuxiSocket :: FilePath
87 defaultLuxiSocket = "/var/run/ganeti/socket/ganeti-master"
88
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
121     } deriving Show
122
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
130  , optExInst      = []
131  , optExTags      = Nothing
132  , optExecJobs    = False
133  , optINodes      = 2
134  , optISpec       = RSpec 1 4096 102400
135  , optLuxi        = Nothing
136  , optMaster      = ""
137  , optMaxLength   = -1
138  , optMcpu        = defVcpuRatio
139  , optMdsk        = defReservedDiskRatio
140  , optMinGain     = 1e-2
141  , optMinGainLim  = 1e-1
142  , optMinScore    = 1e-9
143  , optNoHeaders   = False
144  , optNodeSim     = Nothing
145  , optOffline     = []
146  , optOneline     = False
147  , optOutPath     = "."
148  , optSaveCluster = Nothing
149  , optShowCmds    = Nothing
150  , optShowHelp    = False
151  , optShowInsts   = False
152  , optShowNodes   = Nothing
153  , optShowVer     = False
154  , optTieredSpec  = Nothing
155  , optVerbose     = 1
156  }
157
158 -- | Abrreviation for the option type
159 type OptType = OptDescr (Options -> Result Options)
160
161 oDataFile :: OptType
162 oDataFile = Option "t" ["text-data"]
163             (ReqArg (\ f o -> Ok o { optDataFile = Just f }) "FILE")
164             "the cluster data FILE"
165
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"
171
172 oDynuFile :: OptType
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"
176
177 oEvacMode :: OptType
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"
182
183 oExInst :: OptType
184 oExInst = Option "" ["exclude-instances"]
185           (ReqArg (\ f opts -> Ok opts { optExInst = sepSplit ',' f }) "INSTS")
186           "exclude given instances  from any moves"
187
188 oExTags :: OptType
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"
192
193 oExecJobs :: OptType
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)"
198
199 oIDisk :: OptType
200 oIDisk = Option "" ["disk"]
201          (ReqArg (\ d opts ->
202                      let ospec = optISpec opts
203                          nspec = ospec { rspecDsk = read d }
204                      in Ok opts { optISpec = nspec }) "DISK")
205          "disk size for instances"
206
207 oIMem :: OptType
208 oIMem = Option "" ["memory"]
209         (ReqArg (\ m opts ->
210                      let ospec = optISpec opts
211                          nspec = ospec { rspecMem = read m }
212                      in Ok opts { optISpec = nspec }) "MEMORY")
213         "memory size for instances"
214
215 oINodes :: OptType
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)"
219
220 oIVcpus :: OptType
221 oIVcpus = Option "" ["vcpus"]
222           (ReqArg (\ p opts ->
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"
227
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"
233
234 oMaxCpu :: OptType
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\
238           \ upwards) [64]"
239
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)"
245
246 oMinDisk :: OptType
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]"
250
251 oMinGain :: OptType
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"
255
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"
260
261 oMinScore :: OptType
262 oMinScore = Option "e" ["min-score"]
263             (ReqArg (\ e opts -> Ok opts { optMinScore = read e }) "EPSILON")
264             "mininum score to aim for"
265
266 oNoHeaders :: OptType
267 oNoHeaders = Option "" ["no-headers"]
268              (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
269              "do not show a header line"
270
271 oNodeSim :: OptType
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'"
275
276 oOfflineNode :: OptType
277 oOfflineNode = Option "O" ["offline"]
278                (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
279                "set node as offline"
280
281 oOneline :: OptType
282 oOneline = Option "o" ["oneline"]
283            (NoArg (\ opts -> Ok opts { optOneline = True }))
284            "print the ganeti command list for reaching the solution"
285
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"
290
291 oPrintCommands :: OptType
292 oPrintCommands = Option "C" ["print-commands"]
293                  (OptArg ((\ f opts -> Ok opts { optShowCmds = Just f }) .
294                           fromMaybe "-")
295                   "FILE")
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"
299
300 oPrintInsts :: OptType
301 oPrintInsts = Option "" ["print-instances"]
302               (NoArg (\ opts -> Ok opts { optShowInsts = True }))
303               "print the final instance map"
304
305 oPrintNodes :: OptType
306 oPrintNodes = Option "p" ["print-nodes"]
307               (OptArg ((\ f opts ->
308                             let (prefix, realf) = case f of
309                                   '+':rest -> (["+"], rest)
310                                   _ -> ([], f)
311                                 splitted = prefix ++ sepSplit ',' realf
312                             in Ok opts { optShowNodes = Just splitted }) .
313                        fromMaybe []) "FIELDS")
314               "print the final node list"
315
316 oQuiet :: OptType
317 oQuiet = Option "q" ["quiet"]
318          (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
319          "decrease the verbosity level"
320
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"
325
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"
330
331 oShowHelp :: OptType
332 oShowHelp = Option "h" ["help"]
333             (NoArg (\ opts -> Ok opts { optShowHelp = True}))
334             "show help"
335
336 oShowVer :: OptType
337 oShowVer = Option "V" ["version"]
338            (NoArg (\ opts -> Ok opts { optShowVer = True}))
339            "show the version of the program"
340
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
346                           tspec <-
347                               case prs of
348                                 [dsk, ram, cpu] -> return $ RSpec cpu ram dsk
349                                 _ -> Bad $ "Invalid specification: " ++ inp ++
350                                      ", expected disk,ram,cpu"
351                           return $ opts { optTieredSpec = Just tspec } )
352               "TSPEC")
353              "enable tiered specs allocation, given as 'disk,ram,cpu'"
354
355 oVerbose :: OptType
356 oVerbose = Option "v" ["verbose"]
357            (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
358            "increase the verbosity level"
359
360 -- | Usage info
361 usageHelp :: String -> [OptType] -> String
362 usageHelp progname =
363     usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
364                progname Version.version progname)
365
366 -- | Command line parser, using the 'options' structure.
367 parseOpts :: [String]               -- ^ The command line arguments
368           -> String                 -- ^ The program name
369           -> [OptType]              -- ^ The supported command line options
370           -> IO (Options, [String]) -- ^ The resulting options and leftover
371                                     -- arguments
372 parseOpts argv progname options =
373     case getOpt Permute options argv of
374       (o, n, []) ->
375           do
376             let (pr, args) = (foldM (flip id) defaultOptions o, n)
377             po <- (case pr of
378                      Bad msg -> do
379                        hPutStrLn stderr "Error while parsing command\
380                                         \line arguments:"
381                        hPutStrLn stderr msg
382                        exitWith $ ExitFailure 1
383                      Ok val -> return val)
384             when (optShowHelp po) $ do
385               putStr $ usageHelp progname options
386               exitWith ExitSuccess
387             when (optShowVer po) $ do
388               printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
389                      progname Version.version
390                      compilerName (Data.Version.showVersion compilerVersion)
391                      os arch :: IO ()
392               exitWith ExitSuccess
393             return (po, args)
394       (_, _, errs) -> do
395         hPutStrLn stderr $ "Command line error: "  ++ concat errs
396         hPutStrLn stderr $ usageHelp progname options
397         exitWith $ ExitFailure 2
398
399 -- | A shell script template for autogenerated scripts.
400 shTemplate :: String
401 shTemplate =
402     printf "#!/bin/sh\n\n\
403            \# Auto-generated script for executing cluster rebalancing\n\n\
404            \# To stop, touch the file /tmp/stop-htools\n\n\
405            \set -e\n\n\
406            \check() {\n\
407            \  if [ -f /tmp/stop-htools ]; then\n\
408            \    echo 'Stop requested, exiting'\n\
409            \    exit 0\n\
410            \  fi\n\
411            \}\n\n"