CLI.hs 19.2 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
  ( Options(..)
  , OptType
  , parseOpts
  , shTemplate
  , defaultLuxiSocket
  , maybePrintNodes
  , maybePrintInsts
  , maybeShowWarnings
  , setNodeStatus
  -- * The options
  , oDataFile
  , oDiskMoves
  , oDiskTemplate
  , oDynuFile
  , oEvacMode
  , oExInst
  , oExTags
  , oExecJobs
  , oGroup
  , oInstMoves
  , oLuxiSocket
  , oMachineReadable
  , oMaxCpu
  , oMaxSolLength
  , oMinDisk
  , oMinGain
  , oMinGainLim
  , oMinScore
  , oNoHeaders
  , oNodeSim
  , oOfflineNode
  , oOutputDir
  , oPrintCommands
  , oPrintInsts
  , oPrintNodes
  , oQuiet
  , oRapiMaster
  , oReplay
  , oSaveCluster
  , oSelInst
  , oShowHelp
  , oShowVer
73
  , oStdSpec
Iustin Pop's avatar
Iustin Pop committed
74
75
76
  , oTieredSpec
  , oVerbose
  ) where
77

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

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

95
96
97
98
99
-- * Constants

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

103
104
-- * Data types

105
106
-- | Command line options structure.
data Options = Options
Iustin Pop's avatar
Iustin Pop committed
107
108
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
  { 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
  , 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
137
  , optStdSpec     :: Maybe RSpec    -- ^ Requested standard specs
Iustin Pop's avatar
Iustin Pop committed
138
139
140
141
  , optTieredSpec  :: Maybe RSpec    -- ^ Requested specs for tiered mode
  , optReplay      :: Maybe String   -- ^ Unittests: RNG state
  , optVerbose     :: Int            -- ^ Verbosity level
  } deriving Show
142
143
144
145

-- | Default values for the command line options.
defaultOptions :: Options
defaultOptions  = Options
Iustin Pop's avatar
Iustin Pop committed
146
147
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
  { optDataFile    = Nothing
  , optDiskMoves   = True
  , optInstMoves   = True
  , optDiskTemplate = DTDrbd8
  , optDynuFile    = Nothing
  , optEvacMode    = False
  , optExInst      = []
  , optExTags      = Nothing
  , optExecJobs    = False
  , optGroup       = Nothing
  , optSelInst     = []
  , 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
176
  , optStdSpec     = Nothing
Iustin Pop's avatar
Iustin Pop committed
177
178
179
180
  , optTieredSpec  = Nothing
  , optReplay      = Nothing
  , optVerbose     = 1
  }
181

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

185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
-- * Helper functions

parseISpecString :: String -> String -> Result RSpec
parseISpecString descr inp = do
  let sp = sepSplit ',' inp
  prs <- mapM (\(fn, val) -> fn val) $
         zip [ annotateResult (descr ++ " specs memory") . parseUnit
             , annotateResult (descr ++ " specs disk") . parseUnit
             , tryRead (descr ++ " specs cpus")
             ] sp
  case prs of
    [dsk, ram, cpu] -> return $ RSpec cpu ram dsk
    _ -> Bad $ "Invalid " ++ descr ++ " specification: '" ++ inp ++
         "', expected disk,ram,cpu"

200
201
-- * Command line options

202
203
204
205
oDataFile :: OptType
oDataFile = Option "t" ["text-data"]
            (ReqArg (\ f o -> Ok o { optDataFile = Just f }) "FILE")
            "the cluster data FILE"
206

Iustin Pop's avatar
Iustin Pop committed
207
208
209
210
211
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"
212

213
214
215
oDiskTemplate :: OptType
oDiskTemplate = Option "" ["disk-template"]
                (ReqArg (\ t opts -> do
216
                           dt <- diskTemplateFromRaw t
217
218
219
                           return $ opts { optDiskTemplate = dt }) "TEMPLATE")
                "select the desired disk template"

220
221
222
223
224
oSelInst :: OptType
oSelInst = Option "" ["select-instances"]
          (ReqArg (\ f opts -> Ok opts { optSelInst = sepSplit ',' f }) "INSTS")
          "only select given instances for any moves"

225
226
227
228
229
230
231
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
232
233
234
235
oDynuFile :: OptType
oDynuFile = Option "U" ["dynu-file"]
            (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
            "Import dynamic utilisation data from the given FILE"
236

Iustin Pop's avatar
Iustin Pop committed
237
238
239
240
241
242
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"

243
244
245
oExInst :: OptType
oExInst = Option "" ["exclude-instances"]
          (ReqArg (\ f opts -> Ok opts { optExInst = sepSplit ',' f }) "INSTS")
246
          "exclude given instances from any moves"
247

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

253
254
oExecJobs :: OptType
oExecJobs = Option "X" ["exec"]
255
             (NoArg (\ opts -> Ok opts { optExecJobs = True}))
256
             "execute the suggested moves via Luxi (only available when using\
257
             \ it for data gathering)"
