CLI.hs 20.1 KB
Newer Older
1 2
{-| Implementation of command-line functions.

3 4 5
This module holds the common command-line related functions for the
binaries, separated into this module since "Ganeti.HTools.Utils" is
used in many other places and this is more IO oriented.
6 7 8

-}

Iustin Pop's avatar
Iustin Pop committed
9 10
{-

Iustin Pop's avatar
Iustin Pop committed
11
Copyright (C) 2009, 2010, 2011 Google Inc.
Iustin Pop's avatar
Iustin Pop committed
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.

-}

30
module Ganeti.HTools.CLI
Iustin Pop's avatar
Iustin Pop committed
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
  ( Options(..)
  , OptType
  , parseOpts
  , shTemplate
  , defaultLuxiSocket
  , maybePrintNodes
  , maybePrintInsts
  , maybeShowWarnings
  , setNodeStatus
  -- * The options
  , oDataFile
  , oDiskMoves
  , oDiskTemplate
  , oDynuFile
  , oEvacMode
  , oExInst
  , oExTags
  , oExecJobs
  , oGroup
  , oIDisk
  , oIMem
  , oIVcpus
  , oInstMoves
  , oLuxiSocket
  , oMachineReadable
  , oMaxCpu
  , oMaxSolLength
  , oMinDisk
  , oMinGain
  , oMinGainLim
  , oMinScore
  , oNoHeaders
  , oNodeSim
  , oOfflineNode
  , oOutputDir
  , oPrintCommands
  , oPrintInsts
  , oPrintNodes
  , oQuiet
  , oRapiMaster
  , oReplay
  , oSaveCluster
  , oSelInst
  , oShowHelp
  , oShowVer
  , oTieredSpec
  , oVerbose
  ) where
79

Iustin Pop's avatar
Iustin Pop committed
80
import Control.Monad
81
import Data.Maybe (fromMaybe)
82
import qualified Data.Version
83 84 85
import System.Console.GetOpt
import System.IO
import System.Info
86
import System.Exit
Iustin Pop's avatar
Iustin Pop committed
87
import Text.Printf (printf, hPrintf)
88 89

import qualified Ganeti.HTools.Version as Version(version)
Iustin Pop's avatar
Iustin Pop committed
90 91
import qualified Ganeti.HTools.Container as Container
import qualified Ganeti.HTools.Node as Node
Iustin Pop's avatar
Iustin Pop committed
92
import qualified Ganeti.Constants as C
93
import Ganeti.HTools.Types
94
import Ganeti.HTools.Utils
Iustin Pop's avatar
Iustin Pop committed
95
import Ganeti.HTools.Loader
96

97 98 99 100 101
-- * Constants

-- | The default value for the luxi socket.
--
-- This is re-exported from the "Ganeti.Constants" module.
102
defaultLuxiSocket :: FilePath
Iustin Pop's avatar
Iustin Pop committed
103
defaultLuxiSocket = C.masterSocket
104

105 106
-- * Data types

