Simulation backend: allow multiple node groups
[ganeti-local] / Ganeti / HTools / CLI.hs
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, 2010 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     , oGroup
45     , oIDisk
46     , oIMem
47     , oINodes
48     , oIVcpus
49     , oLuxiSocket
50     , oMaxCpu
51     , oMaxSolLength
52     , oMinDisk
53     , oMinGain
54     , oMinGainLim
55     , oMinScore
56     , oNoHeaders
57     , oNodeSim
58     , oOfflineNode
59     , oOneline
60     , oOutputDir
61     , oPrintCommands
62     , oPrintInsts
63     , oPrintNodes
64     , oQuiet
65     , oRapiMaster
66     , oSaveCluster
67     , oShowHelp
68     , oShowVer
69     , oTieredSpec
70     , oVerbose
71     ) where
72
73 import Data.Maybe (fromMaybe)
74 import qualified Data.Version
75 import Monad
76 import System.Console.GetOpt
77 import System.IO
78 import System.Info
79 import System
80 import Text.Printf (printf)
81
82 import qualified Ganeti.HTools.Version as Version(version)
83 import Ganeti.HTools.Types
84 import Ganeti.HTools.Utils
85
86 -- | The default value for the luxi socket
87 defaultLuxiSocket :: FilePath
88 defaultLuxiSocket = "/var/run/ganeti/socket/ganeti-master"
89
90 -- | Command line options structure.
91 data Options = Options
92     { optDataFile    :: Maybe FilePath -- ^ Path to the cluster data file
93     , optDiskMoves   :: Bool           -- ^ Allow disk moves
94     , optDynuFile    :: Maybe FilePath -- ^ Optional file with dynamic use data
95     , optEvacMode    :: Bool           -- ^ Enable evacuation mode
96     , optExInst      :: [String]       -- ^ Instances to be excluded
97     , optExTags      :: Maybe [String] -- ^ Tags to use for exclusion
98     , optExecJobs    :: Bool           -- ^ Execute the commands via Luxi
99     , optGroup       :: Maybe GroupID  -- ^ The UUID of the group to process
100     , optINodes      :: Int            -- ^ Nodes required for an instance
101     , optISpec       :: RSpec          -- ^ Requested instance specs
102     , optLuxi        :: Maybe FilePath -- ^ Collect data from Luxi
103     , optMaster      :: String         -- ^ Collect data from RAPI
104     , optMaxLength   :: Int            -- ^ Stop after this many steps
105     , optMcpu        :: Double         -- ^ Max cpu ratio for nodes
106     , optMdsk        :: Double         -- ^ Max disk usage ratio for nodes
107     , optMinGain     :: Score          -- ^ Min gain we aim for in a step
108     , optMinGainLim  :: Score          -- ^ Limit below which we apply mingain
109     , optMinScore    :: Score          -- ^ The minimum score we aim for
110     , optNoHeaders   :: Bool           -- ^ Do not show a header line
111     , optNodeSim     :: [String]       -- ^ Cluster simulation mode
112     , optOffline     :: [String]       -- ^ Names of offline nodes
113     , optOneline     :: Bool           -- ^ Switch output to a single line
114     , optOutPath     :: FilePath       -- ^ Path to the output directory
115     , optSaveCluster :: Maybe FilePath -- ^ Save cluster state to this file
116     , optShowCmds    :: Maybe FilePath -- ^ Whether to show the command list
117     , optShowHelp    :: Bool           -- ^ Just show the help
118     , optShowInsts   :: Bool           -- ^ Whether to show the instance map
119     , optShowNodes   :: Maybe [String] -- ^ Whether to show node status
120     , optShowVer     :: Bool           -- ^ Just show the program version
121     , optTieredSpec  :: Maybe RSpec    -- ^ Requested specs for tiered mode
122     , optVerbose     :: Int            -- ^ Verbosity level
123     } deriving Show
124
125 -- | Default values for the command line options.
126 defaultOptions :: Options
127 defaultOptions  = Options
128  { optDataFile    = Nothing
129  , optDiskMoves   = True
130  , optDynuFile    = Nothing
131  , optEvacMode    = False
132  , optExInst      = []
133  , optExTags      = Nothing
134  , optExecJobs    = False
135  , optGroup       = Nothing
136  , optINodes      = 2
137  , optISpec       = RSpec 1 4096 102400
138  , optLuxi        = Nothing
139  , optMaster      = ""
140  , optMaxLength   = -1
141  , optMcpu        = defVcpuRatio
142  , optMdsk        = defReservedDiskRatio
143  , optMinGain     = 1e-2
144  , optMinGainLim  = 1e-1
145  , optMinScore    = 1e-9
146  , optNoHeaders   = False
147  , optNodeSim     = []
148  , optOffline     = []
149  , optOneline     = False
150  , optOutPath     = "."
151  , optSaveCluster = Nothing
152  , optShowCmds    = Nothing
153  , optShowHelp    = False
154  , optShowInsts   = False
155  , optShowNodes   = Nothing
156  , optShowVer     = False
157  , optTieredSpec  = Nothing
158  , optVerbose     = 1
159  }
160
161 -- | Abrreviation for the option type
162 type OptType = OptDescr (Options -> Result Options)
163
164 oDataFile :: OptType
165 oDataFile = Option "t" ["text-data"]
166             (ReqArg (\ f o -> Ok o { optDataFile = Just f }) "FILE")
167             "the cluster data FILE"
168
169 oDiskMoves :: OptType
170 oDiskMoves = Option "" ["no-disk-moves"]
171              (NoArg (\ opts -> Ok opts { optDiskMoves = False}))
172              "disallow disk moves from the list of allowed instance changes,\
173              \ thus allowing only the 'cheap' failover/migrate operations"
174
175 oDynuFile :: OptType
176 oDynuFile = Option "U" ["dynu-file"]
177             (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
178             "Import dynamic utilisation data from the given FILE"
179
180 oEvacMode :: OptType
181 oEvacMode = Option "E" ["evac-mode"]
182             (NoArg (\opts -> Ok opts { optEvacMode = True }))
183             "enable evacuation mode, where the algorithm only moves \
184             \ instances away from offline and drained nodes"
185
186 oExInst :: OptType
187 oExInst = Option "" ["exclude-instances"]
188           (ReqArg (\ f opts -> Ok opts { optExInst = sepSplit ',' f }) "INSTS")
189           "exclude given instances  from any moves"
190
191 oExTags :: OptType
192 oExTags = Option "" ["exclusion-tags"]
193             (ReqArg (\ f opts -> Ok opts { optExTags = Just $ sepSplit ',' f })
194              "TAG,...") "Enable instance exclusion based on given tag prefix"
195
196 oExecJobs :: OptType
197 oExecJobs = Option "X" ["exec"]
198              (NoArg (\ opts -> Ok opts { optExecJobs = True}))
199              "execute the suggested moves via Luxi (only available when using\
200              \ it for data gathering)"
201
202 oGroup :: OptType
203 oGroup = Option "G" ["group"]
204             (ReqArg (\ f o -> Ok o { optGroup = Just f }) "ID")
205             "the ID of the group to balance"
206
207 oIDisk :: OptType
208 oIDisk = Option "" ["disk"]
209          (ReqArg (\ d opts ->
210                      let ospec = optISpec opts
211                          nspec = ospec { rspecDsk = read d }
212                      in Ok opts { optISpec = nspec }) "DISK")
213          "disk size for instances"
214
215 oIMem :: OptType
216 oIMem = Option "" ["memory"]
217         (ReqArg (\ m opts ->
218                      let ospec = optISpec opts
219                          nspec = ospec { rspecMem = read m }
220                      in Ok opts { optISpec = nspec }) "MEMORY")
221         "memory size for instances"
222
223 oINodes :: OptType
224 oINodes = Option "" ["req-nodes"]
225           (ReqArg (\ n opts -> Ok opts { optINodes = read n }) "NODES")
226           "number of nodes for the new instances (1=plain, 2=mirrored)"
227
228 oIVcpus :: OptType
229 oIVcpus = Option "" ["vcpus"]
230           (ReqArg (\ p opts ->
231                        let ospec = optISpec opts
232                            nspec = ospec { rspecCpu = read p }
233                        in Ok opts { optISpec = nspec }) "NUM")
234           "number of virtual cpus for instances"
235
236 oLuxiSocket :: OptType
237 oLuxiSocket = Option "L" ["luxi"]
238               (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
239                        fromMaybe defaultLuxiSocket) "SOCKET")
240               "collect data via Luxi, optionally using the given SOCKET path"
241
242 oMaxCpu :: OptType
243 oMaxCpu = Option "" ["max-cpu"]
244           (ReqArg (\ n opts -> Ok opts { optMcpu = read n }) "RATIO")
245           "maximum virtual-to-physical cpu ratio for nodes (from 1\
246           \ upwards) [64]"
247
248 oMaxSolLength :: OptType
249 oMaxSolLength = Option "l" ["max-length"]
250                 (ReqArg (\ i opts -> Ok opts { optMaxLength = read i }) "N")
251                 "cap the solution at this many moves (useful for very\
252                 \ unbalanced clusters)"
253
254 oMinDisk :: OptType
255 oMinDisk = Option "" ["min-disk"]
256            (ReqArg (\ n opts -> Ok opts { optMdsk = read n }) "RATIO")
257            "minimum free disk space for nodes (between 0 and 1) [0]"
258
259 oMinGain :: OptType
260 oMinGain = Option "g" ["min-gain"]
261             (ReqArg (\ g opts -> Ok opts { optMinGain = read g }) "DELTA")
262             "minimum gain to aim for in a balancing step before giving up"
263
264 oMinGainLim :: OptType
265 oMinGainLim = Option "" ["min-gain-limit"]
266             (ReqArg (\ g opts -> Ok opts { optMinGainLim = read g }) "SCORE")
267             "minimum cluster score for which we start checking the min-gain"
268
269 oMinScore :: OptType
270 oMinScore = Option "e" ["min-score"]
271             (ReqArg (\ e opts -> Ok opts { optMinScore = read e }) "EPSILON")
272             "mininum score to aim for"
273
274 oNoHeaders :: OptType
275 oNoHeaders = Option "" ["no-headers"]
276              (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
277              "do not show a header line"
278
279 oNodeSim :: OptType
280 oNodeSim = Option "" ["simulate"]
281             (ReqArg (\ f o -> Ok o { optNodeSim = f:optNodeSim o }) "SPEC")
282             "simulate an empty cluster, given as 'num_nodes,disk,ram,cpu'"
283
284 oOfflineNode :: OptType
285 oOfflineNode = Option "O" ["offline"]
286                (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
287                "set node as offline"
288
289 oOneline :: OptType
290 oOneline = Option "o" ["oneline"]
291            (NoArg (\ opts -> Ok opts { optOneline = True }))
292            "print the ganeti command list for reaching the solution"
293
294 oOutputDir :: OptType
295 oOutputDir = Option "d" ["output-dir"]
296              (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
297              "directory in which to write output files"
298
299 oPrintCommands :: OptType
300 oPrintCommands = Option "C" ["print-commands"]
301                  (OptArg ((\ f opts -> Ok opts { optShowCmds = Just f }) .
302                           fromMaybe "-")
303                   "FILE")
304                  "print the ganeti command list for reaching the solution,\
305                  \ if an argument is passed then write the commands to a\
306                  \ file named as such"
307
308 oPrintInsts :: OptType
309 oPrintInsts = Option "" ["print-instances"]
310               (NoArg (\ opts -> Ok opts { optShowInsts = True }))
311               "print the final instance map"
312
313 oPrintNodes :: OptType
314 oPrintNodes = Option "p" ["print-nodes"]
315               (OptArg ((\ f opts ->
316                             let (prefix, realf) = case f of
317                                   '+':rest -> (["+"], rest)
318                                   _ -> ([], f)
319                                 splitted = prefix ++ sepSplit ',' realf
320                             in Ok opts { optShowNodes = Just splitted }) .
321                        fromMaybe []) "FIELDS")
322               "print the final node list"
323
324 oQuiet :: OptType
325 oQuiet = Option "q" ["quiet"]
326          (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
327          "decrease the verbosity level"
328
329 oRapiMaster :: OptType
330 oRapiMaster = Option "m" ["master"]
331               (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
332               "collect data via RAPI at the given ADDRESS"
333
334 oSaveCluster :: OptType
335 oSaveCluster = Option "S" ["save"]
336             (ReqArg (\ f opts -> Ok opts { optSaveCluster = Just f }) "FILE")
337             "Save cluster state at the end of the processing to FILE"
338
339 oShowHelp :: OptType
340 oShowHelp = Option "h" ["help"]
341             (NoArg (\ opts -> Ok opts { optShowHelp = True}))
342             "show help"
343
344 oShowVer :: OptType
345 oShowVer = Option "V" ["version"]
346            (NoArg (\ opts -> Ok opts { optShowVer = True}))
347            "show the version of the program"
348
349 oTieredSpec :: OptType
350 oTieredSpec = Option "" ["tiered-alloc"]
351              (ReqArg (\ inp opts -> do
352                           let sp = sepSplit ',' inp
353                           prs <- mapM (tryRead "tiered specs") sp
354                           tspec <-
355                               case prs of
356                                 [dsk, ram, cpu] -> return $ RSpec cpu ram dsk
357                                 _ -> Bad $ "Invalid specification: " ++ inp ++
358                                      ", expected disk,ram,cpu"
359                           return $ opts { optTieredSpec = Just tspec } )
360               "TSPEC")
361              "enable tiered specs allocation, given as 'disk,ram,cpu'"
362
363 oVerbose :: OptType
364 oVerbose = Option "v" ["verbose"]
365            (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
366            "increase the verbosity level"
367
368 -- | Usage info
369 usageHelp :: String -> [OptType] -> String
370 usageHelp progname =
371     usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
372                progname Version.version progname)
373
374 -- | Command line parser, using the 'options' structure.
375 parseOpts :: [String]               -- ^ The command line arguments
376           -> String                 -- ^ The program name
377           -> [OptType]              -- ^ The supported command line options
378           -> IO (Options, [String]) -- ^ The resulting options and leftover
379                                     -- arguments
380 parseOpts argv progname options =
381     case getOpt Permute options argv of
382       (o, n, []) ->
383           do
384             let (pr, args) = (foldM (flip id) defaultOptions o, n)
385             po <- (case pr of
386                      Bad msg -> do
387                        hPutStrLn stderr "Error while parsing command\
388                                         \line arguments:"
389                        hPutStrLn stderr msg
390                        exitWith $ ExitFailure 1
391                      Ok val -> return val)
392             when (optShowHelp po) $ do
393               putStr $ usageHelp progname options
394               exitWith ExitSuccess
395             when (optShowVer po) $ do
396               printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
397                      progname Version.version
398                      compilerName (Data.Version.showVersion compilerVersion)
399                      os arch :: IO ()
400               exitWith ExitSuccess
401             return (po, args)
402       (_, _, errs) -> do
403         hPutStrLn stderr $ "Command line error: "  ++ concat errs
404         hPutStrLn stderr $ usageHelp progname options
405         exitWith $ ExitFailure 2
406
407 -- | A shell script template for autogenerated scripts.
408 shTemplate :: String
409 shTemplate =
410     printf "#!/bin/sh\n\n\
411            \# Auto-generated script for executing cluster rebalancing\n\n\
412            \# To stop, touch the file /tmp/stop-htools\n\n\
413            \set -e\n\n\
414            \check() {\n\
415            \  if [ -f /tmp/stop-htools ]; then\n\
416            \    echo 'Stop requested, exiting'\n\
417            \    exit 0\n\
418            \  fi\n\
419            \}\n\n"