Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (16.9 kB)

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

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

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

    
91
-- | The default value for the luxi socket
92
defaultLuxiSocket :: FilePath
93
defaultLuxiSocket = C.masterSocket
94

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

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

    
170
-- | Abrreviation for the option type
171
type OptType = OptDescr (Options -> Result Options)
172

    
173
oDataFile :: OptType
174
oDataFile = Option "t" ["text-data"]
175
            (ReqArg (\ f o -> Ok o { optDataFile = Just f }) "FILE")
176
            "the cluster data FILE"
177

    
178
oDiskMoves :: OptType
179
oDiskMoves = Option "" ["no-disk-moves"]
180
             (NoArg (\ opts -> Ok opts { optDiskMoves = False}))
181
             "disallow disk moves from the list of allowed instance changes,\
182
             \ thus allowing only the 'cheap' failover/migrate operations"
183

    
184
oSelInst :: OptType
185
oSelInst = Option "" ["select-instances"]
186
          (ReqArg (\ f opts -> Ok opts { optSelInst = sepSplit ',' f }) "INSTS")
187
          "only select given instances for any moves"
188

    
189
oInstMoves :: OptType
190
oInstMoves = Option "" ["no-instance-moves"]
191
             (NoArg (\ opts -> Ok opts { optInstMoves = False}))
192
             "disallow instance (primary node) moves from the list of allowed,\
193
             \ instance changes, thus allowing only slower, but sometimes\
194
             \ safer, drbd secondary changes"
195

    
196
oDynuFile :: OptType
197
oDynuFile = Option "U" ["dynu-file"]
198
            (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
199
            "Import dynamic utilisation data from the given FILE"
200

    
201
oEvacMode :: OptType
202
oEvacMode = Option "E" ["evac-mode"]
203
            (NoArg (\opts -> Ok opts { optEvacMode = True }))
204
            "enable evacuation mode, where the algorithm only moves \
205
            \ instances away from offline and drained nodes"
206

    
207
oExInst :: OptType
208
oExInst = Option "" ["exclude-instances"]
209
          (ReqArg (\ f opts -> Ok opts { optExInst = sepSplit ',' f }) "INSTS")
210
          "exclude given instances from any moves"
211

    
212
oExTags :: OptType
213
oExTags = Option "" ["exclusion-tags"]
214
            (ReqArg (\ f opts -> Ok opts { optExTags = Just $ sepSplit ',' f })
215
             "TAG,...") "Enable instance exclusion based on given tag prefix"
216

    
217
oExecJobs :: OptType
218
oExecJobs = Option "X" ["exec"]
219
             (NoArg (\ opts -> Ok opts { optExecJobs = True}))
220
             "execute the suggested moves via Luxi (only available when using\
221
             \ it for data gathering)"
222

    
223
oGroup :: OptType
224
oGroup = Option "G" ["group"]
225
            (ReqArg (\ f o -> Ok o { optGroup = Just f }) "ID")
226
            "the ID of the group to balance"
227

    
228
oIDisk :: OptType
229
oIDisk = Option "" ["disk"]
230
         (ReqArg (\ d opts ->
231
                     let ospec = optISpec opts
232
                         nspec = ospec { rspecDsk = read d }
233
                     in Ok opts { optISpec = nspec }) "DISK")
234
         "disk size for instances"
235

    
236
oIMem :: OptType
237
oIMem = Option "" ["memory"]
238
        (ReqArg (\ m opts ->
239
                     let ospec = optISpec opts
240
                         nspec = ospec { rspecMem = read m }
241
                     in Ok opts { optISpec = nspec }) "MEMORY")
242
        "memory size for instances"
243

    
244
oINodes :: OptType
245
oINodes = Option "" ["req-nodes"]
246
          (ReqArg (\ n opts -> Ok opts { optINodes = read n }) "NODES")
247
          "number of nodes for the new instances (1=plain, 2=mirrored)"
248

    
249
oIVcpus :: OptType
250
oIVcpus = Option "" ["vcpus"]
251
          (ReqArg (\ p opts ->
252
                       let ospec = optISpec opts
253
                           nspec = ospec { rspecCpu = read p }
254
                       in Ok opts { optISpec = nspec }) "NUM")
255
          "number of virtual cpus for instances"
256

    
257
oLuxiSocket :: OptType
258
oLuxiSocket = Option "L" ["luxi"]
259
              (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
260
                       fromMaybe defaultLuxiSocket) "SOCKET")
