Statistics
| Branch: | Tag: | Revision:

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

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

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

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

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

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

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

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

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

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

    
177
oDynuFile :: OptType
178
oDynuFile = Option "U" ["dynu-file"]
179
            (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
180
            "Import dynamic utilisation data from the given FILE"
181

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
271
oMinScore :: OptType
272
oMinScore = Option "e" ["min-score"]
273
            (ReqArg (\ e opts -> Ok opts { optMinScore = read e }) "EPSILON")
274
            "mininum score to aim for"
275

    
276
oNoHeaders :: OptType
277
oNoHeaders = Option "" ["no-headers"]
278
             (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
279
             "do not show a header line"
280

    
281
oNodeSim :: OptType
282
oNodeSim = Option "" ["simulate"]
283
            (ReqArg (\ f o -> Ok o { optNodeSim = f:optNodeSim o }) "SPEC")
284
            "simulate an empty cluster, given as 'num_nodes,disk,ram,cpu'"
285

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

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

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

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

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

    
315
oPrintNodes :: OptType
316
oPrintNodes = Option "p" ["print-nodes"]
317
              (OptArg ((\ f opts ->
318
                            let (prefix, realf) = case f of
319
                                  '+':rest -> (["+"], rest)
320
                                  _ -> ([], f)
321
                                splitted = prefix ++ sepSplit ',' realf
322
                            in Ok opts { optShowNodes = Just splitted }) .
323
                       fromMaybe []) "FIELDS")
324
              "print the final node list"
325

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

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

    
336
oSaveCluster :: OptType
337
oSaveCluster = Option "S" ["save"]
338
            (ReqArg (\ f opts -> Ok opts { optSaveCluster = Just f }) "FILE")
339
            "Save cluster state at the end of the processing to FILE"
340

    
341
oShowHelp :: OptType
342
oShowHelp = Option "h" ["help"]
343
            (NoArg (\ opts -> Ok opts { optShowHelp = True}))
344
            "show help"
345

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

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

    
365
oVerbose :: OptType
366
oVerbose = Option "v" ["verbose"]
367
           (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
368
           "increase the verbosity level"
369

    
370
-- | Usage info
371
usageHelp :: String -> [OptType] -> String
372
usageHelp progname =
373
    usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
374
               progname Version.version progname)
375

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

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

    
423
-- | Optionally print the node list.
424
maybePrintNodes :: Maybe [String]       -- ^ The field list
425
                -> String               -- ^ Informational message
426
                -> ([String] -> String) -- ^ Function to generate the listing
427
                -> IO ()
428
maybePrintNodes Nothing _ _ = return ()
429
maybePrintNodes (Just fields) msg fn = do
430
  hPutStrLn stderr ""
431
  hPutStrLn stderr (msg ++ " status:")
432
  hPutStrLn stderr $ fn fields
433

    
434

    
435
-- | Optionally print the instance list.
436
maybePrintInsts :: Bool   -- ^ Whether to print the instance list
437
                -> String -- ^ Type of the instance map (e.g. initial)
438
                -> String -- ^ The instance data
439
                -> IO ()
440
maybePrintInsts do_print msg instdata =
441
  when do_print $ do
442
    hPutStrLn stderr ""
443
    hPutStrLn stderr $ msg ++ " instance map:"
444
    hPutStr stderr instdata