Statistics
| Branch: | Tag: | Revision:

root / htools / Ganeti / HTools / CLI.hs @ e19ee6e4

History | View | Annotate | Download (19.1 kB)

1
{-| Implementation of command-line functions.
2

    
3
This module holds the common command-line related functions for the
4
binaries, separated into this module since "Ganeti.HTools.Utils" is
5
used in many other places and this is more IO oriented.
6

    
7
-}
8

    
9
{-
10

    
11
Copyright (C) 2009, 2010, 2011 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
    , maybePrintNodes
37
    , maybePrintInsts
38
    , maybeShowWarnings
39
    -- * The options
40
    , oDataFile
41
    , oDiskMoves
42
    , oDiskTemplate
43
    , oDynuFile
44
    , oEvacMode
45
    , oExInst
46
    , oExTags
47
    , oExecJobs
48
    , oGroup
49
    , oIDisk
50
    , oIMem
51
    , oIVcpus
52
    , oInstMoves
53
    , oLuxiSocket
54
    , oMachineReadable
55
    , oMaxCpu
56
    , oMaxSolLength
57
    , oMinDisk
58
    , oMinGain
59
    , oMinGainLim
60
    , oMinScore
61
    , oNoHeaders
62
    , oNodeSim
63
    , oOfflineNode
64
    , oOutputDir
65
    , oPrintCommands
66
    , oPrintInsts
67
    , oPrintNodes
68
    , oQuiet
69
    , oRapiMaster
70
    , oReplay
71
    , oSaveCluster
72
    , oSelInst
73
    , oShowHelp
74
    , oShowVer
75
    , oTieredSpec
76
    , oVerbose
77
    ) where
78

    
79
import Control.Monad
80
import Data.Maybe (fromMaybe)
81
import qualified Data.Version
82
import System.Console.GetOpt
83
import System.IO
84
import System.Info
85
import System
86
import Text.Printf (printf)
87

    
88
import qualified Ganeti.HTools.Version as Version(version)
89
import qualified Ganeti.Constants as C
90
import Ganeti.HTools.Types
91
import Ganeti.HTools.Utils
92

    
93
-- * Constants
94

    
95
-- | The default value for the luxi socket.
96
--
97
-- This is re-exported from the "Ganeti.Constants" module.
98
defaultLuxiSocket :: FilePath
99
defaultLuxiSocket = C.masterSocket
100

    
101
-- * Data types
102

    
103
-- | Command line options structure.
104
data Options = Options
105
    { optDataFile    :: Maybe FilePath -- ^ Path to the cluster data file
106
    , optDiskMoves   :: Bool           -- ^ Allow disk moves
107
    , optInstMoves   :: Bool           -- ^ Allow instance moves
108
    , optDiskTemplate :: DiskTemplate  -- ^ The requested disk template
109
    , optDynuFile    :: Maybe FilePath -- ^ Optional file with dynamic use data
110
    , optEvacMode    :: Bool           -- ^ Enable evacuation mode
111
    , optExInst      :: [String]       -- ^ Instances to be excluded
112
    , optExTags      :: Maybe [String] -- ^ Tags to use for exclusion
113
    , optExecJobs    :: Bool           -- ^ Execute the commands via Luxi
114
    , optGroup       :: Maybe GroupID  -- ^ The UUID of the group to process
115
    , optSelInst     :: [String]       -- ^ Instances to be excluded
116
    , optISpec       :: RSpec          -- ^ Requested instance specs
117
    , optLuxi        :: Maybe FilePath -- ^ Collect data from Luxi
118
    , optMachineReadable :: Bool       -- ^ Output machine-readable format
119
    , optMaster      :: String         -- ^ Collect data from RAPI
120
    , optMaxLength   :: Int            -- ^ Stop after this many steps
121
    , optMcpu        :: Double         -- ^ Max cpu ratio for nodes
122
    , optMdsk        :: Double         -- ^ Max disk usage ratio for nodes
123
    , optMinGain     :: Score          -- ^ Min gain we aim for in a step
124
    , optMinGainLim  :: Score          -- ^ Limit below which we apply mingain
125
    , optMinScore    :: Score          -- ^ The minimum score we aim for
126
    , optNoHeaders   :: Bool           -- ^ Do not show a header line
127
    , optNodeSim     :: [String]       -- ^ Cluster simulation mode
128
    , optOffline     :: [String]       -- ^ Names of offline nodes
129
    , optOutPath     :: FilePath       -- ^ Path to the output directory
130
    , optSaveCluster :: Maybe FilePath -- ^ Save cluster state to this file
131
    , optShowCmds    :: Maybe FilePath -- ^ Whether to show the command list
132
    , optShowHelp    :: Bool           -- ^ Just show the help
133
    , optShowInsts   :: Bool           -- ^ Whether to show the instance map
134
    , optShowNodes   :: Maybe [String] -- ^ Whether to show node status
135
    , optShowVer     :: Bool           -- ^ Just show the program version
136
    , optTieredSpec  :: Maybe RSpec    -- ^ Requested specs for tiered mode
137
    , optReplay      :: Maybe String   -- ^ Unittests: RNG state
138
    , optVerbose     :: Int            -- ^ Verbosity level
139
    } deriving Show
