Statistics
| Branch: | Tag: | Revision:

root / Ganeti / HTools / CLI.hs @ f0f21ec4

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

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

    
76
import qualified Ganeti.HTools.Version as Version(version)
77
import Ganeti.HTools.Types
78
import Ganeti.HTools.Utils
79

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

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

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

    
145
-- | Abrreviation for the option type
146
type OptType = OptDescr (Options -> Result Options)
147

    
148
oDataFile :: OptType
149
oDataFile = Option "t" ["text-data"]
150
            (ReqArg (\ f o -> Ok o { optDataFile = Just f }) "FILE")
151
            "the cluster data FILE"
152

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

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

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

    
170
oExTags :: OptType
171
oExTags = Option "" ["exclusion-tags"]
172
            (ReqArg (\ f opts -> Ok opts { optExTags = Just $ sepSplit ',' f })
173
             "TAG,...") "Enable instance exclusion based on given tag prefix"
174

    
175
oExecJobs :: OptType
176
oExecJobs = Option "X" ["exec"]
177
             (NoArg (\ opts -> Ok opts { optExecJobs = True}))
178
             "execute the suggested moves via Luxi (only available when using\
179
             \ it for data gathering)"
180

    
181
oIDisk :: OptType
182
oIDisk = Option "" ["disk"]
183
         (ReqArg (\ d opts ->
184
                     let ospec = optISpec opts
185
                         nspec = ospec { rspecDsk = read d }
186
                     in Ok opts { optISpec = nspec }) "DISK")
187
         "disk size for instances"
188

    
189
oIMem :: OptType
190
oIMem = Option "" ["memory"]
191
        (ReqArg (\ m opts ->
192
                     let ospec = optISpec opts
193
                         nspec = ospec { rspecMem = read m }
194
                     in Ok opts { optISpec = nspec }) "MEMORY")
195
        "memory size for instances"
196

    
197
oINodes :: OptType
198
oINodes = Option "" ["req-nodes"]
199
          (ReqArg (\ n opts -> Ok opts { optINodes = read n }) "NODES")
200
          "number of nodes for the new instances (1=plain, 2=mirrored)"
201

    
202
oIVcpus :: OptType
203
oIVcpus = Option "" ["vcpus"]
204
          (ReqArg (\ p opts ->
205
                       let ospec = optISpec opts
206
                           nspec = ospec { rspecCpu = read p }
207
                       in Ok opts { optISpec = nspec }) "NUM")
208
          "number of virtual cpus for instances"
209

    
210
oLuxiSocket :: OptType
211
oLuxiSocket = Option "L" ["luxi"]
212
              (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
213
                       fromMaybe defaultLuxiSocket) "SOCKET")
214
              "collect data via Luxi, optionally using the given SOCKET path"
215

    
216
oMaxCpu :: OptType
217
oMaxCpu = Option "" ["max-cpu"]
218
          (ReqArg (\ n opts -> Ok opts { optMcpu = read n }) "RATIO")
219
          "maximum virtual-to-physical cpu ratio for nodes"
220

    
221
oMaxSolLength :: OptType
222
oMaxSolLength = Option "l" ["max-length"]
223
                (ReqArg (\ i opts -> Ok opts { optMaxLength = read i }) "N")
224
                "cap the solution at this many moves (useful for very\
225
                \ unbalanced clusters)"
226

    
227
oMinDisk :: OptType
228
oMinDisk = Option "" ["min-disk"]
229
           (ReqArg (\ n opts -> Ok opts { optMdsk = read n }) "RATIO")
230
           "minimum free disk space for nodes (between 0 and 1)"
231

    
232
oMinScore :: OptType
233
oMinScore = Option "e" ["min-score"]
234
            (ReqArg (\ e opts -> Ok opts { optMinScore = read e }) "EPSILON")
235
            " mininum score to aim for"
236

    
237
oNoHeaders :: OptType
238
oNoHeaders = Option "" ["no-headers"]
239
             (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
240
             "do not show a header line"
241

    
242
oNodeSim :: OptType
243
oNodeSim = Option "" ["simulate"]
244
            (ReqArg (\ f o -> Ok o { optNodeSim = Just f }) "SPEC")
245
            "simulate an empty cluster, given as 'num_nodes,disk,ram,cpu'"
246

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

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

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

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

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

    
276
oPrintNodes :: OptType
277
oPrintNodes = Option "p" ["print-nodes"]
278
              (OptArg ((\ f opts ->
279
                            let splitted = sepSplit ',' f
280
                            in Ok opts { optShowNodes = Just splitted }) .
281
                       fromMaybe []) "FIELDS")
282
              "print the final node list"
283

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

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

    
294
oShowHelp :: OptType
295
oShowHelp = Option "h" ["help"]
296
            (NoArg (\ opts -> Ok opts { optShowHelp = True}))
297
            "show help"
298

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

    
304
oTieredSpec :: OptType
305
oTieredSpec = Option "" ["tiered-alloc"]
306
             (ReqArg (\ inp opts -> do
307
                          let sp = sepSplit ',' inp
308
                          prs <- mapM (tryRead "tiered specs") sp
309
                          tspec <-
310
                              case prs of
311
                                [dsk, ram, cpu] -> return $ RSpec cpu ram dsk
312
                                _ -> Bad $ "Invalid specification: " ++ inp
313
                          return $ opts { optTieredSpec = Just tspec } )
314
              "TSPEC")
315
             "enable tiered specs allocation, given as 'disk,ram,cpu'"
316

    
317
oVerbose :: OptType
318
oVerbose = Option "v" ["verbose"]
319
           (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
320
           "increase the verbosity level"
321

    
322
-- | Usage info
323
usageHelp :: String -> [OptType] -> String
324
usageHelp progname =
325
    usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
326
               progname Version.version progname)
327

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

    
361
-- | A shell script template for autogenerated scripts.
362
shTemplate :: String
363
shTemplate =
364
    printf "#!/bin/sh\n\n\
365
           \# Auto-generated script for executing cluster rebalancing\n\n\
366
           \# To stop, touch the file /tmp/stop-htools\n\n\
367
           \set -e\n\n\
368
           \check() {\n\
369
           \  if [ -f /tmp/stop-htools ]; then\n\
370
           \    echo 'Stop requested, exiting'\n\
371
           \    exit 0\n\
372
           \  fi\n\
373
           \}\n\n"