Statistics
| Branch: | Tag: | Revision:

root / Ganeti / HTools / CLI.hs @ f5ed8632

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

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

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

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

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

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

    
158
-- | Abrreviation for the option type
159
type OptType = OptDescr (Options -> Result Options)
160

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

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

    
172
oDynuFile :: OptType
173
oDynuFile = Option "U" ["dynu-file"]
174
            (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
175
            "Import dynamic utilisation data from the given FILE"
176

    
177
oEvacMode :: OptType
178
oEvacMode = Option "E" ["evac-mode"]
179
            (NoArg (\opts -> Ok opts { optEvacMode = True }))
180
            "enable evacuation mode, where the algorithm only moves \
181
            \ instances away from offline and drained nodes"
182

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

    
188
oExTags :: OptType
189
oExTags = Option "" ["exclusion-tags"]
190
            (ReqArg (\ f opts -> Ok opts { optExTags = Just $ sepSplit ',' f })
191
             "TAG,...") "Enable instance exclusion based on given tag prefix"
192

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

    
199
oIDisk :: OptType
200
oIDisk = Option "" ["disk"]
201
         (ReqArg (\ d opts ->
202
                     let ospec = optISpec opts
203
                         nspec = ospec { rspecDsk = read d }
204
                     in Ok opts { optISpec = nspec }) "DISK")
205
         "disk size for instances"
206

    
207
oIMem :: OptType
208
oIMem = Option "" ["memory"]
209
        (ReqArg (\ m opts ->
210
                     let ospec = optISpec opts
211
                         nspec = ospec { rspecMem = read m }
212
                     in Ok opts { optISpec = nspec }) "MEMORY")
213
        "memory size for instances"
214

    
215
oINodes :: OptType
216
oINodes = Option "" ["req-nodes"]
217
          (ReqArg (\ n opts -> Ok opts { optINodes = read n }) "NODES")
218
          "number of nodes for the new instances (1=plain, 2=mirrored)"
219

    
220
oIVcpus :: OptType
221
oIVcpus = Option "" ["vcpus"]
222
          (ReqArg (\ p opts ->
223
                       let ospec = optISpec opts
224
                           nspec = ospec { rspecCpu = read p }
225
                       in Ok opts { optISpec = nspec }) "NUM")
226
          "number of virtual cpus for instances"
227

    
228
oLuxiSocket :: OptType
229
oLuxiSocket = Option "L" ["luxi"]
230
              (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
231
                       fromMaybe defaultLuxiSocket) "SOCKET")
232
              "collect data via Luxi, optionally using the given SOCKET path"
233

    
234
oMaxCpu :: OptType
235
oMaxCpu = Option "" ["max-cpu"]
236
          (ReqArg (\ n opts -> Ok opts { optMcpu = read n }) "RATIO")
237
          "maximum virtual-to-physical cpu ratio for nodes (from 1\
238
          \ upwards) [64]"
239

    
240
oMaxSolLength :: OptType
241
oMaxSolLength = Option "l" ["max-length"]
242
                (ReqArg (\ i opts -> Ok opts { optMaxLength = read i }) "N")
243
                "cap the solution at this many moves (useful for very\
244
                \ unbalanced clusters)"
245

    
246
oMinDisk :: OptType
247
oMinDisk = Option "" ["min-disk"]
248
           (ReqArg (\ n opts -> Ok opts { optMdsk = read n }) "RATIO")
249
           "minimum free disk space for nodes (between 0 and 1) [0]"
250

    
251
oMinGain :: OptType
252
oMinGain = Option "g" ["min-gain"]
253
            (ReqArg (\ g opts -> Ok opts { optMinGain = read g }) "DELTA")
254
            "minimum gain to aim for in a balancing step before giving up"
255

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

    
261
oMinScore :: OptType
262
oMinScore = Option "e" ["min-score"]
263
            (ReqArg (\ e opts -> Ok opts { optMinScore = read e }) "EPSILON")
264
            "mininum score to aim for"
265

    
266
oNoHeaders :: OptType
267
oNoHeaders = Option "" ["no-headers"]
268
             (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
269
             "do not show a header line"
270

    
271
oNodeSim :: OptType
272
oNodeSim = Option "" ["simulate"]
273
            (ReqArg (\ f o -> Ok o { optNodeSim = Just f }) "SPEC")
274
            "simulate an empty cluster, given as 'num_nodes,disk,ram,cpu'"
275

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

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

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

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

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

    
305
oPrintNodes :: OptType
306
oPrintNodes = Option "p" ["print-nodes"]
307
              (OptArg ((\ f opts ->
308
                            let (prefix, realf) = case f of
309
                                  '+':rest -> (["+"], rest)
310
                                  _ -> ([], f)
311
                                splitted = prefix ++ sepSplit ',' realf
312
                            in Ok opts { optShowNodes = Just splitted }) .
313
                       fromMaybe []) "FIELDS")
314
              "print the final node list"
315

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

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

    
326
oSaveCluster :: OptType
327
oSaveCluster = Option "S" ["save"]
328
            (ReqArg (\ f opts -> Ok opts { optSaveCluster = Just f }) "FILE")
329
            "Save cluster state at the end of the processing to FILE"
330

    
331
oShowHelp :: OptType
332
oShowHelp = Option "h" ["help"]
333
            (NoArg (\ opts -> Ok opts { optShowHelp = True}))
334
            "show help"
335

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

    
341
oTieredSpec :: OptType
342
oTieredSpec = Option "" ["tiered-alloc"]
343
             (ReqArg (\ inp opts -> do
344
                          let sp = sepSplit ',' inp
345
                          prs <- mapM (tryRead "tiered specs") sp
346
                          tspec <-
347
                              case prs of
348
                                [dsk, ram, cpu] -> return $ RSpec cpu ram dsk
349
                                _ -> Bad $ "Invalid specification: " ++ inp ++
350
                                     ", expected disk,ram,cpu"
351
                          return $ opts { optTieredSpec = Just tspec } )
352
              "TSPEC")
353
             "enable tiered specs allocation, given as 'disk,ram,cpu'"
354

    
355
oVerbose :: OptType
356
oVerbose = Option "v" ["verbose"]
357
           (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
358
           "increase the verbosity level"
359

    
360
-- | Usage info
361
usageHelp :: String -> [OptType] -> String
362
usageHelp progname =
363
    usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
364
               progname Version.version progname)
365

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

    
399
-- | A shell script template for autogenerated scripts.
400
shTemplate :: String
401
shTemplate =
402
    printf "#!/bin/sh\n\n\
403
           \# Auto-generated script for executing cluster rebalancing\n\n\
404
           \# To stop, touch the file /tmp/stop-htools\n\n\
405
           \set -e\n\n\
406
           \check() {\n\
407
           \  if [ -f /tmp/stop-htools ]; then\n\
408
           \    echo 'Stop requested, exiting'\n\
409
           \    exit 0\n\
410
           \  fi\n\
411
           \}\n\n"