CLI.hs 15.2 KB
Newer Older
1
2
3
4
{-| Implementation of command-line functions.

This module holds the common cli-related functions for the binaries,
separated into this module since Utils.hs is used in many other places
Iustin Pop's avatar
Iustin Pop committed
5
and this is more IO oriented.
6
7
8

-}

Iustin Pop's avatar
Iustin Pop committed
9
10
{-

Iustin Pop's avatar
Iustin Pop committed
11
Copyright (C) 2009, 2010 Google Inc.
Iustin Pop's avatar
Iustin Pop committed
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.

-}

30
module Ganeti.HTools.CLI
31
32
    ( Options(..)
    , OptType
Iustin Pop's avatar
Iustin Pop committed
33
    , parseOpts
34
    , shTemplate
35
    , defaultLuxiSocket
36
    -- * The options
37
    , oDataFile
Iustin Pop's avatar
Iustin Pop committed
38
39
    , oDiskMoves
    , oDynuFile
Iustin Pop's avatar
Iustin Pop committed
40
    , oEvacMode
41
    , oExInst
Iustin Pop's avatar
Iustin Pop committed
42
    , oExTags
43
    , oExecJobs
44
    , oGroup
45
    , oIDisk
Iustin Pop's avatar
Iustin Pop committed
46
    , oIMem
47
    , oINodes
Iustin Pop's avatar
Iustin Pop committed
48
49
    , oIVcpus
    , oLuxiSocket
50
    , oMaxCpu
Iustin Pop's avatar
Iustin Pop committed
51
    , oMaxSolLength
52
    , oMinDisk
53
54
    , oMinGain
    , oMinGainLim
Iustin Pop's avatar
Iustin Pop committed
55
56
57
58
59
60
61
62
63
64
65
    , oMinScore
    , oNoHeaders
    , oNodeSim
    , oOfflineNode
    , oOneline
    , oOutputDir
    , oPrintCommands
    , oPrintInsts
    , oPrintNodes
    , oQuiet
    , oRapiMaster
Iustin Pop's avatar
Iustin Pop committed
66
    , oSaveCluster
67
    , oShowHelp
Iustin Pop's avatar
Iustin Pop committed
68
69
70
    , oShowVer
    , oTieredSpec
    , oVerbose
71
72
    ) where

73
import Data.Maybe (fromMaybe)
74
75
import qualified Data.Version
import Monad
76
77
78
79
import System.Console.GetOpt
import System.IO
import System.Info
import System
80
import Text.Printf (printf)
81
82

import qualified Ganeti.HTools.Version as Version(version)
83
import Ganeti.HTools.Types
84
import Ganeti.HTools.Utils
85

86
87
88
89
-- | The default value for the luxi socket
defaultLuxiSocket :: FilePath
defaultLuxiSocket = "/var/run/ganeti/socket/ganeti-master"

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

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

-- | Abrreviation for the option type
162
type OptType = OptDescr (Options -> Result Options)
163

164
165
166
167
oDataFile :: OptType
oDataFile = Option "t" ["text-data"]
            (ReqArg (\ f o -> Ok o { optDataFile = Just f }) "FILE")
            "the cluster data FILE"
168

Iustin Pop's avatar
Iustin Pop committed
169
170
171
172
173
oDiskMoves :: OptType
oDiskMoves = Option "" ["no-disk-moves"]
             (NoArg (\ opts -> Ok opts { optDiskMoves = False}))
             "disallow disk moves from the list of allowed instance changes,\
             \ thus allowing only the 'cheap' failover/migrate operations"
174

Iustin Pop's avatar
Iustin Pop committed
175
176
177
178
oDynuFile :: OptType
oDynuFile = Option "U" ["dynu-file"]
            (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
            "Import dynamic utilisation data from the given FILE"
179

Iustin Pop's avatar
Iustin Pop committed
180
181
182
183
184
185
oEvacMode :: OptType
oEvacMode = Option "E" ["evac-mode"]
            (NoArg (\opts -> Ok opts { optEvacMode = True }))
            "enable evacuation mode, where the algorithm only moves \
            \ instances away from offline and drained nodes"

186
187
188
189
190
oExInst :: OptType
oExInst = Option "" ["exclude-instances"]
          (ReqArg (\ f opts -> Ok opts { optExInst = sepSplit ',' f }) "INSTS")
          "exclude given instances  from any moves"

Iustin Pop's avatar
Iustin Pop committed
191
192
193
194
oExTags :: OptType
oExTags = Option "" ["exclusion-tags"]
            (ReqArg (\ f opts -> Ok opts { optExTags = Just $ sepSplit ',' f })
             "TAG,...") "Enable instance exclusion based on given tag prefix"
195

196
197
oExecJobs :: OptType
oExecJobs = Option "X" ["exec"]
198
             (NoArg (\ opts -> Ok opts { optExecJobs = True}))
199
             "execute the suggested moves via Luxi (only available when using\
200
             \ it for data gathering)"
