89432fc390a27772fb62b13a50756f200ad43688
[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     , 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,ram,cpu'"
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                                 [dsk, ram, cpu] -> return $ RSpec cpu ram dsk
301                                 _ -> Bad $ "Invalid specification: " ++ inp
302                           return $ opts { optTieredSpec = Just tspec } )
303               "TSPEC")
304              "enable tiered specs allocation, given as 'disk,ram,cpu'"
305
306 oShowVer :: OptType
307 oShowVer = Option "V" ["version"]
308            (NoArg (\ opts -> Ok opts { optShowVer = True}))
309            "show the version of the program"
310
311 oShowHelp :: OptType
312 oShowHelp = Option "h" ["help"]
313             (NoArg (\ opts -> Ok opts { optShowHelp = True}))
314             "show help"
315
316 -- | Usage info
317 usageHelp :: String -> [OptType] -> String
318 usageHelp progname =
319     usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
320                progname Version.version progname)
321
322 -- | Command line parser, using the 'options' structure.
323 parseOpts :: [String]               -- ^ The command line arguments
324           -> String                 -- ^ The program name
325           -> [OptType]              -- ^ The supported command line options
326           -> IO (Options, [String]) -- ^ The resulting options and leftover
327                                     -- arguments
328 parseOpts argv progname options =
329     case getOpt Permute options argv of
330       (o, n, []) ->
331           do
332             let (pr, args) = (foldM (flip id) defaultOptions o, n)
333             po <- (case pr of
334                      Bad msg -> do
335                        hPutStrLn stderr "Error while parsing command\
336                                         \line arguments:"
337                        hPutStrLn stderr msg
338                        exitWith $ ExitFailure 1
339                      Ok val -> return val)
340             when (optShowHelp po) $ do
341               putStr $ usageHelp progname options
342               exitWith ExitSuccess
343             when (optShowVer po) $ do
344               printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
345                      progname Version.version
346                      compilerName (Data.Version.showVersion compilerVersion)
347                      os arch
348               exitWith ExitSuccess
349             return (po, args)
350       (_, _, errs) -> do
351         hPutStrLn stderr $ "Command line error: "  ++ concat errs
352         hPutStrLn stderr $ usageHelp progname options
353         exitWith $ ExitFailure 2
354
355 -- | A shell script template for autogenerated scripts.
356 shTemplate :: String
357 shTemplate =
358     printf "#!/bin/sh\n\n\
359            \# Auto-generated script for executing cluster rebalancing\n\n\
360            \# To stop, touch the file /tmp/stop-htools\n\n\
361            \set -e\n\n\
362            \check() {\n\
363            \  if [ -f /tmp/stop-htools ]; then\n\
364            \    echo 'Stop requested, exiting'\n\
365            \    exit 0\n\
366            \  fi\n\
367            \}\n\n"