107 108
-- | Command line options structure.
data Options = Options
Iustin Pop's avatar
Iustin Pop committed
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
  { optDataFile    :: Maybe FilePath -- ^ Path to the cluster data file
  , optDiskMoves   :: Bool           -- ^ Allow disk moves
  , optInstMoves   :: Bool           -- ^ Allow instance moves
  , optDiskTemplate :: DiskTemplate  -- ^ The requested disk template
  , optDynuFile    :: Maybe FilePath -- ^ Optional file with dynamic use data
  , optEvacMode    :: Bool           -- ^ Enable evacuation mode
  , optExInst      :: [String]       -- ^ Instances to be excluded
  , optExTags      :: Maybe [String] -- ^ Tags to use for exclusion
  , optExecJobs    :: Bool           -- ^ Execute the commands via Luxi
  , optGroup       :: Maybe GroupID  -- ^ The UUID of the group to process
  , optSelInst     :: [String]       -- ^ Instances to be excluded
  , optISpec       :: RSpec          -- ^ Requested instance specs
  , optLuxi        :: Maybe FilePath -- ^ Collect data from Luxi
  , optMachineReadable :: Bool       -- ^ Output machine-readable format
  , optMaster      :: String         -- ^ Collect data from RAPI
  , optMaxLength   :: Int            -- ^ Stop after this many steps
  , optMcpu        :: Double         -- ^ Max cpu ratio for nodes
  , optMdsk        :: Double         -- ^ Max disk usage ratio for nodes
  , optMinGain     :: Score          -- ^ Min gain we aim for in a step
  , optMinGainLim  :: Score          -- ^ Limit below which we apply mingain
  , optMinScore    :: Score          -- ^ The minimum score we aim for
  , optNoHeaders   :: Bool           -- ^ Do not show a header line
  , optNodeSim     :: [String]       -- ^ Cluster simulation mode
  , optOffline     :: [String]       -- ^ Names of offline nodes
  , optOutPath     :: FilePath       -- ^ Path to the output directory
  , optSaveCluster :: Maybe FilePath -- ^ Save cluster state to this file
  , optShowCmds    :: Maybe FilePath -- ^ Whether to show the command list
  , optShowHelp    :: Bool           -- ^ Just show the help
  , optShowInsts   :: Bool           -- ^ Whether to show the instance map
  , optShowNodes   :: Maybe [String] -- ^ Whether to show node status
  , optShowVer     :: Bool           -- ^ Just show the program version
  , optTieredSpec  :: Maybe RSpec    -- ^ Requested specs for tiered mode
  , optReplay      :: Maybe String   -- ^ Unittests: RNG state
  , optVerbose     :: Int            -- ^ Verbosity level
  } deriving Show
144 145 146 147

-- | Default values for the command line options.
defaultOptions :: Options
defaultOptions  = Options
Iustin Pop's avatar
Iustin Pop committed
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
  { optDataFile    = Nothing
  , optDiskMoves   = True
  , optInstMoves   = True
  , optDiskTemplate = DTDrbd8
  , optDynuFile    = Nothing
  , optEvacMode    = False
  , optExInst      = []
  , optExTags      = Nothing
  , optExecJobs    = False
  , optGroup       = Nothing
  , optSelInst     = []
  , optISpec       = RSpec 1 4096 102400
  , optLuxi        = Nothing
  , optMachineReadable = False
  , optMaster      = ""
  , optMaxLength   = -1
  , optMcpu        = defVcpuRatio
  , optMdsk        = defReservedDiskRatio
  , optMinGain     = 1e-2
  , optMinGainLim  = 1e-1
  , optMinScore    = 1e-9
  , optNoHeaders   = False
  , optNodeSim     = []
  , optOffline     = []
  , optOutPath     = "."
  , optSaveCluster = Nothing
  , optShowCmds    = Nothing
  , optShowHelp    = False
  , optShowInsts   = False
  , optShowNodes   = Nothing
  , optShowVer     = False
  , optTieredSpec  = Nothing
  , optReplay      = Nothing
  , optVerbose     = 1
  }
183

184
-- | Abrreviation for the option type.
185
type OptType = OptDescr (Options -> Result Options)
186

187 188
-- * Command line options

189 190 191 192
oDataFile :: OptType
oDataFile = Option "t" ["text-data"]
            (ReqArg (\ f o -> Ok o { optDataFile = Just f }) "FILE")
            "the cluster data FILE"
193

Iustin Pop's avatar
Iustin Pop committed
194 195 196 197 198
oDiskMoves :: OptType
oDiskMoves = Option "" ["no-disk-moves"]
             (NoArg (\ opts -> Ok opts { optDiskMoves = False}))
             "disallow disk moves from the list of allowed instance changes,\
             \ thus allowing only the 'cheap' failover/migrate operations"
199

200 201 202
oDiskTemplate :: OptType
oDiskTemplate = Option "" ["disk-template"]
                (ReqArg (\ t opts -> do
203
                           dt <- diskTemplateFromRaw t
204 205 206
                           return $ opts { optDiskTemplate = dt }) "TEMPLATE")
                "select the desired disk template"

