Statistics
| Branch: | Tag: | Revision:

root / htools / Ganeti / HTools / CLI.hs @ 2c9336a4

History | View | Annotate | Download (19.4 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
    , oOneline
65
    , oOutputDir
66
    , oPrintCommands
67
    , oPrintInsts
68
    , oPrintNodes
69
    , oQuiet
70
    , oRapiMaster
71
    , oReplay
72
    , oSaveCluster
73
    , oSelInst
74
    , oShowHelp
75
    , oShowVer
76
    , oTieredSpec
77
    , oVerbose
78
    ) where
79

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

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

    
94
-- * Constants
95

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

    
102
-- * Data types
103

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

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

    
183
-- | Abrreviation for the option type.
184
type OptType = OptDescr (Options -> Result Options)
185

    
186
-- * Command line options
187

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
340
oOneline :: OptType
341
oOneline = Option "o" ["oneline"]
342
           (NoArg (\ opts -> Ok opts { optOneline = True }))
343
           "print the ganeti command list for reaching the solution"
344

    
345
oOutputDir :: OptType
346
oOutputDir = Option "d" ["output-dir"]
347
             (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
348
             "directory in which to write output files"
349

    
350
oPrintCommands :: OptType
351
oPrintCommands = Option "C" ["print-commands"]
352
                 (OptArg ((\ f opts -> Ok opts { optShowCmds = Just f }) .
353
                          fromMaybe "-")
354
                  "FILE")
355
                 "print the ganeti command list for reaching the solution,\
356
                 \ if an argument is passed then write the commands to a\
357
                 \ file named as such"
358

    
359
oPrintInsts :: OptType
360
oPrintInsts = Option "" ["print-instances"]
361
              (NoArg (\ opts -> Ok opts { optShowInsts = True }))
362
              "print the final instance map"
363

    
364
oPrintNodes :: OptType
365
oPrintNodes = Option "p" ["print-nodes"]
366
              (OptArg ((\ f opts ->
367
                            let (prefix, realf) = case f of
368
                                  '+':rest -> (["+"], rest)
369
                                  _ -> ([], f)
370
                                splitted = prefix ++ sepSplit ',' realf
371
                            in Ok opts { optShowNodes = Just splitted }) .
372
                       fromMaybe []) "FIELDS")
373
              "print the final node list"
374

    
375
oQuiet :: OptType
376
oQuiet = Option "q" ["quiet"]
377
         (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
378
         "decrease the verbosity level"
379

    
380
oRapiMaster :: OptType
381
oRapiMaster = Option "m" ["master"]
382
              (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
383
              "collect data via RAPI at the given ADDRESS"
384

    
385
oSaveCluster :: OptType
386
oSaveCluster = Option "S" ["save"]
387
            (ReqArg (\ f opts -> Ok opts { optSaveCluster = Just f }) "FILE")
388
            "Save cluster state at the end of the processing to FILE"
389

    
390
oShowHelp :: OptType
391
oShowHelp = Option "h" ["help"]
392
            (NoArg (\ opts -> Ok opts { optShowHelp = True}))
393
            "show help"
394

    
395
oShowVer :: OptType
396
oShowVer = Option "V" ["version"]
397
           (NoArg (\ opts -> Ok opts { optShowVer = True}))
398
           "show the version of the program"
399

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

    
420
oReplay :: OptType
421
oReplay = Option "" ["replay"]
422
          (ReqArg (\ stat opts -> Ok opts { optReplay = Just stat } ) "STATE")
423
          "Pre-seed the random number generator with STATE"
424

    
425
oVerbose :: OptType
426
oVerbose = Option "v" ["verbose"]
427
           (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
428
           "increase the verbosity level"
429

    
430
-- * Functions
431

    
432
-- | Helper for parsing a yes\/no command line flag.
433
parseYesNo :: Bool         -- ^ Default whalue (when we get a @Nothing@)
434
           -> Maybe String -- ^ Parameter value
435
           -> Result Bool  -- ^ Resulting boolean value
436
parseYesNo v Nothing      = return v
437
parseYesNo _ (Just "yes") = return True
438
parseYesNo _ (Just "no")  = return False
439
parseYesNo _ (Just s)     = fail $ "Invalid choice '" ++ s ++
440
                            "', pass one of 'yes' or 'no'"
441

    
442
-- | Usage info.
443
usageHelp :: String -> [OptType] -> String
444
usageHelp progname =
445
    usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
446
               progname Version.version progname)
447

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

    
481
-- | A shell script template for autogenerated scripts.
482
shTemplate :: String
483
shTemplate =
484
    printf "#!/bin/sh\n\n\
485
           \# Auto-generated script for executing cluster rebalancing\n\n\
486
           \# To stop, touch the file /tmp/stop-htools\n\n\
487
           \set -e\n\n\
488
           \check() {\n\
489
           \  if [ -f /tmp/stop-htools ]; then\n\
490
           \    echo 'Stop requested, exiting'\n\
491
           \    exit 0\n\
492
           \  fi\n\
493
           \}\n\n"
494

    
495
-- | Optionally print the node list.
496
maybePrintNodes :: Maybe [String]       -- ^ The field list
497
                -> String               -- ^ Informational message
498
                -> ([String] -> String) -- ^ Function to generate the listing
499
                -> IO ()
500
maybePrintNodes Nothing _ _ = return ()
501
maybePrintNodes (Just fields) msg fn = do
502
  hPutStrLn stderr ""
503
  hPutStrLn stderr (msg ++ " status:")
504
  hPutStrLn stderr $ fn fields
505

    
506

    
507
-- | Optionally print the instance list.
508
maybePrintInsts :: Bool   -- ^ Whether to print the instance list
509
                -> String -- ^ Type of the instance map (e.g. initial)
510
                -> String -- ^ The instance data
511
                -> IO ()
512
maybePrintInsts do_print msg instdata =
513
  when do_print $ do
514
    hPutStrLn stderr ""
515
    hPutStrLn stderr $ msg ++ " instance map:"
516
    hPutStr stderr instdata
517

    
518
-- | Function to display warning messages from parsing the cluster
519
-- state.
520
maybeShowWarnings :: [String] -- ^ The warning messages
521
                  -> IO ()
522
maybeShowWarnings fix_msgs =
523
  unless (null fix_msgs) $ do
524
    hPutStrLn stderr "Warning: cluster has inconsistent data:"
525
    hPutStrLn stderr . unlines . map (printf "  - %s") $ fix_msgs