1 {-| Implementation of command-line functions.
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.
11 Copyright (C) 2009 Google Inc.
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.
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.
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
30 module Ganeti.HTools.CLI
64 import Control.Exception
65 import Data.Maybe (isJust, fromJust, fromMaybe)
66 import qualified Data.Version
68 import System.Console.GetOpt
69 import System.Posix.Env
73 import Text.Printf (printf, hPrintf)
75 import qualified Ganeti.HTools.Version as Version(version)
76 import qualified Ganeti.HTools.Luxi as Luxi
77 import qualified Ganeti.HTools.Rapi as Rapi
78 import qualified Ganeti.HTools.Simu as Simu
79 import qualified Ganeti.HTools.Text as Text
80 import qualified Ganeti.HTools.Loader as Loader
81 import qualified Ganeti.HTools.Instance as Instance
82 import qualified Ganeti.HTools.Node as Node
83 import qualified Ganeti.HTools.Cluster as Cluster
85 import Ganeti.HTools.Types
87 -- | The default value for the luxi socket
88 defaultLuxiSocket :: FilePath
89 defaultLuxiSocket = "/var/run/ganeti/socket/ganeti-master"
91 -- | Command line options structure.
92 data Options = Options
93 { optShowNodes :: Bool -- ^ Whether to show node status
94 , optShowCmds :: Maybe FilePath -- ^ Whether to show the command list
95 , optOneline :: Bool -- ^ Switch output to a single line
96 , optOutPath :: FilePath -- ^ Path to the output directory
97 , optNoHeaders :: Bool -- ^ Do not show a header line
98 , optNodeFile :: FilePath -- ^ Path to the nodes file
99 , optNodeSet :: Bool -- ^ The nodes have been set by options
100 , optInstFile :: FilePath -- ^ Path to the instances file
101 , optInstSet :: Bool -- ^ The insts have been set by options
102 , optNodeSim :: Maybe String -- ^ Cluster simulation mode
103 , optMaxLength :: Int -- ^ Stop after this many steps
104 , optMaster :: String -- ^ Collect data from RAPI
105 , optLuxi :: Maybe FilePath -- ^ Collect data from Luxi
106 , optOffline :: [String] -- ^ Names of offline nodes
107 , optIMem :: Int -- ^ Instance memory
108 , optIDsk :: Int -- ^ Instance disk
109 , optIVCPUs :: Int -- ^ Instance VCPUs
110 , optINodes :: Int -- ^ Nodes required for an instance
111 , optMinScore :: Cluster.Score -- ^ The minimum score we aim for
112 , optMcpu :: Double -- ^ Max cpu ratio for nodes
113 , optMdsk :: Double -- ^ Max disk usage ratio for nodes
114 , optVerbose :: Int -- ^ Verbosity level
115 , optShowVer :: Bool -- ^ Just show the program version
116 , optShowHelp :: Bool -- ^ Just show the help
119 -- | Default values for the command line options.
120 defaultOptions :: Options
121 defaultOptions = Options
122 { optShowNodes = False
123 , optShowCmds = Nothing
125 , optNoHeaders = False
127 , optNodeFile = "nodes"
129 , optInstFile = "instances"
131 , optNodeSim = Nothing
145 , optShowHelp = False
148 -- | Abrreviation for the option type
149 type OptType = OptDescr (Options -> Options)
151 oPrintNodes :: OptType
152 oPrintNodes = Option "p" ["print-nodes"]
153 (NoArg (\ opts -> opts { optShowNodes = True }))
154 "print the final node list"
156 oPrintCommands :: OptType
157 oPrintCommands = Option "C" ["print-commands"]
158 (OptArg ((\ f opts -> opts { optShowCmds = Just f }) .
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"
166 oOneline = Option "o" ["oneline"]
167 (NoArg (\ opts -> opts { optOneline = True }))
168 "print the ganeti command list for reaching the solution"
170 oNoHeaders :: OptType
171 oNoHeaders = Option "" ["no-headers"]
172 (NoArg (\ opts -> opts { optNoHeaders = True }))
173 "do not show a header line"
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"
181 oNodeFile = Option "n" ["nodes"]
182 (ReqArg (\ f o -> o { optNodeFile = f, optNodeSet = True }) "FILE")
186 oInstFile = Option "i" ["instances"]
187 (ReqArg (\ f o -> o { optInstFile = f, optInstSet = True }) "FILE")
188 "the instance list FILE"
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'"
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"
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"
207 oVerbose = Option "v" ["verbose"]
208 (NoArg (\ opts -> opts { optVerbose = optVerbose opts + 1 }))
209 "increase the verbosity level"
212 oQuiet = Option "q" ["quiet"]
213 (NoArg (\ opts -> opts { optVerbose = optVerbose opts - 1 }))
214 "decrease the verbosity level"
216 oOfflineNode :: OptType
217 oOfflineNode = Option "O" ["offline"]
218 (ReqArg (\ n o -> o { optOffline = n:optOffline o }) "NODE")
219 "set node as offline"
221 oMaxSolLength :: OptType
222 oMaxSolLength = Option "l" ["max-length"]
223 (ReqArg (\ i opts -> opts { optMaxLength = read i::Int }) "N")
224 "cap the solution at this many moves (useful for very\
225 \ unbalanced clusters)"
228 oMinScore = Option "e" ["min-score"]
229 (ReqArg (\ e opts -> opts { optMinScore = read e }) "EPSILON")
230 " mininum score to aim for"
233 oIMem = Option "" ["memory"]
234 (ReqArg (\ m opts -> opts { optIMem = read m }) "MEMORY")
235 "memory size for instances"
238 oIDisk = Option "" ["disk"]
239 (ReqArg (\ d opts -> opts { optIDsk = read d }) "DISK")
240 "disk size for instances"
243 oIVcpus = Option "" ["vcpus"]
244 (ReqArg (\ p opts -> opts { optIVCPUs = read p }) "NUM")
245 "number of virtual cpus for instances"
248 oINodes = Option "" ["req-nodes"]
249 (ReqArg (\ n opts -> opts { optINodes = read n }) "NODES")
250 "number of nodes for the new instances (1=plain, 2=mirrored)"
253 oMaxCpu = Option "" ["max-cpu"]
254 (ReqArg (\ n opts -> opts { optMcpu = read n }) "RATIO")
255 "maximum virtual-to-physical cpu ratio for nodes"
258 oMinDisk = Option "" ["min-disk"]
259 (ReqArg (\ n opts -> opts { optMdsk = read n }) "RATIO")
260 "minimum free disk space for nodes (between 0 and 1)"
263 oShowVer = Option "V" ["version"]
264 (NoArg (\ opts -> opts { optShowVer = True}))
265 "show the version of the program"
268 oShowHelp = Option "h" ["help"]
269 (NoArg (\ opts -> opts { optShowHelp = True}))
273 usageHelp :: String -> [OptType] -> String
275 usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
276 progname Version.version progname)
278 -- | Command line parser, using the 'options' structure.
279 parseOpts :: [String] -- ^ The command line arguments
280 -> String -- ^ The program name
281 -> [OptType] -- ^ The supported command line options
282 -> IO (Options, [String]) -- ^ The resulting options and leftover
284 parseOpts argv progname options =
285 case getOpt Permute options argv of
288 let resu@(po, _) = (foldl (flip id) defaultOptions o, n)
289 when (optShowHelp po) $ do
290 putStr $ usageHelp progname options
292 when (optShowVer po) $ do
293 printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
294 progname Version.version
295 compilerName (Data.Version.showVersion compilerVersion)
300 ioError (userError (concat errs ++ usageHelp progname options))
302 -- | Parse the environment and return the node\/instance names.
304 -- This also hardcodes here the default node\/instance file names.
305 parseEnv :: () -> IO (String, String)
307 a <- getEnvDefault "HTOOLS_NODES" "nodes"
308 b <- getEnvDefault "HTOOLS_INSTANCES" "instances"
311 -- | A shell script template for autogenerated scripts.
314 printf "#!/bin/sh\n\n\
315 \# Auto-generated script for executing cluster rebalancing\n\n\
316 \# To stop, touch the file /tmp/stop-htools\n\n\
319 \ if [ -f /tmp/stop-htools ]; then\n\
320 \ echo 'Stop requested, exiting'\n\
325 -- | Error beautifier
326 wrapIO :: IO (Result a) -> IO (Result a)
328 handle (\e -> return $ Bad $ show e)
331 -- | External tool data loader from a variety of sources.
332 loadExternalData :: Options
333 -> IO (Node.List, Instance.List, String)
334 loadExternalData opts = do
335 (env_node, env_inst) <- parseEnv ()
336 let nodef = if optNodeSet opts then optNodeFile opts
338 instf = if optInstSet opts then optInstFile opts
340 mhost = optMaster opts
342 simdata = optNodeSim opts
343 setRapi = mhost /= ""
344 setLuxi = isJust lsock
345 setSim = isJust simdata
346 setFiles = optNodeSet opts || optInstSet opts
347 allSet = filter id [setRapi, setLuxi, setFiles]
348 when (length allSet > 1) $
350 hPutStrLn stderr "Error: Only one of the rapi, luxi, and data\
351 \ files options should be given."
352 exitWith $ ExitFailure 1
356 _ | setRapi -> wrapIO $ Rapi.loadData mhost
357 | setLuxi -> wrapIO $ Luxi.loadData $ fromJust lsock
358 | setSim -> Simu.loadData $ fromJust simdata
359 | otherwise -> wrapIO $ Text.loadData nodef instf
361 let ldresult = input_data >>= Loader.mergeData
362 (loaded_nl, il, csf) <-
366 hPrintf stderr "Error: failed to load data. Details:\n%s\n" s
367 exitWith $ ExitFailure 1
369 let (fix_msgs, fixed_nl) = Loader.checkData loaded_nl il
371 unless (null fix_msgs || optVerbose opts == 0) $ do
372 hPutStrLn stderr "Warning: cluster has inconsistent data:"
373 hPutStrLn stderr . unlines . map (printf " - %s") $ fix_msgs
375 return (fixed_nl, il, csf)