207 208 209 210 211
oSelInst :: OptType
oSelInst = Option "" ["select-instances"]
          (ReqArg (\ f opts -> Ok opts { optSelInst = sepSplit ',' f }) "INSTS")
          "only select given instances for any moves"

212 213 214 215 216 217 218
oInstMoves :: OptType
oInstMoves = Option "" ["no-instance-moves"]
             (NoArg (\ opts -> Ok opts { optInstMoves = False}))
             "disallow instance (primary node) moves from the list of allowed,\
             \ instance changes, thus allowing only slower, but sometimes\
             \ safer, drbd secondary changes"

Iustin Pop's avatar
Iustin Pop committed
219 220 221 222
oDynuFile :: OptType
oDynuFile = Option "U" ["dynu-file"]
            (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
            "Import dynamic utilisation data from the given FILE"
223

Iustin Pop's avatar
Iustin Pop committed
224 225 226 227 228 229
oEvacMode :: OptType
oEvacMode = Option "E" ["evac-mode"]
            (NoArg (\opts -> Ok opts { optEvacMode = True }))
            "enable evacuation mode, where the algorithm only moves \
            \ instances away from offline and drained nodes"

230 231 232
oExInst :: OptType
oExInst = Option "" ["exclude-instances"]
          (ReqArg (\ f opts -> Ok opts { optExInst = sepSplit ',' f }) "INSTS")
233
          "exclude given instances from any moves"
234

Iustin Pop's avatar
Iustin Pop committed
235 236 237 238
oExTags :: OptType
oExTags = Option "" ["exclusion-tags"]
            (ReqArg (\ f opts -> Ok opts { optExTags = Just $ sepSplit ',' f })
             "TAG,...") "Enable instance exclusion based on given tag prefix"
239

240 241
oExecJobs :: OptType
oExecJobs = Option "X" ["exec"]
242
             (NoArg (\ opts -> Ok opts { optExecJobs = True}))
243
             "execute the suggested moves via Luxi (only available when using\
244
             \ it for data gathering)"
245

246 247 248 249 250
oGroup :: OptType
oGroup = Option "G" ["group"]
            (ReqArg (\ f o -> Ok o { optGroup = Just f }) "ID")
            "the ID of the group to balance"

Iustin Pop's avatar
Iustin Pop committed
251 252
oIDisk :: OptType
oIDisk = Option "" ["disk"]
253
         (ReqArg (\ d opts -> do
Iustin Pop's avatar
Iustin Pop committed
254
                    dsk <- annotateResult "--disk option" (parseUnit d)
255 256 257
                    let ospec = optISpec opts
                        nspec = ospec { rspecDsk = dsk }
                    return $ opts { optISpec = nspec }) "DISK")
Iustin Pop's avatar
Iustin Pop committed
258
         "disk size for instances"
259 260 261

oIMem :: OptType
oIMem = Option "" ["memory"]
262
        (ReqArg (\ m opts -> do
Iustin Pop's avatar
Iustin Pop committed
263
                   mem <- annotateResult "--memory option" (parseUnit m)
264 265 266
                   let ospec = optISpec opts
                       nspec = ospec { rspecMem = mem }
                   return $ opts { optISpec = nspec }) "MEMORY")
267 268 269 270
        "memory size for instances"

oIVcpus :: OptType
oIVcpus = Option "" ["vcpus"]
271 272 273 274 275
          (ReqArg (\ p opts -> do
                     vcpus <- tryRead "--vcpus option" p
                     let ospec = optISpec opts
                         nspec = ospec { rspecCpu = vcpus }
                     return $ opts { optISpec = nspec }) "NUM")
276 277
          "number of virtual cpus for instances"

Iustin Pop's avatar
Iustin Pop committed
278 279 280 281 282
oLuxiSocket :: OptType
oLuxiSocket = Option "L" ["luxi"]
              (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
                       fromMaybe defaultLuxiSocket) "SOCKET")
              "collect data via Luxi, optionally using the given SOCKET path"
283

