Statistics
| Branch: | Tag: | Revision:

root / Ganeti / HTools / CLI.hs @ 1f9066c0

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

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

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

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

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

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

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

    
149
oPrintNodes :: OptType
150
oPrintNodes = Option "p" ["print-nodes"]
151
              (NoArg (\ opts -> Ok opts { optShowNodes = True }))
152
              "print the final node list"
153

    
154
oPrintInsts :: OptType
155
oPrintInsts = Option "" ["print-instances"]
156
              (NoArg (\ opts -> Ok opts { optShowInsts = True }))
157
              "print the final instance map"
158

    
159
oPrintCommands :: OptType
160
oPrintCommands = Option "C" ["print-commands"]
161
                 (OptArg ((\ f opts -> Ok opts { optShowCmds = Just f }) .
162
                          fromMaybe "-")
163
                  "FILE")
164
                 "print the ganeti command list for reaching the solution,\
165
                 \ if an argument is passed then write the commands to a\
166
                 \ file named as such"
167

    
168
oOneline :: OptType
169
oOneline = Option "o" ["oneline"]
170
           (NoArg (\ opts -> Ok opts { optOneline = True }))
171
           "print the ganeti command list for reaching the solution"
172

    
173
oNoHeaders :: OptType
174
oNoHeaders = Option "" ["no-headers"]
175
             (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
176
             "do not show a header line"
177

    
178
oOutputDir :: OptType
179
oOutputDir = Option "d" ["output-dir"]
180
             (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
181
             "directory in which to write output files"
182

    
183
oNodeFile :: OptType
184
oNodeFile = Option "n" ["nodes"]
185
            (ReqArg (\ f o -> Ok o { optNodeFile = f,
186
                                     optNodeSet = True }) "FILE")
187
            "the node list FILE"
188

    
189
oInstFile :: OptType
190
oInstFile = Option "i" ["instances"]
191
            (ReqArg (\ f o -> Ok o { optInstFile = f,
192
                                     optInstSet = True }) "FILE")
193
            "the instance list FILE"
194

    
195
oNodeSim :: OptType
196
oNodeSim = Option "" ["simulate"]
197
            (ReqArg (\ f o -> Ok o { optNodeSim = Just f }) "SPEC")
198
            "simulate an empty cluster, given as 'num_nodes,disk,memory,cpus'"
199

    
200
oRapiMaster :: OptType
201
oRapiMaster = Option "m" ["master"]
202
              (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
203
              "collect data via RAPI at the given ADDRESS"
204

    
205
oLuxiSocket :: OptType
206
oLuxiSocket = Option "L" ["luxi"]
207
              (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
208
                       fromMaybe defaultLuxiSocket) "SOCKET")
209
              "collect data via Luxi, optionally using the given SOCKET path"
210

    
211
oExecJobs :: OptType
212
oExecJobs = Option "X" ["exec"]
213
             (NoArg (\ opts -> Ok opts { optExecJobs = True}))
214
             "execute the suggested moves via Luxi (only available when using\
215
             \ it for data gathering"
216

    
217
oVerbose :: OptType
218
oVerbose = Option "v" ["verbose"]
219
           (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
220
           "increase the verbosity level"
221

    
222
oQuiet :: OptType
223
oQuiet = Option "q" ["quiet"]
224
         (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
225
         "decrease the verbosity level"
226

    
227
oOfflineNode :: OptType
228
oOfflineNode = Option "O" ["offline"]
229
               (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
230
               "set node as offline"
231

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

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

    
243
oIMem :: OptType
244
oIMem = Option "" ["memory"]
245
        (ReqArg (\ m opts ->
246
                     let ospec = optISpec opts
247
                         nspec = ospec { rspecMem = read m }
248
                     in Ok opts { optISpec = nspec }) "MEMORY")
249
        "memory size for instances"
250

    
251
oIDisk :: OptType
252
oIDisk = Option "" ["disk"]
253
         (ReqArg (\ d opts ->
254
                     let ospec = optISpec opts
255
                         nspec = ospec { rspecDsk = read d }
256
                     in Ok opts { optISpec = nspec }) "DISK")
257
         "disk size for instances"
258

    
259
oIVcpus :: OptType
260
oIVcpus = Option "" ["vcpus"]
261
          (ReqArg (\ p opts ->
262
                       let ospec = optISpec opts
263
                           nspec = ospec { rspecCpu = read p }
264
                       in Ok opts { optISpec = nspec }) "NUM")
265
          "number of virtual cpus for instances"
266

    
267
oINodes :: OptType
268
oINodes = Option "" ["req-nodes"]
269
          (ReqArg (\ n opts -> Ok opts { optINodes = read n }) "NODES")
270
          "number of nodes for the new instances (1=plain, 2=mirrored)"
271

    
272
oMaxCpu :: OptType
273
oMaxCpu = Option "" ["max-cpu"]
274
          (ReqArg (\ n opts -> Ok opts { optMcpu = read n }) "RATIO")
275
          "maximum virtual-to-physical cpu ratio for nodes"
276

    
277
oMinDisk :: OptType
278
oMinDisk = Option "" ["min-disk"]
279
           (ReqArg (\ n opts -> Ok opts { optMdsk = read n }) "RATIO")
280
           "minimum free disk space for nodes (between 0 and 1)"
281

    
282
oDiskMoves :: OptType
283
oDiskMoves = Option "" ["no-disk-moves"]
284
             (NoArg (\ opts -> Ok opts { optDiskMoves = False}))
285
             "disallow disk moves from the list of allowed instance changes,\
286
             \ thus allowing only the 'cheap' failover/migrate operations"
287

    
288
oDynuFile :: OptType
289
oDynuFile = Option "U" ["dynu-file"]
290
            (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
291
            "Import dynamic utilisation data from the given FILE"
292

    
293
oTieredSpec :: OptType
294
oTieredSpec = Option "" ["tiered-alloc"]
295
             (ReqArg (\ inp opts -> do
296
                          let sp = sepSplit ',' inp
297
                          prs <- mapM (tryRead "tiered specs") sp
298
                          tspec <-
299
                              case prs of
300
                                [cpu, ram, dsk] -> return $ RSpec cpu ram dsk
301
                                _ -> Bad $ "Invalid specification: " ++ inp
302
                          return $ opts { optTieredSpec = Just tspec } )
303
              "TSPEC")
304
             "enable tiered specs allocation, where we decrease the instance\
305
             \ spec on failure to allocate and restart the allocation process"
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
oShowHelp :: OptType
313
oShowHelp = Option "h" ["help"]
314
            (NoArg (\ opts -> Ok opts { optShowHelp = True}))
315
            "show help"
316

    
317
-- | Usage info
318
usageHelp :: String -> [OptType] -> String
319
usageHelp progname =
320
    usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
321
               progname Version.version progname)
322

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

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