140

    
141
-- | Default values for the command line options.
142
defaultOptions :: Options
143
defaultOptions  = Options
144
 { optDataFile    = Nothing
145
 , optDiskMoves   = True
146
 , optInstMoves   = True
147
 , optDiskTemplate = DTDrbd8
148
 , optDynuFile    = Nothing
149
 , optEvacMode    = False
150
 , optExInst      = []
151
 , optExTags      = Nothing
152
 , optExecJobs    = False
153
 , optGroup       = Nothing
154
 , optSelInst     = []
155
 , optISpec       = RSpec 1 4096 102400
156
 , optLuxi        = Nothing
157
 , optMachineReadable = False
158
 , optMaster      = ""
159
 , optMaxLength   = -1
160
 , optMcpu        = defVcpuRatio
161
 , optMdsk        = defReservedDiskRatio
162
 , optMinGain     = 1e-2
163
 , optMinGainLim  = 1e-1
164
 , optMinScore    = 1e-9
165
 , optNoHeaders   = False
166
 , optNodeSim     = []
167
 , optOffline     = []
168
 , optOutPath     = "."
169
 , optSaveCluster = Nothing
170
 , optShowCmds    = Nothing
171
 , optShowHelp    = False
172
 , optShowInsts   = False
173
 , optShowNodes   = Nothing
174
 , optShowVer     = False
175
 , optTieredSpec  = Nothing
176
 , optReplay      = Nothing
177
 , optVerbose     = 1
178
 }
179

    
180
-- | Abrreviation for the option type.
181
type OptType = OptDescr (Options -> Result Options)
182

    
183
-- * Command line options
184

    
185
oDataFile :: OptType
186
oDataFile = Option "t" ["text-data"]
187
            (ReqArg (\ f o -> Ok o { optDataFile = Just f }) "FILE")
188
            "the cluster data FILE"
189

    
190
oDiskMoves :: OptType
191
oDiskMoves = Option "" ["no-disk-moves"]
192
             (NoArg (\ opts -> Ok opts { optDiskMoves = False}))
193
             "disallow disk moves from the list of allowed instance changes,\
194
             \ thus allowing only the 'cheap' failover/migrate operations"
195

    
196
oDiskTemplate :: OptType
197
oDiskTemplate = Option "" ["disk-template"]
198
                (ReqArg (\ t opts -> do
199
                           dt <- diskTemplateFromString t
200
                           return $ opts { optDiskTemplate = dt }) "TEMPLATE")
201
                "select the desired disk template"
202

    
203
oSelInst :: OptType
204
oSelInst = Option "" ["select-instances"]
205
          (ReqArg (\ f opts -> Ok opts { optSelInst = sepSplit ',' f }) "INSTS")
206
          "only select given instances for any moves"
207

    
208
oInstMoves :: OptType
209
oInstMoves = Option "" ["no-instance-moves"]
210
             (NoArg (\ opts -> Ok opts { optInstMoves = False}))
211
             "disallow instance (primary node) moves from the list of allowed,\
212
             \ instance changes, thus allowing only slower, but sometimes\
213
             \ safer, drbd secondary changes"
