Switch the text file format to single-file
[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     , oDataFile
43     , oNodeSim
44     , oRapiMaster
45     , oLuxiSocket
46     , oExecJobs
47     , oMaxSolLength
48     , oVerbose
49     , oQuiet
50     , oOfflineNode
51     , oMinScore
52     , oIMem
53     , oIDisk
54     , oIVcpus
55     , oINodes
56     , oMaxCpu
57     , oMinDisk
58     , oDiskMoves
59     , oDynuFile
60     , oTieredSpec
61     , oExTags
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   :: Maybe [String] -- ^ 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     , optDataFile    :: Maybe FilePath -- ^ Path to the cluster data file
92     , optNodeSim     :: Maybe String   -- ^ Cluster simulation mode
93     , optMaxLength   :: Int            -- ^ Stop after this many steps
94     , optMaster      :: String         -- ^ Collect data from RAPI
95     , optLuxi        :: Maybe FilePath -- ^ Collect data from Luxi
96     , optExecJobs    :: Bool           -- ^ Execute the commands via Luxi
97     , optOffline     :: [String]       -- ^ Names of offline nodes
98     , optINodes      :: Int            -- ^ Nodes required for an instance
99     , optISpec       :: RSpec          -- ^ Requested instance specs
100     , optTieredSpec  :: Maybe RSpec    -- ^ Requested specs for tiered mode
101     , optMinScore    :: Score          -- ^ The minimum score we aim for
102     , optMcpu        :: Double         -- ^ Max cpu ratio for nodes
103     , optMdsk        :: Double         -- ^ Max disk usage ratio for nodes
104     , optDiskMoves   :: Bool           -- ^ Allow disk moves
105     , optDynuFile    :: Maybe FilePath -- ^ Optional file with dynamic use data
106     , optExTags      :: Maybe [String] -- ^ Tags to use for exclusion
107     , optVerbose     :: Int            -- ^ Verbosity level
108     , optShowVer     :: Bool           -- ^ Just show the program version
109     , optShowHelp    :: Bool           -- ^ Just show the help
110     } deriving Show
111
112 -- | Default values for the command line options.
113 defaultOptions :: Options
114 defaultOptions  = Options
115  { optShowNodes   = Nothing
116  , optShowInsts   = False
117  , optShowCmds    = Nothing
118  , optOneline     = False
119  , optNoHeaders   = False
120  , optOutPath     = "."
121  , optDataFile    = Nothing
122  , optNodeSim     = Nothing
123  , optMaxLength   = -1
124  , optMaster      = ""
125  , optLuxi        = Nothing
126  , optExecJobs    = False
127  , optOffline     = []
128  , optINodes      = 2
129  , optISpec       = RSpec 1 4096 102400
130  , optTieredSpec  = Nothing
131  , optMinScore    = 1e-9
132  , optMcpu        = -1
133  , optMdsk        = -1
134  , optDiskMoves   = True
135  , optDynuFile    = Nothing
136  , optExTags      = Nothing
137  , optVerbose     = 1
138  , optShowVer     = False
139  , optShowHelp    = False
140  }
141
142 -- | Abrreviation for the option type
143 type OptType = OptDescr (Options -> Result Options)
144
145 oPrintNodes :: OptType
146 oPrintNodes = Option "p" ["print-nodes"]
147               (OptArg ((\ f opts ->
148                             let splitted = sepSplit ',' f
149                             in Ok opts { optShowNodes = Just splitted }) .
150                        fromMaybe []) "FIELDS")
151               "print the final node list"
152
153 oPrintInsts :: OptType
154 oPrintInsts = Option "" ["print-instances"]
155               (NoArg (\ opts -> Ok opts { optShowInsts = True }))
156               "print the final instance map"
157
158 oPrintCommands :: OptType
159 oPrintCommands = Option "C" ["print-commands"]
160                  (OptArg ((\ f opts -> Ok opts { optShowCmds = Just f }) .
161                           fromMaybe "-")
162                   "FILE")
163                  "print the ganeti command list for reaching the solution,\
164                  \ if an argument is passed then write the commands to a\
165                  \ file named as such"
166
167 oOneline :: OptType
168 oOneline = Option "o" ["oneline"]
169            (NoArg (\ opts -> Ok opts { optOneline = True }))
170            "print the ganeti command list for reaching the solution"
171
172 oNoHeaders :: OptType
173 oNoHeaders = Option "" ["no-headers"]
174              (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
175              "do not show a header line"
176
177 oOutputDir :: OptType
178 oOutputDir = Option "d" ["output-dir"]
179              (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
180              "directory in which to write output files"
181
182 oDataFile :: OptType
183 oDataFile = Option "t" ["text-data"]
184             (ReqArg (\ f o -> Ok o { optDataFile = Just f }) "FILE")
185             "the cluster data FILE"
186
187 oNodeSim :: OptType
188 oNodeSim = Option "" ["simulate"]
189             (ReqArg (\ f o -> Ok o { optNodeSim = Just f }) "SPEC")
190             "simulate an empty cluster, given as 'num_nodes,disk,ram,cpu'"
191
192 oRapiMaster :: OptType
193 oRapiMaster = Option "m" ["master"]
194               (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
195               "collect data via RAPI at the given ADDRESS"
196
197 oLuxiSocket :: OptType
198 oLuxiSocket = Option "L" ["luxi"]
199               (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
200                        fromMaybe defaultLuxiSocket) "SOCKET")
201               "collect data via Luxi, optionally using the given SOCKET path"
202
203 oExecJobs :: OptType
204 oExecJobs = Option "X" ["exec"]
205              (NoArg (\ opts -> Ok opts { optExecJobs = True}))
206              "execute the suggested moves via Luxi (only available when using\
207              \ it for data gathering"
208
209 oVerbose :: OptType
210 oVerbose = Option "v" ["verbose"]
211            (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
212            "increase the verbosity level"
213
214 oQuiet :: OptType
215 oQuiet = Option "q" ["quiet"]
216          (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
217          "decrease the verbosity level"
218
219 oOfflineNode :: OptType
220 oOfflineNode = Option "O" ["offline"]
221                (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
222                "set node as offline"
223
224 oMaxSolLength :: OptType
225 oMaxSolLength = Option "l" ["max-length"]
226                 (ReqArg (\ i opts -> Ok opts { optMaxLength = read i }) "N")
227                 "cap the solution at this many moves (useful for very\
228                 \ unbalanced clusters)"
229
230 oMinScore :: OptType
231 oMinScore = Option "e" ["min-score"]
232             (ReqArg (\ e opts -> Ok opts { optMinScore = read e }) "EPSILON")
233             " mininum score to aim for"
234
235 oIMem :: OptType
236 oIMem = Option "" ["memory"]
237         (ReqArg (\ m opts ->
238                      let ospec = optISpec opts
239                          nspec = ospec { rspecMem = read m }
240                      in Ok opts { optISpec = nspec }) "MEMORY")
241         "memory size for instances"
242
243 oIDisk :: OptType
244 oIDisk = Option "" ["disk"]
245          (ReqArg (\ d opts ->
246                      let ospec = optISpec opts
247                          nspec = ospec { rspecDsk = read d }
248                      in Ok opts { optISpec = nspec }) "DISK")
249          "disk size for instances"
250
251 oIVcpus :: OptType
252 oIVcpus = Option "" ["vcpus"]
253           (ReqArg (\ p opts ->
254                        let ospec = optISpec opts
255                            nspec = ospec { rspecCpu = read p }
256                        in Ok opts { optISpec = nspec }) "NUM")
257           "number of virtual cpus for instances"
258
259 oINodes :: OptType
260 oINodes = Option "" ["req-nodes"]
261           (ReqArg (\ n opts -> Ok opts { optINodes = read n }) "NODES")
262           "number of nodes for the new instances (1=plain, 2=mirrored)"
263
264 oMaxCpu :: OptType
265 oMaxCpu = Option "" ["max-cpu"]
266           (ReqArg (\ n opts -> Ok opts { optMcpu = read n }) "RATIO")
267           "maximum virtual-to-physical cpu ratio for nodes"
268
269 oMinDisk :: OptType
270 oMinDisk = Option "" ["min-disk"]
271            (ReqArg (\ n opts -> Ok opts { optMdsk = read n }) "RATIO")
272            "minimum free disk space for nodes (between 0 and 1)"
273
274 oDiskMoves :: OptType
275 oDiskMoves = Option "" ["no-disk-moves"]
276              (NoArg (\ opts -> Ok opts { optDiskMoves = False}))
277              "disallow disk moves from the list of allowed instance changes,\
278              \ thus allowing only the 'cheap' failover/migrate operations"
279
280 oDynuFile :: OptType
281 oDynuFile = Option "U" ["dynu-file"]
282             (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
283             "Import dynamic utilisation data from the given FILE"
284
285 oExTags :: OptType
286 oExTags = Option "" ["exclusion-tags"]
287             (ReqArg (\ f opts -> Ok opts { optExTags = Just $ sepSplit ',' f })
288              "TAG,...") "Enable instance exclusion based on given tag prefix"
289
290 oTieredSpec :: OptType
291 oTieredSpec = Option "" ["tiered-alloc"]
292              (ReqArg (\ inp opts -> do
293                           let sp = sepSplit ',' inp
294                           prs <- mapM (tryRead "tiered specs") sp
295                           tspec <-
296                               case prs of
297                                 [dsk, ram, cpu] -> return $ RSpec cpu ram dsk
298                                 _ -> Bad $ "Invalid specification: " ++ inp
299                           return $ opts { optTieredSpec = Just tspec } )
300               "TSPEC")
301              "enable tiered specs allocation, given as 'disk,ram,cpu'"
302
303 oShowVer :: OptType
304 oShowVer = Option "V" ["version"]
305            (NoArg (\ opts -> Ok opts { optShowVer = True}))
306            "show the version of the program"
307
308 oShowHelp :: OptType
309 oShowHelp = Option "h" ["help"]
310             (NoArg (\ opts -> Ok opts { optShowHelp = True}))
311             "show help"
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"