201

202
203
204
205
206
oGroup :: OptType
oGroup = Option "G" ["group"]
            (ReqArg (\ f o -> Ok o { optGroup = Just f }) "ID")
            "the ID of the group to balance"

Iustin Pop's avatar
Iustin Pop committed
207
208
209
210
211
212
213
oIDisk :: OptType
oIDisk = Option "" ["disk"]
         (ReqArg (\ d opts ->
                     let ospec = optISpec opts
                         nspec = ospec { rspecDsk = read d }
                     in Ok opts { optISpec = nspec }) "DISK")
         "disk size for instances"
214
215
216

oIMem :: OptType
oIMem = Option "" ["memory"]
217
218
219
220
        (ReqArg (\ m opts ->
                     let ospec = optISpec opts
                         nspec = ospec { rspecMem = read m }
                     in Ok opts { optISpec = nspec }) "MEMORY")
221
222
        "memory size for instances"

Iustin Pop's avatar
Iustin Pop committed
223
224
225
226
oINodes :: OptType
oINodes = Option "" ["req-nodes"]
          (ReqArg (\ n opts -> Ok opts { optINodes = read n }) "NODES")
          "number of nodes for the new instances (1=plain, 2=mirrored)"
227
228
229

oIVcpus :: OptType
oIVcpus = Option "" ["vcpus"]
230
231
232
233
          (ReqArg (\ p opts ->
                       let ospec = optISpec opts
                           nspec = ospec { rspecCpu = read p }
                       in Ok opts { optISpec = nspec }) "NUM")
234
235
          "number of virtual cpus for instances"

Iustin Pop's avatar
Iustin Pop committed
236
237
238
239
240
oLuxiSocket :: OptType
oLuxiSocket = Option "L" ["luxi"]
              (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
                       fromMaybe defaultLuxiSocket) "SOCKET")
              "collect data via Luxi, optionally using the given SOCKET path"
241
242
243

oMaxCpu :: OptType
oMaxCpu = Option "" ["max-cpu"]
244
          (ReqArg (\ n opts -> Ok opts { optMcpu = read n }) "RATIO")
245
246
          "maximum virtual-to-physical cpu ratio for nodes (from 1\
          \ upwards) [64]"
247

Iustin Pop's avatar
Iustin Pop committed
248
249
250
251
252
253
oMaxSolLength :: OptType
oMaxSolLength = Option "l" ["max-length"]
                (ReqArg (\ i opts -> Ok opts { optMaxLength = read i }) "N")
                "cap the solution at this many moves (useful for very\
                \ unbalanced clusters)"

254
255
oMinDisk :: OptType
oMinDisk = Option "" ["min-disk"]
256
           (ReqArg (\ n opts -> Ok opts { optMdsk = read n }) "RATIO")
257
           "minimum free disk space for nodes (between 0 and 1) [0]"
258

259
260
261
262
263
264
265
266
267
268
oMinGain :: OptType
oMinGain = Option "g" ["min-gain"]
            (ReqArg (\ g opts -> Ok opts { optMinGain = read g }) "DELTA")
            "minimum gain to aim for in a balancing step before giving up"

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

Iustin Pop's avatar
Iustin Pop committed
269
270
271
oMinScore :: OptType
oMinScore = Option "e" ["min-score"]
            (ReqArg (\ e opts -> Ok opts { optMinScore = read e }) "EPSILON")
272
            "mininum score to aim for"
273