214

    
215
oDynuFile :: OptType
216
oDynuFile = Option "U" ["dynu-file"]
217
            (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
218
            "Import dynamic utilisation data from the given FILE"
219

    
220
oEvacMode :: OptType
221
oEvacMode = Option "E" ["evac-mode"]
222
            (NoArg (\opts -> Ok opts { optEvacMode = True }))
223
            "enable evacuation mode, where the algorithm only moves \
224
            \ instances away from offline and drained nodes"
225

    
226
oExInst :: OptType
227
oExInst = Option "" ["exclude-instances"]
228
          (ReqArg (\ f opts -> Ok opts { optExInst = sepSplit ',' f }) "INSTS")
229
          "exclude given instances from any moves"
230

    
231
oExTags :: OptType
232
oExTags = Option "" ["exclusion-tags"]
233
            (ReqArg (\ f opts -> Ok opts { optExTags = Just $ sepSplit ',' f })
234
             "TAG,...") "Enable instance exclusion based on given tag prefix"
235

    
236
oExecJobs :: OptType
237
oExecJobs = Option "X" ["exec"]
238
             (NoArg (\ opts -> Ok opts { optExecJobs = True}))
239
             "execute the suggested moves via Luxi (only available when using\
240
             \ it for data gathering)"
241

    
242
oGroup :: OptType
243
oGroup = Option "G" ["group"]
244
            (ReqArg (\ f o -> Ok o { optGroup = Just f }) "ID")
245
            "the ID of the group to balance"
246

    
247
oIDisk :: OptType
248
oIDisk = Option "" ["disk"]
249
         (ReqArg (\ d opts -> do
250
                    dsk <- annotateResult "--disk option" (parseUnit d)
251
                    let ospec = optISpec opts
252
                        nspec = ospec { rspecDsk = dsk }
253
                    return $ opts { optISpec = nspec }) "DISK")
254
         "disk size for instances"
255

    
256
oIMem :: OptType
257
oIMem = Option "" ["memory"]
258
        (ReqArg (\ m opts -> do
259
                   mem <- annotateResult "--memory option" (parseUnit m)
260
                   let ospec = optISpec opts
261
                       nspec = ospec { rspecMem = mem }
262
                   return $ opts { optISpec = nspec }) "MEMORY")
263
        "memory size for instances"
264

    
265
oIVcpus :: OptType
266
oIVcpus = Option "" ["vcpus"]
267
          (ReqArg (\ p opts -> do
268
                     vcpus <- tryRead "--vcpus option" p
269
                     let ospec = optISpec opts
270
                         nspec = ospec { rspecCpu = vcpus }
271
                     return $ opts { optISpec = nspec }) "NUM")
272
          "number of virtual cpus for instances"
273

    
274
oLuxiSocket :: OptType
275
oLuxiSocket = Option "L" ["luxi"]
276
              (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
277
                       fromMaybe defaultLuxiSocket) "SOCKET")
278
              "collect data via Luxi, optionally using the given SOCKET path"
279

    
280
oMachineReadable :: OptType
281
oMachineReadable = Option "" ["machine-readable"]
282
          (OptArg (\ f opts -> do
283
                     flag <- parseYesNo True f
284
                     return $ opts { optMachineReadable = flag }) "CHOICE")
285
          "enable machine readable output (pass either 'yes' or 'no' to\
286
          \ explicitely control the flag, or without an argument defaults to\
287
          \ yes"
288

    
289
oMaxCpu :: OptType
290
oMaxCpu = Option "" ["max-cpu"]
291
          (ReqArg (\ n opts -> Ok opts { optMcpu = read n }) "RATIO")
292
          "maximum virtual-to-physical cpu ratio for nodes (from 1\
293
          \ upwards) [64]"
294

    
295
oMaxSolLength :: OptType
296
oMaxSolLength = Option "l" ["max-length"]
297
                (ReqArg (\ i opts -> Ok opts { optMaxLength = read i }) "N")
298
                "cap the solution at this many balancing or allocation \
299
                \ rounds (useful for very unbalanced clusters or empty \
300
                \ clusters)"
301

    
302
oMinDisk :: OptType
303
oMinDisk = Option "" ["min-disk"]
304
           (ReqArg (\ n opts -> Ok opts { optMdsk = read n }) "RATIO")
305
           "minimum free disk space for nodes (between 0 and 1) [0]"
306

    
307
oMinGain :: OptType
308
oMinGain = Option "g" ["min-gain"]
309
            (ReqArg (\ g opts -> Ok opts { optMinGain = read g }) "DELTA")
310
            "minimum gain to aim for in a balancing step before giving up"
311

    
312
oMinGainLim :: OptType
313
oMinGainLim = Option "" ["min-gain-limit"]
314
            (ReqArg (\ g opts -> Ok opts { optMinGainLim = read g }) "SCORE")
