Statistics
| Branch: | Tag: | Revision:

root / htools / Ganeti / HTools / CLI.hs @ ee4ffc8a

History | View | Annotate | Download (20.7 kB)

1
{-| Implementation of command-line functions.
2

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

    
7
-}
8

    
9
{-
10

    
11
Copyright (C) 2009, 2010, 2011, 2012 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
  , parseOptsInner
35
  , parseYesNo
36
  , parseISpecString
37
  , shTemplate
38
  , defaultLuxiSocket
39
  , maybePrintNodes
40
  , maybePrintInsts
41
  , maybeShowWarnings
42
  , setNodeStatus
43
  -- * The options
44
  , oDataFile
45
  , oDiskMoves
46
  , oDiskTemplate
47
  , oDynuFile
48
  , oEvacMode
49
  , oExInst
50
  , oExTags
51
  , oExecJobs
52
  , oGroup
53
  , oInstMoves
54
  , oLuxiSocket
55
  , oMachineReadable
56
  , oMaxCpu
57
  , oMaxSolLength
58
  , oMinDisk
59
  , oMinGain
60
  , oMinGainLim
61
  , oMinScore
62
  , oNoHeaders
63
  , oNodeSim
64
  , oOfflineNode
65
  , oOutputDir
66
  , oPrintCommands
67
  , oPrintInsts
68
  , oPrintNodes
69
  , oQuiet
70
  , oRapiMaster
71
  , oReplay
72
  , oSaveCluster
73
  , oSelInst
74
  , oShowHelp
75
  , oShowVer
76
  , oStdSpec
77
  , oTestCount
78
  , oTieredSpec
79
  , oVerbose
80
  ) where
81

    
82
import Control.Monad
83
import Data.Maybe (fromMaybe)
84
import qualified Data.Version
85
import System.Console.GetOpt
86
import System.IO
87
import System.Info
88
import System.Exit
89
import Text.Printf (printf, hPrintf)
90

    
91
import qualified Ganeti.HTools.Version as Version(version)
92
import qualified Ganeti.HTools.Container as Container
93
import qualified Ganeti.HTools.Node as Node
94
import qualified Ganeti.Constants as C
95
import Ganeti.HTools.Types
96
import Ganeti.HTools.Utils
97
import Ganeti.HTools.Loader
98

    
99
-- * Constants
100

    
101
-- | The default value for the luxi socket.
102
--
103
-- This is re-exported from the "Ganeti.Constants" module.
104
defaultLuxiSocket :: FilePath
105
defaultLuxiSocket = C.masterSocket
106

    
107
-- * Data types
108

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

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

    
188
-- | Abrreviation for the option type.
189
type OptType = OptDescr (Options -> Result Options)
190

    
191
-- * Helper functions
192

    
193
parseISpecString :: String -> String -> Result RSpec
194
parseISpecString descr inp = do
195
  let sp = sepSplit ',' inp
196
      err = Bad ("Invalid " ++ descr ++ " specification: '" ++ inp ++
197
                 "', expected disk,ram,cpu")
198
  when (length sp /= 3) err
199
  prs <- mapM (\(fn, val) -> fn val) $
200
         zip [ annotateResult (descr ++ " specs disk") . parseUnit
201
             , annotateResult (descr ++ " specs memory") . parseUnit
202
             , tryRead (descr ++ " specs cpus")
203
             ] sp
204
  case prs of
205
    [dsk, ram, cpu] -> return $ RSpec cpu ram dsk
206
    _ -> err
207

    
208
-- * Command line options
209

    
210
oDataFile :: OptType
211
oDataFile = Option "t" ["text-data"]
212
            (ReqArg (\ f o -> Ok o { optDataFile = Just f }) "FILE")
213
            "the cluster data FILE"
214

    
215
oDiskMoves :: OptType
216
oDiskMoves = Option "" ["no-disk-moves"]
217
             (NoArg (\ opts -> Ok opts { optDiskMoves = False}))
218
             "disallow disk moves from the list of allowed instance changes,\
219
             \ thus allowing only the 'cheap' failover/migrate operations"
220

    
221
oDiskTemplate :: OptType
222
oDiskTemplate = Option "" ["disk-template"]
223
                (ReqArg (\ t opts -> do
224
                           dt <- diskTemplateFromRaw t
225
                           return $ opts { optDiskTemplate = Just dt })
226
                 "TEMPLATE") "select the desired disk template"
227

    
228
oSelInst :: OptType
229
oSelInst = Option "" ["select-instances"]
230
          (ReqArg (\ f opts -> Ok opts { optSelInst = sepSplit ',' f }) "INSTS")
231
          "only select given instances for any moves"
232

    
233
oInstMoves :: OptType
234
oInstMoves = Option "" ["no-instance-moves"]
235
             (NoArg (\ opts -> Ok opts { optInstMoves = False}))
236
             "disallow instance (primary node) moves from the list of allowed,\
237
             \ instance changes, thus allowing only slower, but sometimes\
