Statistics
| Branch: | Tag: | Revision:

root / Ganeti / HTools / CLI.hs @ 10f396e1

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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