258

259
260
261
262
263
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
264
265
266
267
268
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"
269

270
271
oMachineReadable :: OptType
oMachineReadable = Option "" ["machine-readable"]
Iustin Pop's avatar
Iustin Pop committed
272
                   (OptArg (\ f opts -> do
273
274
275
276
277
278
                     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"

279
280
oMaxCpu :: OptType
oMaxCpu = Option "" ["max-cpu"]
281
          (ReqArg (\ n opts -> Ok opts { optMcpu = read n }) "RATIO")
282
283
          "maximum virtual-to-physical cpu ratio for nodes (from 1\
          \ upwards) [64]"
284

Iustin Pop's avatar
Iustin Pop committed
285
286
287
oMaxSolLength :: OptType
oMaxSolLength = Option "l" ["max-length"]
                (ReqArg (\ i opts -> Ok opts { optMaxLength = read i }) "N")
Iustin Pop's avatar
Iustin Pop committed
288
289
290
                "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
291

292
293
oMinDisk :: OptType
oMinDisk = Option "" ["min-disk"]
294
           (ReqArg (\ n opts -> Ok opts { optMdsk = read n }) "RATIO")
295
           "minimum free disk space for nodes (between 0 and 1) [0]"
296

297
298
299
300
301
302
303
304
305
306
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
307
308
309
oMinScore :: OptType
oMinScore = Option "e" ["min-score"]
            (ReqArg (\ e opts -> Ok opts { optMinScore = read e }) "EPSILON")
310
            "mininum score to aim for"
311

Iustin Pop's avatar
Iustin Pop committed
312
313
314
315
oNoHeaders :: OptType
oNoHeaders = Option "" ["no-headers"]
             (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
             "do not show a header line"
316

Iustin Pop's avatar
Iustin Pop committed
317
318
oNodeSim :: OptType
oNodeSim = Option "" ["simulate"]
319
            (ReqArg (\ f o -> Ok o { optNodeSim = f:optNodeSim o }) "SPEC")
Iustin Pop's avatar
Iustin Pop committed
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
            "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
349
350
351
352
353
                          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
354
355
356
357
358
359
360
361
362
363
364
365
366
                       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
367
368
369
370
371
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
372
373
374
375
376
377
378
379
380
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"
381

382
383
384
385
oStdSpec :: OptType
oStdSpec = Option "" ["standard-alloc"]
             (ReqArg (\ inp opts -> do
                        tspec <- parseISpecString "standard" inp
386
                        return $ opts { optStdSpec = Just tspec } )
387
388
389
              "STDSPEC")
             "enable standard specs allocation, given as 'disk,ram,cpu'"

390
391
392
oTieredSpec :: OptType
oTieredSpec = Option "" ["tiered-alloc"]
             (ReqArg (\ inp opts -> do
393
                        tspec <- parseISpecString "tiered" inp
Iustin Pop's avatar
Iustin Pop committed
394
                        return $ opts { optTieredSpec = Just tspec } )
395
              "TSPEC")
396
             "enable tiered specs allocation, given as 'disk,ram,cpu'"
397

398
399
400
401
402
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
403
404
405
406
oVerbose :: OptType
oVerbose = Option "v" ["verbose"]
           (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
           "increase the verbosity level"
407

408
409
-- * Functions

410
411
412
413
414
415
416
417
418
419
-- | 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'"

420
-- | Usage info.
421
usageHelp :: String -> [OptType] -> String
Iustin Pop's avatar
Iustin Pop committed
422
usageHelp progname =
Iustin Pop's avatar
Iustin Pop committed
423
424
  usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
             progname Version.version progname)
425

426
-- | Command line parser, using the 'Options' structure.
427
428
429
430
431
432
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
433
434
435
436
  case getOpt Permute options argv of
    (o, n, []) ->
      do
        let (pr, args) = (foldM (flip id) defaultOptions o, n)
Iustin Pop's avatar
Iustin Pop committed
437
438
439
440
441
442
443
        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
Iustin Pop's avatar
Iustin Pop committed
444
445
446
447
448
449
450
451
452
453
454
455
456
457
        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
458

Iustin Pop's avatar
Iustin Pop committed
459
-- | A shell script template for autogenerated scripts.
460
461
shTemplate :: String
shTemplate =
Iustin Pop's avatar
Iustin Pop committed
462
463
464
465
466
467
468
469
470
471
  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"
472
473
474
475
476
477
478
479
480
481
482

-- | 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
483
484
485
486
487
488
489
490
491
492
493
494


-- | 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
495
496
497
498
499
500
501
502
503

-- | 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
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518

-- | 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

Iustin Pop's avatar
Iustin Pop committed
519
  unless (null offline_wrong) $ do
Iustin Pop's avatar
Iustin Pop committed
520
521
522
523
524
525
526
527
528
529
         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