315
            "minimum cluster score for which we start checking the min-gain"
316

    
317
oMinScore :: OptType
318
oMinScore = Option "e" ["min-score"]
319
            (ReqArg (\ e opts -> Ok opts { optMinScore = read e }) "EPSILON")
320
            "mininum score to aim for"
321

    
322
oNoHeaders :: OptType
323
oNoHeaders = Option "" ["no-headers"]
324
             (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
325
             "do not show a header line"
326

    
327
oNodeSim :: OptType
328
oNodeSim = Option "" ["simulate"]
329
            (ReqArg (\ f o -> Ok o { optNodeSim = f:optNodeSim o }) "SPEC")
330
            "simulate an empty cluster, given as 'num_nodes,disk,ram,cpu'"
331

    
332
oOfflineNode :: OptType
333
oOfflineNode = Option "O" ["offline"]
334
               (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
335
               "set node as offline"
336

    
337
oOutputDir :: OptType
338
oOutputDir = Option "d" ["output-dir"]
339
             (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
340
             "directory in which to write output files"
341

    
342
oPrintCommands :: OptType
343
oPrintCommands = Option "C" ["print-commands"]
344
                 (OptArg ((\ f opts -> Ok opts { optShowCmds = Just f }) .
345
                          fromMaybe "-")
346
                  "FILE")
347
                 "print the ganeti command list for reaching the solution,\
348
                 \ if an argument is passed then write the commands to a\
349
                 \ file named as such"
350

    
351
oPrintInsts :: OptType
352
oPrintInsts = Option "" ["print-instances"]
353
              (NoArg (\ opts -> Ok opts { optShowInsts = True }))
354
              "print the final instance map"
355

    
356
oPrintNodes :: OptType
357
oPrintNodes = Option "p" ["print-nodes"]
358
              (OptArg ((\ f opts ->
359
                            let (prefix, realf) = case f of
360
                                  '+':rest -> (["+"], rest)
361
                                  _ -> ([], f)
362
                                splitted = prefix ++ sepSplit ',' realf
363
                            in Ok opts { optShowNodes = Just splitted }) .
364
                       fromMaybe []) "FIELDS")
365
              "print the final node list"
366

    
367
oQuiet :: OptType
368
oQuiet = Option "q" ["quiet"]
369
         (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
370
         "decrease the verbosity level"
371

    
372
oRapiMaster :: OptType
373
oRapiMaster = Option "m" ["master"]
374
              (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
375
              "collect data via RAPI at the given ADDRESS"
376

    
377
oSaveCluster :: OptType
378
oSaveCluster = Option "S" ["save"]
379
            (ReqArg (\ f opts -> Ok opts { optSaveCluster = Just f }) "FILE")
380
            "Save cluster state at the end of the processing to FILE"
381

    
382
oShowHelp :: OptType
383
oShowHelp = Option "h" ["help"]
384
            (NoArg (\ opts -> Ok opts { optShowHelp = True}))
385
            "show help"
386

    
387
oShowVer :: OptType
388
oShowVer = Option "V" ["version"]
389
           (NoArg (\ opts -> Ok opts { optShowVer = True}))
390
           "show the version of the program"
391

    
392
oTieredSpec :: OptType
393
oTieredSpec = Option "" ["tiered-alloc"]
394
             (ReqArg (\ inp opts -> do
395
                          let sp = sepSplit ',' inp
396
                          prs <- mapM (\(fn, val) -> fn val) $
397
                                 zip [ annotateResult "tiered specs memory" .
398
                                       parseUnit
399
                                     , annotateResult "tiered specs disk" .
400
                                       parseUnit
401
                                     , tryRead "tiered specs cpus"
402
                                     ] sp
403
                          tspec <-
404
                              case prs of
405
                                [dsk, ram, cpu] -> return $ RSpec cpu ram dsk
406
                                _ -> Bad $ "Invalid specification: " ++ inp ++
407
                                     ", expected disk,ram,cpu"
408
                          return $ opts { optTieredSpec = Just tspec } )
409
              "TSPEC")
410
             "enable tiered specs allocation, given as 'disk,ram,cpu'"
411

    
412
oReplay :: OptType
413
oReplay = Option "" ["replay"]
414
          (ReqArg (\ stat opts -> Ok opts { optReplay = Just stat } ) "STATE")
415
          "Pre-seed the random number generator with STATE"
416

    
417
oVerbose :: OptType
418
oVerbose = Option "v" ["verbose"]
419
           (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
420
           "increase the verbosity level"
421

    
422
-- * Functions
423

    
424
-- | Helper for parsing a yes\/no command line flag.
425
parseYesNo :: Bool         -- ^ Default whalue (when we get a @Nothing@)
426
           -> Maybe String -- ^ Parameter value
427
           -> Result Bool  -- ^ Resulting boolean value
428
parseYesNo v Nothing      = return v
429
parseYesNo _ (Just "yes") = return True
430
parseYesNo _ (Just "no")  = return False
431
parseYesNo _ (Just s)     = fail $ "Invalid choice '" ++ s ++
432
                            "', pass one of 'yes' or 'no'"
433

    
434
-- | Usage info.
435
usageHelp :: String -> [OptType] -> String
436
usageHelp progname =
437
    usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
438
               progname Version.version progname)
439

    
440
-- | Command line parser, using the 'Options' structure.
441
parseOpts :: [String]               -- ^ The command line arguments
442
          -> String                 -- ^ The program name
443
          -> [OptType]              -- ^ The supported command line options
444
          -> IO (Options, [String]) -- ^ The resulting options and leftover
445
                                    -- arguments
446
parseOpts argv progname options =
447
    case getOpt Permute options argv of
448
      (o, n, []) ->
449
          do
450
            let (pr, args) = (foldM (flip id) defaultOptions o, n)
451
            po <- (case pr of
452
                     Bad msg -> do
453
                       hPutStrLn stderr "Error while parsing command\
454
                                        \line arguments:"
455
                       hPutStrLn stderr msg
456
                       exitWith $ ExitFailure 1
457
                     Ok val -> return val)
458
            when (optShowHelp po) $ do
459
              putStr $ usageHelp progname options
460
              exitWith ExitSuccess
461
            when (optShowVer po) $ do
462
              printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
463
                     progname Version.version
464
                     compilerName (Data.Version.showVersion compilerVersion)
465
                     os arch :: IO ()
466
              exitWith ExitSuccess
467
            return (po, args)
468
      (_, _, errs) -> do
469
        hPutStrLn stderr $ "Command line error: "  ++ concat errs
470
        hPutStrLn stderr $ usageHelp progname options
471
        exitWith $ ExitFailure 2
472

    
473
-- | A shell script template for autogenerated scripts.
474
shTemplate :: String
475
shTemplate =
476
    printf "#!/bin/sh\n\n\
477
           \# Auto-generated script for executing cluster rebalancing\n\n\
478
           \# To stop, touch the file /tmp/stop-htools\n\n\
479
           \set -e\n\n\
480
           \check() {\n\
481
           \  if [ -f /tmp/stop-htools ]; then\n\
482
           \    echo 'Stop requested, exiting'\n\
483
           \    exit 0\n\
484
           \  fi\n\
485
           \}\n\n"
486

    
487
-- | Optionally print the node list.
488
maybePrintNodes :: Maybe [String]       -- ^ The field list
489
                -> String               -- ^ Informational message
490
                -> ([String] -> String) -- ^ Function to generate the listing
491
                -> IO ()
492
maybePrintNodes Nothing _ _ = return ()
493
maybePrintNodes (Just fields) msg fn = do
494
  hPutStrLn stderr ""
495
  hPutStrLn stderr (msg ++ " status:")
496
  hPutStrLn stderr $ fn fields
497

    
498

    
499
-- | Optionally print the instance list.
500
maybePrintInsts :: Bool   -- ^ Whether to print the instance list
501
                -> String -- ^ Type of the instance map (e.g. initial)
502
                -> String -- ^ The instance data
503
                -> IO ()
504
maybePrintInsts do_print msg instdata =
505
  when do_print $ do
506
    hPutStrLn stderr ""
507
    hPutStrLn stderr $ msg ++ " instance map:"
508
    hPutStr stderr instdata
509

    
510
-- | Function to display warning messages from parsing the cluster
511
-- state.
512
maybeShowWarnings :: [String] -- ^ The warning messages
513
                  -> IO ()
514
maybeShowWarnings fix_msgs =
515
  unless (null fix_msgs) $ do
516
    hPutStrLn stderr "Warning: cluster has inconsistent data:"
517
    hPutStrLn stderr . unlines . map (printf "  - %s") $ fix_msgs