Add an evac mode CLI option
[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 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"