261
              "collect data via Luxi, optionally using the given SOCKET path"
262

    
263
oMaxCpu :: OptType
264
oMaxCpu = Option "" ["max-cpu"]
265
          (ReqArg (\ n opts -> Ok opts { optMcpu = read n }) "RATIO")
266
          "maximum virtual-to-physical cpu ratio for nodes (from 1\
267
          \ upwards) [64]"
268

    
269
oMaxSolLength :: OptType
270
oMaxSolLength = Option "l" ["max-length"]
271
                (ReqArg (\ i opts -> Ok opts { optMaxLength = read i }) "N")
272
                "cap the solution at this many moves (useful for very\
273
                \ unbalanced clusters)"
274

    
275
oMinDisk :: OptType
276
oMinDisk = Option "" ["min-disk"]
277
           (ReqArg (\ n opts -> Ok opts { optMdsk = read n }) "RATIO")
278
           "minimum free disk space for nodes (between 0 and 1) [0]"
279

    
280
oMinGain :: OptType
281
oMinGain = Option "g" ["min-gain"]
282
            (ReqArg (\ g opts -> Ok opts { optMinGain = read g }) "DELTA")
283
            "minimum gain to aim for in a balancing step before giving up"
284

    
285
oMinGainLim :: OptType
286
oMinGainLim = Option "" ["min-gain-limit"]
287
            (ReqArg (\ g opts -> Ok opts { optMinGainLim = read g }) "SCORE")
288
            "minimum cluster score for which we start checking the min-gain"
289

    
290
oMinScore :: OptType
291
oMinScore = Option "e" ["min-score"]
292
            (ReqArg (\ e opts -> Ok opts { optMinScore = read e }) "EPSILON")
293
            "mininum score to aim for"
