Statistics
| Branch: | Tag: | Revision:

root / Ganeti / HTools / CLI.hs @ a46f34d7

History | View | Annotate | Download (13.7 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 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
    , oMinScore
53
    , oNoHeaders
54
    , oNodeSim
55
    , oOfflineNode
56
    , oOneline
57
    , oOutputDir
58
    , oPrintCommands
59
    , oPrintInsts
60
    , oPrintNodes
61
    , oQuiet
62
    , oRapiMaster
63
    , oShowHelp
64
    , oShowVer
65
    , oTieredSpec
66
    , oVerbose
67
    ) where
68

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

    
78
import qualified Ganeti.HTools.Version as Version(version)
79
import Ganeti.HTools.Types
80
import Ganeti.HTools.Utils
81

    
82
-- | The default value for the luxi socket
83
defaultLuxiSocket :: FilePath
84
defaultLuxiSocket = "/var/run/ganeti/socket/ganeti-master"
85

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

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

    
149
-- | Abrreviation for the option type
150
type OptType = OptDescr (Options -> Result Options)
151

    
152
oDataFile :: OptType
153
oDataFile = Option "t" ["text-data"]
154
            (ReqArg (\ f o -> Ok o { optDataFile = Just f }) "FILE")
155
            "the cluster data FILE"
156

    
157
oDiskMoves :: OptType
158
oDiskMoves = Option "" ["no-disk-moves"]
159
             (NoArg (\ opts -> Ok opts { optDiskMoves = False}))
160
             "disallow disk moves from the list of allowed instance changes,\
161
             \ thus allowing only the 'cheap' failover/migrate operations"
162

    
163
oDynuFile :: OptType
164
oDynuFile = Option "U" ["dynu-file"]
165
            (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
166
            "Import dynamic utilisation data from the given FILE"
167

    
168
oEvacMode :: OptType
169
oEvacMode = Option "E" ["evac-mode"]
170
            (NoArg (\opts -> Ok opts { optEvacMode = True }))
171
            "enable evacuation mode, where the algorithm only moves \
172
            \ instances away from offline and drained nodes"
173

    
174
oExInst :: OptType
175
oExInst = Option "" ["exclude-instances"]
176
          (ReqArg (\ f opts -> Ok opts { optExInst = sepSplit ',' f }) "INSTS")
177
          "exclude given instances  from any moves"
178

    
179
oExTags :: OptType
180
oExTags = Option "" ["exclusion-tags"]
181
            (ReqArg (\ f opts -> Ok opts { optExTags = Just $ sepSplit ',' f })
182
             "TAG,...") "Enable instance exclusion based on given tag prefix"
183

    
184
oExecJobs :: OptType
185
oExecJobs = Option "X" ["exec"]
186
             (NoArg (\ opts -> Ok opts { optExecJobs = True}))
187
             "execute the suggested moves via Luxi (only available when using\
188
             \ it for data gathering)"
189

    
190
oIDisk :: OptType
191
oIDisk = Option "" ["disk"]
192
         (ReqArg (\ d opts ->
193
                     let ospec = optISpec opts
194
                         nspec = ospec { rspecDsk = read d }
195
                     in Ok opts { optISpec = nspec }) "DISK")
196
         "disk size for instances"
197

    
198
oIMem :: OptType
199
oIMem = Option "" ["memory"]
200
        (ReqArg (\ m opts ->
201
                     let ospec = optISpec opts
202
                         nspec = ospec { rspecMem = read m }
203
                     in Ok opts { optISpec = nspec }) "MEMORY")
204
        "memory size for instances"
205

    
206
oINodes :: OptType
207
oINodes = Option "" ["req-nodes"]
208
          (ReqArg (\ n opts -> Ok opts { optINodes = read n }) "NODES")
209
          "number of nodes for the new instances (1=plain, 2=mirrored)"
210

    
211
oIVcpus :: OptType
212
oIVcpus = Option "" ["vcpus"]
213
          (ReqArg (\ p opts ->
214
                       let ospec = optISpec opts
215
                           nspec = ospec { rspecCpu = read p }
216
                       in Ok opts { optISpec = nspec }) "NUM")
217
          "number of virtual cpus for instances"
218

    
219
oLuxiSocket :: OptType
220
oLuxiSocket = Option "L" ["luxi"]
221
              (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
222
                       fromMaybe defaultLuxiSocket) "SOCKET")
223
              "collect data via Luxi, optionally using the given SOCKET path"
224

    
225
oMaxCpu :: OptType
226
oMaxCpu = Option "" ["max-cpu"]
227
          (ReqArg (\ n opts -> Ok opts { optMcpu = read n }) "RATIO")
228
          "maximum virtual-to-physical cpu ratio for nodes"
