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