284 285
oMachineReadable :: OptType
oMachineReadable = Option "" ["machine-readable"]
Iustin Pop's avatar
Iustin Pop committed
286
                   (OptArg (\ f opts -> do
287 288 289 290 291 292
                     flag <- parseYesNo True f
                     return $ opts { optMachineReadable = flag }) "CHOICE")
          "enable machine readable output (pass either 'yes' or 'no' to\
          \ explicitely control the flag, or without an argument defaults to\
          \ yes"

293 294
oMaxCpu :: OptType
oMaxCpu = Option "" ["max-cpu"]
295
          (ReqArg (\ n opts -> Ok opts { optMcpu = read n }) "RATIO")
296 297
          "maximum virtual-to-physical cpu ratio for nodes (from 1\
          \ upwards) [64]"
298

Iustin Pop's avatar
Iustin Pop committed
299 300 301
oMaxSolLength :: OptType
oMaxSolLength = Option "l" ["max-length"]
                (ReqArg (\ i opts -> Ok opts { optMaxLength = read i }) "N")
Iustin Pop's avatar
Iustin Pop committed
302 303 304
                "cap the solution at this many balancing or allocation \
                \ rounds (useful for very unbalanced clusters or empty \
                \ clusters)"
Iustin Pop's avatar
Iustin Pop committed
305

306 307
oMinDisk :: OptType
oMinDisk = Option "" ["min-disk"]
308
           (ReqArg (\ n opts -> Ok opts { optMdsk = read n }) "RATIO")
309
           "minimum free disk space for nodes (between 0 and 1) [0]"
310

311 312 313 314 315 316 317 318 319 320
oMinGain :: OptType
oMinGain = Option "g" ["min-gain"]
            (ReqArg (\ g opts -> Ok opts { optMinGain = read g }) "DELTA")
            "minimum gain to aim for in a balancing step before giving up"

oMinGainLim :: OptType
oMinGainLim = Option "" ["min-gain-limit"]
            (ReqArg (\ g opts -> Ok opts { optMinGainLim = read g }) "SCORE")
            "minimum cluster score for which we start checking the min-gain"

Iustin Pop's avatar
Iustin Pop committed
321 322 323
oMinScore :: OptType
oMinScore = Option "e" ["min-score"]
            (ReqArg (\ e opts -> Ok opts { optMinScore = read e }) "EPSILON")
324
            "mininum score to aim for"
325