229

    
230
oMaxSolLength :: OptType
231
oMaxSolLength = Option "l" ["max-length"]
232
                (ReqArg (\ i opts -> Ok opts { optMaxLength = read i }) "N")
233
                "cap the solution at this many moves (useful for very\
234
                \ unbalanced clusters)"
235

    
236
oMinDisk :: OptType
237
oMinDisk = Option "" ["min-disk"]
238
           (ReqArg (\ n opts -> Ok opts { optMdsk = read n }) "RATIO")
239
           "minimum free disk space for nodes (between 0 and 1)"
240

    
241
oMinScore :: OptType
242
oMinScore = Option "e" ["min-score"]
243
            (ReqArg (\ e opts -> Ok opts { optMinScore = read e }) "EPSILON")
244
            " mininum score to aim for"
245

    
246
oNoHeaders :: OptType
247
oNoHeaders = Option "" ["no-headers"]
248
             (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
249
             "do not show a header line"
250

    
251
oNodeSim :: OptType
252
oNodeSim = Option "" ["simulate"]
253
            (ReqArg (\ f o -> Ok o { optNodeSim = Just f }) "SPEC")
254
            "simulate an empty cluster, given as 'num_nodes,disk,ram,cpu'"
255

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

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

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

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

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

    
285
oPrintNodes :: OptType
286
oPrintNodes = Option "p" ["print-nodes"]
287
              (OptArg ((\ f opts ->
288
                            let splitted = sepSplit ',' f
289
                            in Ok opts { optShowNodes = Just splitted }) .
290
                       fromMaybe []) "FIELDS")
291
              "print the final node list"
292

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

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

    
303
oShowHelp :: OptType
304
oShowHelp = Option "h" ["help"]
305
            (NoArg (\ opts -> Ok opts { optShowHelp = True}))
306
            "show help"
307

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

    
313
oTieredSpec :: OptType
314
oTieredSpec = Option "" ["tiered-alloc"]
315
             (ReqArg (\ inp opts -> do
316
                          let sp = sepSplit ',' inp
317
                          prs <- mapM (tryRead "tiered specs") sp
318
                          tspec <-
319
                              case prs of
320
                                [dsk, ram, cpu] -> return $ RSpec cpu ram dsk
321
                                _ -> Bad $ "Invalid specification: " ++ inp
322
                          return $ opts { optTieredSpec = Just tspec } )
323
              "TSPEC")
324
             "enable tiered specs allocation, given as 'disk,ram,cpu'"
325

    
326
oVerbose :: OptType
327
oVerbose = Option "v" ["verbose"]
328
           (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
329
           "increase the verbosity level"
330

    
331
-- | Usage info
332
usageHelp :: String -> [OptType] -> String
333
usageHelp progname =
334
    usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
335
               progname Version.version progname)
336

    
337
-- | Command line parser, using the 'options' structure.
338
parseOpts :: [String]               -- ^ The command line arguments
339
          -> String                 -- ^ The program name
340
          -> [OptType]              -- ^ The supported command line options
341
          -> IO (Options, [String]) -- ^ The resulting options and leftover
342
                                    -- arguments
343
parseOpts argv progname options =
344
    case getOpt Permute options argv of
345
      (o, n, []) ->
346
          do
347
            let (pr, args) = (foldM (flip id) defaultOptions o, n)
348
            po <- (case pr of
349
                     Bad msg -> do
350
                       hPutStrLn stderr "Error while parsing command\
351
                                        \line arguments:"
352
                       hPutStrLn stderr msg
353
                       exitWith $ ExitFailure 1
354
                     Ok val -> return val)
355
            when (optShowHelp po) $ do
356
              putStr $ usageHelp progname options
357
              exitWith ExitSuccess
358
            when (optShowVer po) $ do
359
              printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
360
                     progname Version.version
361
                     compilerName (Data.Version.showVersion compilerVersion)
362
                     os arch :: IO ()
363
              exitWith ExitSuccess
364
            return (po, args)
365
      (_, _, errs) -> do
366
        hPutStrLn stderr $ "Command line error: "  ++ concat errs
367
        hPutStrLn stderr $ usageHelp progname options
368
        exitWith $ ExitFailure 2
369

    
370
-- | A shell script template for autogenerated scripts.
371
shTemplate :: String
372
shTemplate =
373
    printf "#!/bin/sh\n\n\
374
           \# Auto-generated script for executing cluster rebalancing\n\n\
375
           \# To stop, touch the file /tmp/stop-htools\n\n\
376
           \set -e\n\n\
377
           \check() {\n\
378
           \  if [ -f /tmp/stop-htools ]; then\n\
379
           \    echo 'Stop requested, exiting'\n\
380
           \    exit 0\n\
381
           \  fi\n\
382
           \}\n\n"