Iustin Pop's avatar
Iustin Pop committed
274
275
276
277
oNoHeaders :: OptType
oNoHeaders = Option "" ["no-headers"]
             (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
             "do not show a header line"
278

Iustin Pop's avatar
Iustin Pop committed
279
280
oNodeSim :: OptType
oNodeSim = Option "" ["simulate"]
281
            (ReqArg (\ f o -> Ok o { optNodeSim = f:optNodeSim o }) "SPEC")
Iustin Pop's avatar
Iustin Pop committed
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
            "simulate an empty cluster, given as 'num_nodes,disk,ram,cpu'"

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

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

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

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

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

oPrintNodes :: OptType
oPrintNodes = Option "p" ["print-nodes"]
              (OptArg ((\ f opts ->
Iustin Pop's avatar
Iustin Pop committed
316
317
318
319
                            let (prefix, realf) = case f of
                                  '+':rest -> (["+"], rest)
                                  _ -> ([], f)
                                splitted = prefix ++ sepSplit ',' realf
Iustin Pop's avatar
Iustin Pop committed
320
321
322
323
324
325
326
327
328
329
330
331
332
333
                            in Ok opts { optShowNodes = Just splitted }) .
                       fromMaybe []) "FIELDS")
              "print the final node list"

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

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

Iustin Pop's avatar
Iustin Pop committed
334
335
336
337
338
oSaveCluster :: OptType
oSaveCluster = Option "S" ["save"]
            (ReqArg (\ f opts -> Ok opts { optSaveCluster = Just f }) "FILE")
            "Save cluster state at the end of the processing to FILE"

Iustin Pop's avatar
Iustin Pop committed
339
340
341
342
343
344
345
346
347
oShowHelp :: OptType
oShowHelp = Option "h" ["help"]
            (NoArg (\ opts -> Ok opts { optShowHelp = True}))
            "show help"

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

349
350
351
352
353
354
355
oTieredSpec :: OptType
oTieredSpec = Option "" ["tiered-alloc"]
             (ReqArg (\ inp opts -> do
                          let sp = sepSplit ',' inp
                          prs <- mapM (tryRead "tiered specs") sp
                          tspec <-
                              case prs of
356
                                [dsk, ram, cpu] -> return $ RSpec cpu ram dsk
357
358
                                _ -> Bad $ "Invalid specification: " ++ inp ++
                                     ", expected disk,ram,cpu"
359
360
                          return $ opts { optTieredSpec = Just tspec } )
              "TSPEC")
361
             "enable tiered specs allocation, given as 'disk,ram,cpu'"
362

Iustin Pop's avatar
Iustin Pop committed
363
364
365
366
oVerbose :: OptType
oVerbose = Option "v" ["verbose"]
           (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
           "increase the verbosity level"
367

368
-- | Usage info
369
usageHelp :: String -> [OptType] -> String
Iustin Pop's avatar
Iustin Pop committed
370
usageHelp progname =
371
    usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
Iustin Pop's avatar
Iustin Pop committed
372
               progname Version.version progname)
373

374
-- | Command line parser, using the 'options' structure.
375
376
377
378
379
380
parseOpts :: [String]               -- ^ The command line arguments
          -> String                 -- ^ The program name
          -> [OptType]              -- ^ The supported command line options
          -> IO (Options, [String]) -- ^ The resulting options and leftover
                                    -- arguments
parseOpts argv progname options =
381
382
383
    case getOpt Permute options argv of
      (o, n, []) ->
          do
384
385
386
387
388
389
390
391
            let (pr, args) = (foldM (flip id) defaultOptions o, n)
            po <- (case pr of
                     Bad msg -> do
                       hPutStrLn stderr "Error while parsing command\
                                        \line arguments:"
                       hPutStrLn stderr msg
                       exitWith $ ExitFailure 1
                     Ok val -> return val)
392
            when (optShowHelp po) $ do
393
              putStr $ usageHelp progname options
394
              exitWith ExitSuccess
395
            when (optShowVer po) $ do
Iustin Pop's avatar
Iustin Pop committed
396
397
398
              printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
                     progname Version.version
                     compilerName (Data.Version.showVersion compilerVersion)
Iustin Pop's avatar
Iustin Pop committed
399
                     os arch :: IO ()
Iustin Pop's avatar
Iustin Pop committed
400
              exitWith ExitSuccess
401
            return (po, args)
402
403
404
405
      (_, _, errs) -> do
        hPutStrLn stderr $ "Command line error: "  ++ concat errs
        hPutStrLn stderr $ usageHelp progname options
        exitWith $ ExitFailure 2
406

Iustin Pop's avatar
Iustin Pop committed
407
-- | A shell script template for autogenerated scripts.
408
409
410
411
412
413
414
415
416
417
418
419
shTemplate :: String
shTemplate =
    printf "#!/bin/sh\n\n\
           \# Auto-generated script for executing cluster rebalancing\n\n\
           \# To stop, touch the file /tmp/stop-htools\n\n\
           \set -e\n\n\
           \check() {\n\
           \  if [ -f /tmp/stop-htools ]; then\n\
           \    echo 'Stop requested, exiting'\n\
           \    exit 0\n\
           \  fi\n\
           \}\n\n"