238
             \ safer, drbd secondary changes"
239

    
240
oDynuFile :: OptType
241
oDynuFile = Option "U" ["dynu-file"]
242
            (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
243
            "Import dynamic utilisation data from the given FILE"
244

    
245
oEvacMode :: OptType
246
oEvacMode = Option "E" ["evac-mode"]
247
            (NoArg (\opts -> Ok opts { optEvacMode = True }))
248
            "enable evacuation mode, where the algorithm only moves \
249
            \ instances away from offline and drained nodes"
250

    
251
oExInst :: OptType
252
oExInst = Option "" ["exclude-instances"]
253
          (ReqArg (\ f opts -> Ok opts { optExInst = sepSplit ',' f }) "INSTS")
254
          "exclude given instances from any moves"
255

    
256
oExTags :: OptType
257
oExTags = Option "" ["exclusion-tags"]
258
            (ReqArg (\ f opts -> Ok opts { optExTags = Just $ sepSplit ',' f })
259
             "TAG,...") "Enable instance exclusion based on given tag prefix"
260

    
261
oExecJobs :: OptType
262
oExecJobs = Option "X" ["exec"]
263
             (NoArg (\ opts -> Ok opts { optExecJobs = True}))
264
             "execute the suggested moves via Luxi (only available when using\
265
             \ it for data gathering)"
266

    
267
oGroup :: OptType
268
oGroup = Option "G" ["group"]
269
            (ReqArg (\ f o -> Ok o { optGroup = Just f }) "ID")
270
            "the ID of the group to balance"
271

    
272
oLuxiSocket :: OptType
273
oLuxiSocket = Option "L" ["luxi"]
274
              (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
275
                       fromMaybe defaultLuxiSocket) "SOCKET")
276
              "collect data via Luxi, optionally using the given SOCKET path"
277

    
278
oMachineReadable :: OptType
279
oMachineReadable = Option "" ["machine-readable"]
280
                   (OptArg (\ f opts -> do
281
                     flag <- parseYesNo True f
282
                     return $ opts { optMachineReadable = flag }) "CHOICE")
283
          "enable machine readable output (pass either 'yes' or 'no' to\
284
          \ explicitely control the flag, or without an argument defaults to\
285
          \ yes"
286

    
287
oMaxCpu :: OptType
288
oMaxCpu = Option "" ["max-cpu"]
289
          (ReqArg (\ n opts -> do
290
                     mcpu <- tryRead "parsing max-cpu" n
291
                     when (mcpu <= 0) $
292
                          fail "Invalid value of the max-cpu ratio,\
293
                               \ expected >0"
294
                     return $ opts { optMcpu = Just mcpu }) "RATIO")
295
          "maximum virtual-to-physical cpu ratio for nodes (from 0\
296
          \ upwards) [default read from cluster]"
297

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

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

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

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

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

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

    
330
oNodeSim :: OptType
331
oNodeSim = Option "" ["simulate"]
332
            (ReqArg (\ f o -> Ok o { optNodeSim = f:optNodeSim o }) "SPEC")
333
            "simulate an empty cluster, given as 'num_nodes,disk,ram,cpu'"
334

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

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

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

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

    
359
oPrintNodes :: OptType
360
oPrintNodes = Option "p" ["print-nodes"]
361
              (OptArg ((\ f opts ->
362
                          let (prefix, realf) = case f of
363
                                                  '+':rest -> (["+"], rest)
364
                                                  _ -> ([], f)
365
                              splitted = prefix ++ sepSplit ',' realf
366
                          in Ok opts { optShowNodes = Just splitted }) .
367
                       fromMaybe []) "FIELDS")
368
              "print the final node list"
369

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

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

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

    
385
oShowHelp :: OptType
386
oShowHelp = Option "h" ["help"]
387
            (NoArg (\ opts -> Ok opts { optShowHelp = True}))
388
            "show help"
389

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

    
395
oStdSpec :: OptType
396
oStdSpec = Option "" ["standard-alloc"]
397
             (ReqArg (\ inp opts -> do
398
                        tspec <- parseISpecString "standard" inp
399
                        return $ opts { optStdSpec = Just tspec } )
400
              "STDSPEC")
401
             "enable standard specs allocation, given as 'disk,ram,cpu'"
402

    
403
oTestCount :: OptType
404
oTestCount = Option "" ["test-count"]
405
             (ReqArg (\ inp opts -> do
406
                        tcount <- tryRead "parsing test count" inp
407
                        return $ opts { optTestCount = Just tcount } )
408
              "COUNT")
409
             "override the target test count"
410

    
411
oTieredSpec :: OptType
412
oTieredSpec = Option "" ["tiered-alloc"]
413
             (ReqArg (\ inp opts -> do
414
                        tspec <- parseISpecString "tiered" inp
415
                        return $ opts { optTieredSpec = Just tspec } )
416
              "TSPEC")
