Statistics
| Branch: | Tag: | Revision:

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

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

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

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

    
92
-- * Constants
93

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

    
100
-- * Data types
101

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

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

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

    
182
-- * Command line options
183

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
279
oMaxCpu :: OptType
280
oMaxCpu = Option "" ["max-cpu"]
281
          (ReqArg (\ n opts -> Ok opts { optMcpu = read n }) "RATIO")
282
          "maximum virtual-to-physical cpu ratio for nodes (from 1\
283
          \ upwards) [64]"
284

    
285
oMaxSolLength :: OptType
286
oMaxSolLength = Option "l" ["max-length"]
287
                (ReqArg (\ i opts -> Ok opts { optMaxLength = read i }) "N")
288
                "cap the solution at this many moves (useful for very\
289
                \ unbalanced clusters)"
290

    
291
oMinDisk :: OptType
292
oMinDisk = Option "" ["min-disk"]
293
           (ReqArg (\ n opts -> Ok opts { optMdsk = read n }) "RATIO")
294
           "minimum free disk space for nodes (between 0 and 1) [0]"
295

    
296
oMinGain :: OptType
297
oMinGain = Option "g" ["min-gain"]
298
            (ReqArg (\ g opts -> Ok opts { optMinGain = read g }) "DELTA")
299
            "minimum gain to aim for in a balancing step before giving up"
300

    
301
oMinGainLim :: OptType
302
oMinGainLim = Option "" ["min-gain-limit"]
303
            (ReqArg (\ g opts -> Ok opts { optMinGainLim = read g }) "SCORE")
304
            "minimum cluster score for which we start checking the min-gain"
305

    
306
oMinScore :: OptType
307
oMinScore = Option "e" ["min-score"]
308
            (ReqArg (\ e opts -> Ok opts { optMinScore = read e }) "EPSILON")
309
            "mininum score to aim for"
310

    
311
oNoHeaders :: OptType
312
oNoHeaders = Option "" ["no-headers"]
313
             (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
314
             "do not show a header line"
315

    
316
oNodeSim :: OptType
317
oNodeSim = Option "" ["simulate"]
318
            (ReqArg (\ f o -> Ok o { optNodeSim = f:optNodeSim o }) "SPEC")
319
            "simulate an empty cluster, given as 'num_nodes,disk,ram,cpu'"
320

    
321
oOfflineNode :: OptType
322
oOfflineNode = Option "O" ["offline"]
323
               (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
324
               "set node as offline"
325

    
326
oOneline :: OptType
327
oOneline = Option "o" ["oneline"]
328
           (NoArg (\ opts -> Ok opts { optOneline = True }))
329
           "print the ganeti command list for reaching the solution"
330

    
331
oOutputDir :: OptType
332
oOutputDir = Option "d" ["output-dir"]
333
             (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
334
             "directory in which to write output files"
335

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

    
345
oPrintInsts :: OptType
346
oPrintInsts = Option "" ["print-instances"]
347
              (NoArg (\ opts -> Ok opts { optShowInsts = True }))
348
              "print the final instance map"
349

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

    
361
oQuiet :: OptType
362
oQuiet = Option "q" ["quiet"]
363
         (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
364
         "decrease the verbosity level"
365

    
366
oRapiMaster :: OptType
367
oRapiMaster = Option "m" ["master"]
368
              (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
369
              "collect data via RAPI at the given ADDRESS"
370

    
371
oSaveCluster :: OptType
372
oSaveCluster = Option "S" ["save"]
373
            (ReqArg (\ f opts -> Ok opts { optSaveCluster = Just f }) "FILE")
374
            "Save cluster state at the end of the processing to FILE"
375

    
376
oShowHelp :: OptType
377
oShowHelp = Option "h" ["help"]
378
            (NoArg (\ opts -> Ok opts { optShowHelp = True}))
379
            "show help"
380

    
381
oShowVer :: OptType
382
oShowVer = Option "V" ["version"]
383
           (NoArg (\ opts -> Ok opts { optShowVer = True}))
384
           "show the version of the program"
385

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

    
406
oReplay :: OptType
407
oReplay = Option "" ["replay"]
408
          (ReqArg (\ stat opts -> Ok opts { optReplay = Just stat } ) "STATE")
409
          "Pre-seed the random number generator with STATE"
410

    
411
oVerbose :: OptType
412
oVerbose = Option "v" ["verbose"]
413
           (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
414
           "increase the verbosity level"
415

    
416
-- * Functions
417

    
418
-- | Usage info.
419
usageHelp :: String -> [OptType] -> String
420
usageHelp progname =
421
    usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
422
               progname Version.version progname)
423

    
424
-- | Command line parser, using the 'Options' structure.
425
parseOpts :: [String]               -- ^ The command line arguments
426
          -> String                 -- ^ The program name
427
          -> [OptType]              -- ^ The supported command line options
428
          -> IO (Options, [String]) -- ^ The resulting options and leftover
429
                                    -- arguments
430
parseOpts argv progname options =
431
    case getOpt Permute options argv of
432
      (o, n, []) ->
433
          do
434
            let (pr, args) = (foldM (flip id) defaultOptions o, n)
435
            po <- (case pr of
436
                     Bad msg -> do
437
                       hPutStrLn stderr "Error while parsing command\
438
                                        \line arguments:"
439
                       hPutStrLn stderr msg
440
                       exitWith $ ExitFailure 1
441
                     Ok val -> return val)
442
            when (optShowHelp po) $ do
443
              putStr $ usageHelp progname options
444
              exitWith ExitSuccess
445
            when (optShowVer po) $ do
446
              printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
447
                     progname Version.version
448
                     compilerName (Data.Version.showVersion compilerVersion)
449
                     os arch :: IO ()
450
              exitWith ExitSuccess
451
            return (po, args)
452
      (_, _, errs) -> do
453
        hPutStrLn stderr $ "Command line error: "  ++ concat errs
454
        hPutStrLn stderr $ usageHelp progname options
455
        exitWith $ ExitFailure 2
456

    
457
-- | A shell script template for autogenerated scripts.
458
shTemplate :: String
459
shTemplate =
460
    printf "#!/bin/sh\n\n\
461
           \# Auto-generated script for executing cluster rebalancing\n\n\
462
           \# To stop, touch the file /tmp/stop-htools\n\n\
463
           \set -e\n\n\
464
           \check() {\n\
465
           \  if [ -f /tmp/stop-htools ]; then\n\
466
           \    echo 'Stop requested, exiting'\n\
467
           \    exit 0\n\
468
           \  fi\n\
469
           \}\n\n"
470

    
471
-- | Optionally print the node list.
472
maybePrintNodes :: Maybe [String]       -- ^ The field list
473
                -> String               -- ^ Informational message
474
                -> ([String] -> String) -- ^ Function to generate the listing
475
                -> IO ()
476
maybePrintNodes Nothing _ _ = return ()
477
maybePrintNodes (Just fields) msg fn = do
478
  hPutStrLn stderr ""
479
  hPutStrLn stderr (msg ++ " status:")
480
  hPutStrLn stderr $ fn fields
481

    
482

    
483
-- | Optionally print the instance list.
484
maybePrintInsts :: Bool   -- ^ Whether to print the instance list
485
                -> String -- ^ Type of the instance map (e.g. initial)
486
                -> String -- ^ The instance data
487
                -> IO ()
488
maybePrintInsts do_print msg instdata =
489
  when do_print $ do
490
    hPutStrLn stderr ""
491
    hPutStrLn stderr $ msg ++ " instance map:"
492
    hPutStr stderr instdata