Statistics
| Branch: | Tag: | Revision:

root / Ganeti / HTools / CLI.hs @ df18fdfe

History | View | Annotate | Download (13 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
    , oExTags
40
    , oExecJobs
41
    , oIDisk
42
    , oIMem
43
    , oINodes
44
    , oIVcpus
45
    , oLuxiSocket
46
    , oMaxCpu
47
    , oMaxSolLength
48
    , oMinDisk
49
    , oMinScore
50
    , oNoHeaders
51
    , oNodeSim
52
    , oOfflineNode
53
    , oOneline
54
    , oOutputDir
55
    , oPrintCommands
56
    , oPrintInsts
57
    , oPrintNodes
58
    , oQuiet
59
    , oRapiMaster
60
    , oShowHelp
61
    , oShowVer
62
    , oTieredSpec
63
    , oVerbose
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
    { optDataFile    :: Maybe FilePath -- ^ Path to the cluster data file
86
    , optDiskMoves   :: Bool           -- ^ Allow disk moves
87
    , optDynuFile    :: Maybe FilePath -- ^ Optional file with dynamic use data
88
    , optExTags      :: Maybe [String] -- ^ Tags to use for exclusion
89
    , optExecJobs    :: Bool           -- ^ Execute the commands via Luxi
90
    , optINodes      :: Int            -- ^ Nodes required for an instance
91
    , optISpec       :: RSpec          -- ^ Requested instance specs
92
    , optLuxi        :: Maybe FilePath -- ^ Collect data from Luxi
93
    , optMaster      :: String         -- ^ Collect data from RAPI
94
    , optMaxLength   :: Int            -- ^ Stop after this many steps
95
    , optMcpu        :: Double         -- ^ Max cpu ratio for nodes
96
    , optMdsk        :: Double         -- ^ Max disk usage ratio for nodes
97
    , optMinScore    :: Score          -- ^ The minimum score we aim for
98
    , optNoHeaders   :: Bool           -- ^ Do not show a header line
99
    , optNodeSim     :: Maybe String   -- ^ Cluster simulation mode
100
    , optOffline     :: [String]       -- ^ Names of offline nodes
101
    , optOneline     :: Bool           -- ^ Switch output to a single line
102
    , optOutPath     :: FilePath       -- ^ Path to the output directory
103
    , optShowCmds    :: Maybe FilePath -- ^ Whether to show the command list
104
    , optShowHelp    :: Bool           -- ^ Just show the help
105
    , optShowInsts   :: Bool           -- ^ Whether to show the instance map
106
    , optShowNodes   :: Maybe [String] -- ^ Whether to show node status
107
    , optShowVer     :: Bool           -- ^ Just show the program version
108
    , optTieredSpec  :: Maybe RSpec    -- ^ Requested specs for tiered mode
109
    , optVerbose     :: Int            -- ^ Verbosity level
110
    } deriving Show
111

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

    
142
-- | Abrreviation for the option type
143
type OptType = OptDescr (Options -> Result Options)
144

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

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

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

    
161
oExTags :: OptType
162
oExTags = Option "" ["exclusion-tags"]
163
            (ReqArg (\ f opts -> Ok opts { optExTags = Just $ sepSplit ',' f })
164
             "TAG,...") "Enable instance exclusion based on given tag prefix"
165

    
166
oExecJobs :: OptType
167
oExecJobs = Option "X" ["exec"]
168
             (NoArg (\ opts -> Ok opts { optExecJobs = True}))
169
             "execute the suggested moves via Luxi (only available when using\
170
             \ it for data gathering)"
171

    
172
oIDisk :: OptType
173
oIDisk = Option "" ["disk"]
174
         (ReqArg (\ d opts ->
175
                     let ospec = optISpec opts
176
                         nspec = ospec { rspecDsk = read d }
177
                     in Ok opts { optISpec = nspec }) "DISK")
178
         "disk size for instances"
179

    
180
oIMem :: OptType
181
oIMem = Option "" ["memory"]
182
        (ReqArg (\ m opts ->
183
                     let ospec = optISpec opts
184
                         nspec = ospec { rspecMem = read m }
185
                     in Ok opts { optISpec = nspec }) "MEMORY")
186
        "memory size for instances"
187

    
188
oINodes :: OptType
189
oINodes = Option "" ["req-nodes"]
190
          (ReqArg (\ n opts -> Ok opts { optINodes = read n }) "NODES")
191
          "number of nodes for the new instances (1=plain, 2=mirrored)"
192

    
193
oIVcpus :: OptType
194
oIVcpus = Option "" ["vcpus"]
195
          (ReqArg (\ p opts ->
196
                       let ospec = optISpec opts
197
                           nspec = ospec { rspecCpu = read p }
198
                       in Ok opts { optISpec = nspec }) "NUM")