417
             "enable tiered specs allocation, given as 'disk,ram,cpu'"
418

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

    
424
oVerbose :: OptType
425
oVerbose = Option "v" ["verbose"]
426
           (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
427
           "increase the verbosity level"
428

    
429
-- * Functions
430

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

    
441
-- | Usage info.
442
usageHelp :: String -> [OptType] -> String
443
usageHelp progname =
444
  usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
445
             progname Version.version progname)
446

    
447
-- | Show the program version info.
448
versionInfo :: String -> String
449
versionInfo progname =
450
  printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
451
         progname Version.version compilerName
452
         (Data.Version.showVersion compilerVersion)
453
         os arch
454

    
455
-- | Command line parser, using the 'Options' structure.
456
parseOpts :: [String]               -- ^ The command line arguments
457
          -> String                 -- ^ The program name
458
          -> [OptType]              -- ^ The supported command line options
459
          -> IO (Options, [String]) -- ^ The resulting options and leftover
460
                                    -- arguments
461
parseOpts argv progname options =
462
  case parseOptsInner argv progname options of
463
    Left (code, msg) -> do
464
      hPutStr (if code == 0 then stdout else stderr) msg
465
      exitWith (if code == 0 then ExitSuccess else ExitFailure code)
466
    Right result ->
467
      return result
468

    
469
-- | Inner parse options. The arguments are similar to 'parseOpts',
470
-- but it returns either a 'Left' composed of exit code and message,
471
-- or a 'Right' for the success case.
472
parseOptsInner :: [String] -> String -> [OptType]
473
               -> Either (Int, String) (Options, [String])
474
parseOptsInner argv progname options =
475
  case getOpt Permute options argv of
476
    (o, n, []) ->
477
      let (pr, args) = (foldM (flip id) defaultOptions o, n)
478
      in case pr of
479
           Bad msg -> Left (1, "Error while parsing command\
480
                               \line arguments:\n" ++ msg ++ "\n")
481
           Ok po ->
482
             select (Right (po, args))
483
                 [ (optShowHelp po, Left (0, usageHelp progname options))
484
                 , (optShowVer po,  Left (0, versionInfo progname))
485
                 ]
486
    (_, _, errs) ->
487
      Left (2, "Command line error: "  ++ concat errs ++ "\n" ++
488
            usageHelp progname options)
489

    
490
-- | A shell script template for autogenerated scripts.
491
shTemplate :: String
492
shTemplate =
493
  printf "#!/bin/sh\n\n\
494
         \# Auto-generated script for executing cluster rebalancing\n\n\
495
         \# To stop, touch the file /tmp/stop-htools\n\n\
496
         \set -e\n\n\
497
         \check() {\n\
498
         \  if [ -f /tmp/stop-htools ]; then\n\
499
         \    echo 'Stop requested, exiting'\n\
500
         \    exit 0\n\
501
         \  fi\n\
502
         \}\n\n"
503

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

    
515

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

    
527
-- | Function to display warning messages from parsing the cluster
528
-- state.
529
maybeShowWarnings :: [String] -- ^ The warning messages
530
                  -> IO ()
531
maybeShowWarnings fix_msgs =
532
  unless (null fix_msgs) $ do
533
    hPutStrLn stderr "Warning: cluster has inconsistent data:"
534
    hPutStrLn stderr . unlines . map (printf "  - %s") $ fix_msgs
535

    
536
-- | Potentially set the node as offline based on passed offline list.
537
setNodeOffline :: [Ndx] -> Node.Node -> Node.Node
538
setNodeOffline offline_indices n =
539
  if Node.idx n `elem` offline_indices
540
    then Node.setOffline n True
541
    else n
542

    
543
-- | Set node properties based on command line options.
544
setNodeStatus :: Options -> Node.List -> IO Node.List
545
setNodeStatus opts fixed_nl = do
546
  let offline_passed = optOffline opts
547
      all_nodes = Container.elems fixed_nl
548
      offline_lkp = map (lookupName (map Node.name all_nodes)) offline_passed
549
      offline_wrong = filter (not . goodLookupResult) offline_lkp
550
      offline_names = map lrContent offline_lkp
551
      offline_indices = map Node.idx $
552
                        filter (\n -> Node.name n `elem` offline_names)
553
                               all_nodes
554
      m_cpu = optMcpu opts
555
      m_dsk = optMdsk opts
556

    
557
  unless (null offline_wrong) $ do
558
         hPrintf stderr "Error: Wrong node name(s) set as offline: %s\n"
559
                     (commaJoin (map lrContent offline_wrong)) :: IO ()
560
         exitWith $ ExitFailure 1
561
  let setMCpuFn = case m_cpu of
562
                    Nothing -> id
563
                    Just new_mcpu -> flip Node.setMcpu new_mcpu
564
  let nm = Container.map (setNodeOffline offline_indices .
565
                          flip Node.setMdsk m_dsk .
566
                          setMCpuFn) fixed_nl
567
  return nm