294

    
295
oNoHeaders :: OptType
296
oNoHeaders = Option "" ["no-headers"]
297
             (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
298
             "do not show a header line"
299

    
300
oNodeSim :: OptType
301
oNodeSim = Option "" ["simulate"]
302
            (ReqArg (\ f o -> Ok o { optNodeSim = f:optNodeSim o }) "SPEC")
303
            "simulate an empty cluster, given as 'num_nodes,disk,ram,cpu'"
304

    
305
oOfflineNode :: OptType
306
oOfflineNode = Option "O" ["offline"]
307
               (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
308
               "set node as offline"
309

    
310
oOneline :: OptType
311
oOneline = Option "o" ["oneline"]
312
           (NoArg (\ opts -> Ok opts { optOneline = True }))
313
           "print the ganeti command list for reaching the solution"
314

    
315
oOutputDir :: OptType
316
oOutputDir = Option "d" ["output-dir"]
317
             (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
318
             "directory in which to write output files"
319

    
320
oPrintCommands :: OptType
321
oPrintCommands = Option "C" ["print-commands"]
322
                 (OptArg ((\ f opts -> Ok opts { optShowCmds = Just f }) .
323
                          fromMaybe "-")
324
                  "FILE")
325
                 "print the ganeti command list for reaching the solution,\
326
                 \ if an argument is passed then write the commands to a\
327
                 \ file named as such"
328

    
329
oPrintInsts :: OptType
330
oPrintInsts = Option "" ["print-instances"]
331
              (NoArg (\ opts -> Ok opts { optShowInsts = True }))
332
              "print the final instance map"
333

    
334
oPrintNodes :: OptType
335
oPrintNodes = Option "p" ["print-nodes"]
336
              (OptArg ((\ f opts ->
337
                            let (prefix, realf) = case f of
338
                                  '+':rest -> (["+"], rest)
339
                                  _ -> ([], f)
340
                                splitted = prefix ++ sepSplit ',' realf
341
                            in Ok opts { optShowNodes = Just splitted }) .
342
                       fromMaybe []) "FIELDS")
343
              "print the final node list"
344

    
345
oQuiet :: OptType
346
oQuiet = Option "q" ["quiet"]
347
         (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
348
         "decrease the verbosity level"
349

    
350
oRapiMaster :: OptType
351
oRapiMaster = Option "m" ["master"]
352
              (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
353
              "collect data via RAPI at the given ADDRESS"
354

    
355
oSaveCluster :: OptType
356
oSaveCluster = Option "S" ["save"]
357
            (ReqArg (\ f opts -> Ok opts { optSaveCluster = Just f }) "FILE")
358
            "Save cluster state at the end of the processing to FILE"
359

    
360
oShowHelp :: OptType
361
oShowHelp = Option "h" ["help"]
362
            (NoArg (\ opts -> Ok opts { optShowHelp = True}))
363
            "show help"
364

    
365
oShowVer :: OptType
366
oShowVer = Option "V" ["version"]
367
           (NoArg (\ opts -> Ok opts { optShowVer = True}))
368
           "show the version of the program"
369

    
370
oTieredSpec :: OptType
371
oTieredSpec = Option "" ["tiered-alloc"]
372
             (ReqArg (\ inp opts -> do
373
                          let sp = sepSplit ',' inp
374
                          prs <- mapM (tryRead "tiered specs") sp
375
                          tspec <-
376
                              case prs of
377
                                [dsk, ram, cpu] -> return $ RSpec cpu ram dsk
378
                                _ -> Bad $ "Invalid specification: " ++ inp ++
379
                                     ", expected disk,ram,cpu"
380
                          return $ opts { optTieredSpec = Just tspec } )
381
              "TSPEC")
382
             "enable tiered specs allocation, given as 'disk,ram,cpu'"
383

    
384
oVerbose :: OptType
385
oVerbose = Option "v" ["verbose"]
386
           (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
387
           "increase the verbosity level"
388

    
389
-- | Usage info
390
usageHelp :: String -> [OptType] -> String
391
usageHelp progname =
392
    usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
393
               progname Version.version progname)
394

    
395
-- | Command line parser, using the 'options' structure.
396
parseOpts :: [String]               -- ^ The command line arguments
397
          -> String                 -- ^ The program name
398
          -> [OptType]              -- ^ The supported command line options
399
          -> IO (Options, [String]) -- ^ The resulting options and leftover
400
                                    -- arguments
401
parseOpts argv progname options =
402
    case getOpt Permute options argv of
403
      (o, n, []) ->
404
          do
405
            let (pr, args) = (foldM (flip id) defaultOptions o, n)
406
            po <- (case pr of
407
                     Bad msg -> do
408
                       hPutStrLn stderr "Error while parsing command\
409
                                        \line arguments:"
410
                       hPutStrLn stderr msg
411
                       exitWith $ ExitFailure 1
412
                     Ok val -> return val)
413
            when (optShowHelp po) $ do
414
              putStr $ usageHelp progname options
415
              exitWith ExitSuccess
416
            when (optShowVer po) $ do
417
              printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
418
                     progname Version.version
419
                     compilerName (Data.Version.showVersion compilerVersion)
420
                     os arch :: IO ()
421
              exitWith ExitSuccess
422
            return (po, args)
423
      (_, _, errs) -> do
424
        hPutStrLn stderr $ "Command line error: "  ++ concat errs
425
        hPutStrLn stderr $ usageHelp progname options
426
        exitWith $ ExitFailure 2
427

    
428
-- | A shell script template for autogenerated scripts.
429
shTemplate :: String
430
shTemplate =
431
    printf "#!/bin/sh\n\n\
432
           \# Auto-generated script for executing cluster rebalancing\n\n\
433
           \# To stop, touch the file /tmp/stop-htools\n\n\
434
           \set -e\n\n\
435
           \check() {\n\
436
           \  if [ -f /tmp/stop-htools ]; then\n\
437
           \    echo 'Stop requested, exiting'\n\
438
           \    exit 0\n\
439
           \  fi\n\
440
           \}\n\n"
441

    
442
-- | Optionally print the node list.
443
maybePrintNodes :: Maybe [String]       -- ^ The field list
444
                -> String               -- ^ Informational message
445
                -> ([String] -> String) -- ^ Function to generate the listing
446
                -> IO ()
447
maybePrintNodes Nothing _ _ = return ()
448
maybePrintNodes (Just fields) msg fn = do
449
  hPutStrLn stderr ""
450
  hPutStrLn stderr (msg ++ " status:")
451
  hPutStrLn stderr $ fn fields
452

    
453

    
454
-- | Optionally print the instance list.
455
maybePrintInsts :: Bool   -- ^ Whether to print the instance list
456
                -> String -- ^ Type of the instance map (e.g. initial)
457
                -> String -- ^ The instance data
458
                -> IO ()
459
maybePrintInsts do_print msg instdata =
460
  when do_print $ do
461
    hPutStrLn stderr ""
462
    hPutStrLn stderr $ msg ++ " instance map:"
463
    hPutStr stderr instdata