Iustin Pop's avatar
Iustin Pop committed
326 327 328 329
oNoHeaders :: OptType
oNoHeaders = Option "" ["no-headers"]
             (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
             "do not show a header line"
330

Iustin Pop's avatar
Iustin Pop committed
331 332
oNodeSim :: OptType
oNodeSim = Option "" ["simulate"]
333
            (ReqArg (\ f o -> Ok o { optNodeSim = f:optNodeSim o }) "SPEC")
Iustin Pop's avatar
Iustin Pop committed
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
            "simulate an empty cluster, given as 'num_nodes,disk,ram,cpu'"

oOfflineNode :: OptType
oOfflineNode = Option "O" ["offline"]
               (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
               "set node as offline"

oOutputDir :: OptType
oOutputDir = Option "d" ["output-dir"]
             (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
             "directory in which to write output files"

oPrintCommands :: OptType
oPrintCommands = Option "C" ["print-commands"]
                 (OptArg ((\ f opts -> Ok opts { optShowCmds = Just f }) .
                          fromMaybe "-")
                  "FILE")
                 "print the ganeti command list for reaching the solution,\
                 \ if an argument is passed then write the commands to a\
                 \ file named as such"

oPrintInsts :: OptType
oPrintInsts = Option "" ["print-instances"]
              (NoArg (\ opts -> Ok opts { optShowInsts = True }))
              "print the final instance map"

oPrintNodes :: OptType
oPrintNodes = Option "p" ["print-nodes"]
              (OptArg ((\ f opts ->
Iustin Pop's avatar
Iustin Pop committed
363 364 365 366 367
                          let (prefix, realf) = case f of
                                                  '+':rest -> (["+"], rest)
                                                  _ -> ([], f)
                              splitted = prefix ++ sepSplit ',' realf
                          in Ok opts { optShowNodes = Just splitted }) .
Iustin Pop's avatar
Iustin Pop committed
368 369 370 371 372 373 374 375 376 377 378 379 380
                       fromMaybe []) "FIELDS")
              "print the final node list"

oQuiet :: OptType
oQuiet = Option "q" ["quiet"]
         (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
         "decrease the verbosity level"

oRapiMaster :: OptType
oRapiMaster = Option "m" ["master"]
              (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
              "collect data via RAPI at the given ADDRESS"

Iustin Pop's avatar
Iustin Pop committed
381 382 383 384 385
oSaveCluster :: OptType
oSaveCluster = Option "S" ["save"]
            (ReqArg (\ f opts -> Ok opts { optSaveCluster = Just f }) "FILE")
            "Save cluster state at the end of the processing to FILE"

Iustin Pop's avatar
Iustin Pop committed
386 387 388 389 390 391 392 393 394
oShowHelp :: OptType
oShowHelp = Option "h" ["help"]
            (NoArg (\ opts -> Ok opts { optShowHelp = True}))
            "show help"

oShowVer :: OptType
oShowVer = Option "V" ["version"]
           (NoArg (\ opts -> Ok opts { optShowVer = True}))
           "show the version of the program"
395

396 397 398
oTieredSpec :: OptType
oTieredSpec = Option "" ["tiered-alloc"]
             (ReqArg (\ inp opts -> do
Iustin Pop's avatar
Iustin Pop committed
399 400 401 402 403 404 405 406 407 408 409 410 411 412
                        let sp = sepSplit ',' inp
                        prs <- mapM (\(fn, val) -> fn val) $
                               zip [ annotateResult "tiered specs memory" .
                                     parseUnit
                                   , annotateResult "tiered specs disk" .
                                     parseUnit
                                   , tryRead "tiered specs cpus"
                                   ] sp
                        tspec <-
                          case prs of
                            [dsk, ram, cpu] -> return $ RSpec cpu ram dsk
                            _ -> Bad $ "Invalid specification: " ++ inp ++
                                 ", expected disk,ram,cpu"
                        return $ opts { optTieredSpec = Just tspec } )
413
              "TSPEC")
414
             "enable tiered specs allocation, given as 'disk,ram,cpu'"
415

416 417 418 419 420
oReplay :: OptType
oReplay = Option "" ["replay"]
          (ReqArg (\ stat opts -> Ok opts { optReplay = Just stat } ) "STATE")
          "Pre-seed the random number generator with STATE"

Iustin Pop's avatar
Iustin Pop committed
421 422 423 424
oVerbose :: OptType
oVerbose = Option "v" ["verbose"]
           (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
           "increase the verbosity level"
425

426 427
-- * Functions

428 429 430 431 432 433 434 435 436 437
-- | Helper for parsing a yes\/no command line flag.
parseYesNo :: Bool         -- ^ Default whalue (when we get a @Nothing@)
           -> Maybe String -- ^ Parameter value
           -> Result Bool  -- ^ Resulting boolean value
parseYesNo v Nothing      = return v
parseYesNo _ (Just "yes") = return True
parseYesNo _ (Just "no")  = return False
parseYesNo _ (Just s)     = fail $ "Invalid choice '" ++ s ++
                            "', pass one of 'yes' or 'no'"

438
-- | Usage info.
439
usageHelp :: String -> [OptType] -> String
Iustin Pop's avatar
Iustin Pop committed
440
usageHelp progname =
Iustin Pop's avatar
Iustin Pop committed
441 442
  usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
             progname Version.version progname)
443

444
-- | Command line parser, using the 'Options' structure.
445 446 447 448 449 450
parseOpts :: [String]               -- ^ The command line arguments
          -> String                 -- ^ The program name
          -> [OptType]              -- ^ The supported command line options
          -> IO (Options, [String]) -- ^ The resulting options and leftover
                                    -- arguments
parseOpts argv progname options =
Iustin Pop's avatar
Iustin Pop committed
451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475
  case getOpt Permute options argv of
    (o, n, []) ->
      do
        let (pr, args) = (foldM (flip id) defaultOptions o, n)
        po <- (case pr of
                 Bad msg -> do
                   hPutStrLn stderr "Error while parsing command\
                                    \line arguments:"
                   hPutStrLn stderr msg
                   exitWith $ ExitFailure 1
                 Ok val -> return val)
        when (optShowHelp po) $ do
          putStr $ usageHelp progname options
          exitWith ExitSuccess
        when (optShowVer po) $ do
          printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
                 progname Version.version
                 compilerName (Data.Version.showVersion compilerVersion)
                 os arch :: IO ()
          exitWith ExitSuccess
        return (po, args)
    (_, _, errs) -> do
      hPutStrLn stderr $ "Command line error: "  ++ concat errs
      hPutStrLn stderr $ usageHelp progname options
      exitWith $ ExitFailure 2
476

Iustin Pop's avatar
Iustin Pop committed
477
-- | A shell script template for autogenerated scripts.
478 479
shTemplate :: String
shTemplate =
Iustin Pop's avatar
Iustin Pop committed
480 481 482 483 484 485 486 487 488 489
  printf "#!/bin/sh\n\n\
         \# Auto-generated script for executing cluster rebalancing\n\n\
         \# To stop, touch the file /tmp/stop-htools\n\n\
         \set -e\n\n\
         \check() {\n\
         \  if [ -f /tmp/stop-htools ]; then\n\
         \    echo 'Stop requested, exiting'\n\
         \    exit 0\n\
         \  fi\n\
         \}\n\n"
490 491 492 493 494 495 496 497 498 499 500

-- | Optionally print the node list.
maybePrintNodes :: Maybe [String]       -- ^ The field list
                -> String               -- ^ Informational message
                -> ([String] -> String) -- ^ Function to generate the listing
                -> IO ()
maybePrintNodes Nothing _ _ = return ()
maybePrintNodes (Just fields) msg fn = do
  hPutStrLn stderr ""
  hPutStrLn stderr (msg ++ " status:")
  hPutStrLn stderr $ fn fields
501 502 503 504 505 506 507 508 509 510 511 512


-- | Optionally print the instance list.
maybePrintInsts :: Bool   -- ^ Whether to print the instance list
                -> String -- ^ Type of the instance map (e.g. initial)
                -> String -- ^ The instance data
                -> IO ()
maybePrintInsts do_print msg instdata =
  when do_print $ do
    hPutStrLn stderr ""
    hPutStrLn stderr $ msg ++ " instance map:"
    hPutStr stderr instdata
513 514 515 516 517 518 519 520 521

-- | Function to display warning messages from parsing the cluster
-- state.
maybeShowWarnings :: [String] -- ^ The warning messages
                  -> IO ()
maybeShowWarnings fix_msgs =
  unless (null fix_msgs) $ do
    hPutStrLn stderr "Warning: cluster has inconsistent data:"
    hPutStrLn stderr . unlines . map (printf "  - %s") $ fix_msgs
Iustin Pop's avatar
Iustin Pop committed
522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547

-- | Set node properties based on command line options.
setNodeStatus :: Options -> Node.List -> IO Node.List
setNodeStatus opts fixed_nl = do
  let offline_passed = optOffline opts
      all_nodes = Container.elems fixed_nl
      offline_lkp = map (lookupName (map Node.name all_nodes)) offline_passed
      offline_wrong = filter (not . goodLookupResult) offline_lkp
      offline_names = map lrContent offline_lkp
      offline_indices = map Node.idx $
                        filter (\n -> Node.name n `elem` offline_names)
                               all_nodes
      m_cpu = optMcpu opts
      m_dsk = optMdsk opts

  when (not (null offline_wrong)) $ do
         hPrintf stderr "Error: Wrong node name(s) set as offline: %s\n"
                     (commaJoin (map lrContent offline_wrong)) :: IO ()
         exitWith $ ExitFailure 1

  let nm = Container.map (\n -> if Node.idx n `elem` offline_indices
                                then Node.setOffline n True
                                else n) fixed_nl
      nlf = Container.map (flip Node.setMdsk m_dsk . flip Node.setMcpu m_cpu)
            nm
  return nlf