hbal: Implement grouping of moves into jobsets
[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     , oPrintCommands
38     , oOneline
39     , oNoHeaders
40     , oOutputDir
41     , oNodeFile
42     , oInstFile
43     , oNodeSim
44     , oRapiMaster
45     , oLuxiSocket
46     , oMaxSolLength
47     , oVerbose
48     , oQuiet
49     , oOfflineNode
50     , oMinScore
51     , oIMem
52     , oIDisk
53     , oIVcpus
54     , oINodes
55     , oMaxCpu
56     , oMinDisk
57     , oDiskMoves
58     , oShowVer
59     , oShowHelp
60     ) where
61
62 import Data.Maybe (fromMaybe)
63 import qualified Data.Version
64 import Monad
65 import System.Console.GetOpt
66 import System.IO
67 import System.Info
68 import System
69 import Text.Printf (printf)
70
71 import qualified Ganeti.HTools.Version as Version(version)
72 import qualified Ganeti.HTools.Cluster as Cluster
73
74 -- | The default value for the luxi socket
75 defaultLuxiSocket :: FilePath
76 defaultLuxiSocket = "/var/run/ganeti/socket/ganeti-master"
77
78 -- | Command line options structure.
79 data Options = Options
80     { optShowNodes :: Bool           -- ^ Whether to show node status
81     , optShowCmds  :: Maybe FilePath -- ^ Whether to show the command list
82     , optOneline   :: Bool           -- ^ Switch output to a single line
83     , optOutPath   :: FilePath       -- ^ Path to the output directory
84     , optNoHeaders :: Bool           -- ^ Do not show a header line
85     , optNodeFile  :: FilePath       -- ^ Path to the nodes file
86     , optNodeSet   :: Bool           -- ^ The nodes have been set by options
87     , optInstFile  :: FilePath       -- ^ Path to the instances file
88     , optInstSet   :: Bool           -- ^ The insts have been set by options
89     , optNodeSim   :: Maybe String   -- ^ Cluster simulation mode
90     , optMaxLength :: Int            -- ^ Stop after this many steps
91     , optMaster    :: String         -- ^ Collect data from RAPI
92     , optLuxi      :: Maybe FilePath -- ^ Collect data from Luxi
93     , optOffline   :: [String]       -- ^ Names of offline nodes
94     , optIMem      :: Int            -- ^ Instance memory
95     , optIDsk      :: Int            -- ^ Instance disk
96     , optIVCPUs    :: Int            -- ^ Instance VCPUs
97     , optINodes    :: Int            -- ^ Nodes required for an instance
98     , optMinScore  :: Cluster.Score  -- ^ The minimum score we aim for
99     , optMcpu      :: Double         -- ^ Max cpu ratio for nodes
100     , optMdsk      :: Double         -- ^ Max disk usage ratio for nodes
101     , optDiskMoves :: Bool           -- ^ Allow disk moves
102     , optVerbose   :: Int            -- ^ Verbosity level
103     , optShowVer   :: Bool           -- ^ Just show the program version
104     , optShowHelp  :: Bool           -- ^ Just show the help
105     } deriving Show
106
107 -- | Default values for the command line options.
108 defaultOptions :: Options
109 defaultOptions  = Options
110  { optShowNodes = False
111  , optShowCmds  = Nothing
112  , optOneline   = False
113  , optNoHeaders = False
114  , optOutPath   = "."
115  , optNodeFile  = "nodes"
116  , optNodeSet   = False
117  , optInstFile  = "instances"
118  , optInstSet   = False
119  , optNodeSim   = Nothing
120  , optMaxLength = -1
121  , optMaster    = ""
122  , optLuxi      = Nothing
123  , optOffline   = []
124  , optIMem      = 4096
125  , optIDsk      = 102400
126  , optIVCPUs    = 1
127  , optINodes    = 2
128  , optMinScore  = 1e-9
129  , optMcpu      = -1
130  , optMdsk      = -1
131  , optDiskMoves = True
132  , optVerbose   = 1
133  , optShowVer   = False
134  , optShowHelp  = False
135  }
136
137 -- | Abrreviation for the option type
138 type OptType = OptDescr (Options -> Options)
139
140 oPrintNodes :: OptType
141 oPrintNodes = Option "p" ["print-nodes"]
142               (NoArg (\ opts -> opts { optShowNodes = True }))
143               "print the final node list"
144
145 oPrintCommands :: OptType
146 oPrintCommands = Option "C" ["print-commands"]
147                  (OptArg ((\ f opts -> opts { optShowCmds = Just f }) .
148                           fromMaybe "-")
149                   "FILE")
150                  "print the ganeti command list for reaching the solution,\
151                  \ if an argument is passed then write the commands to a\
152                  \ file named as such"
153
154 oOneline :: OptType
155 oOneline = Option "o" ["oneline"]
156            (NoArg (\ opts -> opts { optOneline = True }))
157            "print the ganeti command list for reaching the solution"
158
159 oNoHeaders :: OptType
160 oNoHeaders = Option "" ["no-headers"]
161              (NoArg (\ opts -> opts { optNoHeaders = True }))
162              "do not show a header line"
163
164 oOutputDir :: OptType
165 oOutputDir = Option "d" ["output-dir"]
166              (ReqArg (\ d opts -> opts { optOutPath = d }) "PATH")
167              "directory in which to write output files"
168
169 oNodeFile :: OptType
170 oNodeFile = Option "n" ["nodes"]
171             (ReqArg (\ f o -> o { optNodeFile = f, optNodeSet = True }) "FILE")
172             "the node list FILE"
173
174 oInstFile :: OptType
175 oInstFile = Option "i" ["instances"]
176             (ReqArg (\ f o -> o { optInstFile = f, optInstSet = True }) "FILE")
177             "the instance list FILE"
178
179 oNodeSim :: OptType
180 oNodeSim = Option "" ["simulate"]
181             (ReqArg (\ f o -> o { optNodeSim = Just f }) "SPEC")
182             "simulate an empty cluster, given as 'num_nodes,disk,memory,cpus'"
183
184 oRapiMaster :: OptType
185 oRapiMaster = Option "m" ["master"]
186               (ReqArg (\ m opts -> opts { optMaster = m }) "ADDRESS")
187               "collect data via RAPI at the given ADDRESS"
188
189 oLuxiSocket :: OptType
190 oLuxiSocket = Option "L" ["luxi"]
191               (OptArg ((\ f opts -> opts { optLuxi = Just f }) .
192                        fromMaybe defaultLuxiSocket) "SOCKET")
193               "collect data via Luxi, optionally using the given SOCKET path"
194
195 oVerbose :: OptType
196 oVerbose = Option "v" ["verbose"]
197            (NoArg (\ opts -> opts { optVerbose = optVerbose opts + 1 }))
198            "increase the verbosity level"
199
200 oQuiet :: OptType
201 oQuiet = Option "q" ["quiet"]
202          (NoArg (\ opts -> opts { optVerbose = optVerbose opts - 1 }))
203          "decrease the verbosity level"
204
205 oOfflineNode :: OptType
206 oOfflineNode = Option "O" ["offline"]
207                (ReqArg (\ n o -> o { optOffline = n:optOffline o }) "NODE")
208                "set node as offline"
209
210 oMaxSolLength :: OptType
211 oMaxSolLength = Option "l" ["max-length"]
212                 (ReqArg (\ i opts -> opts { optMaxLength =  read i::Int }) "N")
213                 "cap the solution at this many moves (useful for very\
214                 \ unbalanced clusters)"
215
216 oMinScore :: OptType
217 oMinScore = Option "e" ["min-score"]
218             (ReqArg (\ e opts -> opts { optMinScore = read e }) "EPSILON")
219             " mininum score to aim for"
220
221 oIMem :: OptType
222 oIMem = Option "" ["memory"]
223         (ReqArg (\ m opts -> opts { optIMem = read m }) "MEMORY")
224         "memory size for instances"
225
226 oIDisk :: OptType
227 oIDisk = Option "" ["disk"]
228          (ReqArg (\ d opts -> opts { optIDsk = read d }) "DISK")
229          "disk size for instances"
230
231 oIVcpus :: OptType
232 oIVcpus = Option "" ["vcpus"]
233           (ReqArg (\ p opts -> opts { optIVCPUs = read p }) "NUM")
234           "number of virtual cpus for instances"
235
236 oINodes :: OptType
237 oINodes = Option "" ["req-nodes"]
238           (ReqArg (\ n opts -> opts { optINodes = read n }) "NODES")
239           "number of nodes for the new instances (1=plain, 2=mirrored)"
240
241 oMaxCpu :: OptType
242 oMaxCpu = Option "" ["max-cpu"]
243           (ReqArg (\ n opts -> opts { optMcpu = read n }) "RATIO")
244           "maximum virtual-to-physical cpu ratio for nodes"
245
246 oMinDisk :: OptType
247 oMinDisk = Option "" ["min-disk"]
248            (ReqArg (\ n opts -> opts { optMdsk = read n }) "RATIO")
249            "minimum free disk space for nodes (between 0 and 1)"
250
251 oDiskMoves :: OptType
252 oDiskMoves = Option "" ["no-disk-moves"]
253              (NoArg (\ opts -> opts { optDiskMoves = False}))
254              "disallow disk moves from the list of allowed instance changes,\
255              \ thus allowing only the 'cheap' failover/migrate operations"
256
257 oShowVer :: OptType
258 oShowVer = Option "V" ["version"]
259            (NoArg (\ opts -> opts { optShowVer = True}))
260            "show the version of the program"
261
262 oShowHelp :: OptType
263 oShowHelp = Option "h" ["help"]
264             (NoArg (\ opts -> opts { optShowHelp = True}))
265             "show help"
266
267 -- | Usage info
268 usageHelp :: String -> [OptType] -> String
269 usageHelp progname =
270     usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
271                progname Version.version progname)
272
273 -- | Command line parser, using the 'options' structure.
274 parseOpts :: [String]               -- ^ The command line arguments
275           -> String                 -- ^ The program name
276           -> [OptType]              -- ^ The supported command line options
277           -> IO (Options, [String]) -- ^ The resulting options and leftover
278                                     -- arguments
279 parseOpts argv progname options =
280     case getOpt Permute options argv of
281       (o, n, []) ->
282           do
283             let resu@(po, _) = (foldl (flip id) defaultOptions o, n)
284             when (optShowHelp po) $ do
285               putStr $ usageHelp progname options
286               exitWith ExitSuccess
287             when (optShowVer po) $ do
288               printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
289                      progname Version.version
290                      compilerName (Data.Version.showVersion compilerVersion)
291                      os arch
292               exitWith ExitSuccess
293             return resu
294       (_, _, errs) -> do
295         hPutStrLn stderr $ "Command line error: "  ++ concat errs
296         hPutStrLn stderr $ usageHelp progname options
297         exitWith $ ExitFailure 2
298
299 -- | A shell script template for autogenerated scripts.
300 shTemplate :: String
301 shTemplate =
302     printf "#!/bin/sh\n\n\
303            \# Auto-generated script for executing cluster rebalancing\n\n\
304            \# To stop, touch the file /tmp/stop-htools\n\n\
305            \set -e\n\n\
306            \check() {\n\
307            \  if [ -f /tmp/stop-htools ]; then\n\
308            \    echo 'Stop requested, exiting'\n\
309            \    exit 0\n\
310            \  fi\n\
311            \}\n\n"