199
          "number of virtual cpus for instances"
200

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

    
207
oMaxCpu :: OptType
208
oMaxCpu = Option "" ["max-cpu"]
209
          (ReqArg (\ n opts -> Ok opts { optMcpu = read n }) "RATIO")
210
          "maximum virtual-to-physical cpu ratio for nodes"
211

    
212
oMaxSolLength :: OptType
213
oMaxSolLength = Option "l" ["max-length"]
214
                (ReqArg (\ i opts -> Ok opts { optMaxLength = read i }) "N")
215
                "cap the solution at this many moves (useful for very\
216
                \ unbalanced clusters)"
217

    
218
oMinDisk :: OptType
219
oMinDisk = Option "" ["min-disk"]
220
           (ReqArg (\ n opts -> Ok opts { optMdsk = read n }) "RATIO")
221
           "minimum free disk space for nodes (between 0 and 1)"
222

    
223
oMinScore :: OptType
224
oMinScore = Option "e" ["min-score"]
225
            (ReqArg (\ e opts -> Ok opts { optMinScore = read e }) "EPSILON")
226
            " mininum score to aim for"
227

    
228
oNoHeaders :: OptType
229
oNoHeaders = Option "" ["no-headers"]
230
             (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
231
             "do not show a header line"
232

    
233
oNodeSim :: OptType
234
oNodeSim = Option "" ["simulate"]
235
            (ReqArg (\ f o -> Ok o { optNodeSim = Just f }) "SPEC")
236
            "simulate an empty cluster, given as 'num_nodes,disk,ram,cpu'"
237

    
238
oOfflineNode :: OptType
239
oOfflineNode = Option "O" ["offline"]
240
               (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
241
               "set node as offline"
242

    
243
oOneline :: OptType
244
oOneline = Option "o" ["oneline"]
245
           (NoArg (\ opts -> Ok opts { optOneline = True }))
246
           "print the ganeti command list for reaching the solution"
247

    
248
oOutputDir :: OptType
249
oOutputDir = Option "d" ["output-dir"]
250
             (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
251
             "directory in which to write output files"
252

    
253
oPrintCommands :: OptType
254
oPrintCommands = Option "C" ["print-commands"]
255
                 (OptArg ((\ f opts -> Ok opts { optShowCmds = Just f }) .
256
                          fromMaybe "-")
257
                  "FILE")
258
                 "print the ganeti command list for reaching the solution,\
259
                 \ if an argument is passed then write the commands to a\
260
                 \ file named as such"
261

    
262
oPrintInsts :: OptType
263
oPrintInsts = Option "" ["print-instances"]
264
              (NoArg (\ opts -> Ok opts { optShowInsts = True }))
265
              "print the final instance map"
266

    
267
oPrintNodes :: OptType
268
oPrintNodes = Option "p" ["print-nodes"]
269
              (OptArg ((\ f opts ->
270
                            let splitted = sepSplit ',' f
271
                            in Ok opts { optShowNodes = Just splitted }) .
272
                       fromMaybe []) "FIELDS")
273
              "print the final node list"
274

    
275
oQuiet :: OptType
276
oQuiet = Option "q" ["quiet"]
277
         (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
278
         "decrease the verbosity level"
279

    
280
oRapiMaster :: OptType
281
oRapiMaster = Option "m" ["master"]
282
              (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
283
              "collect data via RAPI at the given ADDRESS"
284

    
285
oShowHelp :: OptType
286
oShowHelp = Option "h" ["help"]
287
            (NoArg (\ opts -> Ok opts { optShowHelp = True}))
288
            "show help"
289

    
290
oShowVer :: OptType
291
oShowVer = Option "V" ["version"]
292
           (NoArg (\ opts -> Ok opts { optShowVer = True}))
293
           "show the version of the program"
294

    
295
oTieredSpec :: OptType
296
oTieredSpec = Option "" ["tiered-alloc"]
297
             (ReqArg (\ inp opts -> do
298
                          let sp = sepSplit ',' inp
299
                          prs <- mapM (tryRead "tiered specs") sp
300
                          tspec <-
301
                              case prs of
302
                                [dsk, ram, cpu] -> return $ RSpec cpu ram dsk
303
                                _ -> Bad $ "Invalid specification: " ++ inp
304
                          return $ opts { optTieredSpec = Just tspec } )
305
              "TSPEC")
306
             "enable tiered specs allocation, given as 'disk,ram,cpu'"
307

    
308
oVerbose :: OptType
309
oVerbose = Option "v" ["verbose"]
310
           (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
311
           "increase the verbosity level"
312

    
313
-- | Usage info
314
usageHelp :: String -> [OptType] -> String
315
usageHelp progname =
316
    usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
317
               progname Version.version progname)
318

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

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