Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / HTools / CLI.hs @ 313fdabc

History | View | Annotate | Download (21.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.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, 2013 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
  , defaultOptions
34
  , Ganeti.HTools.CLI.parseOpts
35
  , parseOptsInner
36
  , parseYesNo
37
  , parseISpecString
38
  , shTemplate
39
  , maybePrintNodes
40
  , maybePrintInsts
41
  , maybeShowWarnings
42
  , printKeys
43
  , printFinal
44
  , setNodeStatus
45
  -- * The options
46
  , oDataFile
47
  , oDiskMoves
48
  , oDiskTemplate
49
  , oSpindleUse
50
  , oDynuFile
51
  , oEvacMode
52
  , oExInst
53
  , oExTags
54
  , oExecJobs
55
  , oForce
56
  , oGroup
57
  , oIAllocSrc
58
  , oInstMoves
59
  , oJobDelay
60
  , genOLuxiSocket
61
  , oLuxiSocket
62
  , oMachineReadable
63
  , oMaxCpu
64
  , oMaxSolLength
65
  , oMinDisk
66
  , oMinGain
67
  , oMinGainLim
68
  , oMinScore
69
  , oNoHeaders
70
  , oNoSimulation
71
  , oNodeSim
72
  , oNodeTags
73
  , oOfflineNode
74
  , oOutputDir
75
  , oPrintCommands
76
  , oPrintInsts
77
  , oPrintNodes
78
  , oQuiet
79
  , oRapiMaster
80
  , oSaveCluster
81
  , oSelInst
82
  , oShowHelp
83
  , oShowVer
84
  , oShowComp
85
  , oStdSpec
86
  , oTieredSpec
87
  , oVerbose
88
  , oPriority
89
  , genericOpts
90
  ) where
91

    
92
import Control.Monad
93
import Data.Char (toUpper)
94
import Data.Maybe (fromMaybe)
95
import System.Console.GetOpt
96
import System.IO
97
import Text.Printf (printf)
98

    
99
import qualified Ganeti.HTools.Container as Container
100
import qualified Ganeti.HTools.Node as Node
101
import qualified Ganeti.Path as Path
102
import Ganeti.HTools.Types
103
import Ganeti.BasicTypes
104
import Ganeti.Common as Common
105
import Ganeti.Types
106
import Ganeti.Utils
107

    
108
-- * Data types
109

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

    
157
-- | Default values for the command line options.
158
defaultOptions :: Options
159
defaultOptions  = Options
160
  { optDataFile    = Nothing
161
  , optDiskMoves   = True
162
  , optInstMoves   = True
163
  , optDiskTemplate = Nothing
164
  , optSpindleUse  = Nothing
165
  , optDynuFile    = Nothing
166
  , optEvacMode    = False
167
  , optExInst      = []
168
  , optExTags      = Nothing
169
  , optExecJobs    = False
170
  , optForce       = False
171
  , optGroup       = Nothing
172
  , optIAllocSrc   = Nothing
173
  , optSelInst     = []
174
  , optLuxi        = Nothing
175
  , optJobDelay    = 10
176
  , optMachineReadable = False
177
  , optMaster      = ""
178
  , optMaxLength   = -1
179
  , optMcpu        = Nothing
180
  , optMdsk        = defReservedDiskRatio
181
  , optMinGain     = 1e-2
182
  , optMinGainLim  = 1e-1
183
  , optMinScore    = 1e-9
184
  , optNoHeaders   = False
185
  , optNoSimulation = False
186
  , optNodeSim     = []
187
  , optNodeTags    = Nothing
188
  , optOffline     = []
189
  , optOutPath     = "."
190
  , optSaveCluster = Nothing
191
  , optShowCmds    = Nothing
192
  , optShowHelp    = False
193
  , optShowComp    = False
194
  , optShowInsts   = False
195
  , optShowNodes   = Nothing
196
  , optShowVer     = False
197
  , optStdSpec     = Nothing
198
  , optTestCount   = Nothing
199
  , optTieredSpec  = Nothing
200
  , optReplay      = Nothing
201
  , optVerbose     = 1
202
  , optPriority    = Nothing
203
  }
204

    
205
-- | Abbreviation for the option type.
206
type OptType = GenericOptType Options
207

    
208
instance StandardOptions Options where
209
  helpRequested = optShowHelp
210
  verRequested  = optShowVer
211
  compRequested = optShowComp
212
  requestHelp o = o { optShowHelp = True }
213
  requestVer  o = o { optShowVer  = True }
214
  requestComp o = o { optShowComp = True }
215

    
216
-- * Helper functions
217

    
218
parseISpecString :: String -> String -> Result RSpec
219
parseISpecString descr inp = do
220
  let sp = sepSplit ',' inp
221
      err = Bad ("Invalid " ++ descr ++ " specification: '" ++ inp ++
222
                 "', expected disk,ram,cpu")
223
  when (length sp /= 3) err
224
  prs <- mapM (\(fn, val) -> fn val) $
225
         zip [ annotateResult (descr ++ " specs disk") . parseUnit
226
             , annotateResult (descr ++ " specs memory") . parseUnit
227
             , tryRead (descr ++ " specs cpus")
228
             ] sp
229
  case prs of
230
    [dsk, ram, cpu] -> return $ RSpec cpu ram dsk
231
    _ -> err
232

    
233
-- | Disk template choices.
234
optComplDiskTemplate :: OptCompletion
235
optComplDiskTemplate = OptComplChoices $
236
                       map diskTemplateToRaw [minBound..maxBound]
237

    
238
-- * Command line options
239

    
240
oDataFile :: OptType
241
oDataFile =
242
  (Option "t" ["text-data"]
243
   (ReqArg (\ f o -> Ok o { optDataFile = Just f }) "FILE")
244
   "the cluster data FILE",
245
   OptComplFile)
246

    
247
oDiskMoves :: OptType
248
oDiskMoves =
249
  (Option "" ["no-disk-moves"]
250
   (NoArg (\ opts -> Ok opts { optDiskMoves = False}))
251
   "disallow disk moves from the list of allowed instance changes,\
252
   \ thus allowing only the 'cheap' failover/migrate operations",
253
   OptComplNone)
254

    
255
oDiskTemplate :: OptType
256
oDiskTemplate =
257
  (Option "" ["disk-template"]
258
   (reqWithConversion diskTemplateFromRaw
259
    (\dt opts -> Ok opts { optDiskTemplate = Just dt })
260
    "TEMPLATE") "select the desired disk template",
261
   optComplDiskTemplate)
262

    
263
oSpindleUse :: OptType
264
oSpindleUse =
265
  (Option "" ["spindle-use"]
266
   (reqWithConversion (tryRead "parsing spindle-use")
267
    (\su opts -> do
268
       when (su < 0) $
269
            fail "Invalid value of the spindle-use (expected >= 0)"
270
       return $ opts { optSpindleUse = Just su })
271
    "SPINDLES") "select how many virtual spindle instances use\
272
                \ [default read from cluster]",
273
   OptComplFloat)
274

    
275
oSelInst :: OptType
276
oSelInst =
277
  (Option "" ["select-instances"]
278
   (ReqArg (\ f opts -> Ok opts { optSelInst = sepSplit ',' f }) "INSTS")
279
   "only select given instances for any moves",
280
   OptComplManyInstances)
281

    
282
oInstMoves :: OptType
283
oInstMoves =
284
  (Option "" ["no-instance-moves"]
285
   (NoArg (\ opts -> Ok opts { optInstMoves = False}))
286
   "disallow instance (primary node) moves from the list of allowed,\
287
   \ instance changes, thus allowing only slower, but sometimes\
288
   \ safer, drbd secondary changes",
289
   OptComplNone)
290

    
291
oDynuFile :: OptType
292
oDynuFile =
293
  (Option "U" ["dynu-file"]
294
   (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
295
   "Import dynamic utilisation data from the given FILE",
296
   OptComplFile)
297

    
298
oEvacMode :: OptType
299
oEvacMode =
300
  (Option "E" ["evac-mode"]
301
   (NoArg (\opts -> Ok opts { optEvacMode = True }))
302
   "enable evacuation mode, where the algorithm only moves\
303
   \ instances away from offline and drained nodes",
304
   OptComplNone)
305

    
306
oExInst :: OptType
307
oExInst =
308
  (Option "" ["exclude-instances"]
309
   (ReqArg (\ f opts -> Ok opts { optExInst = sepSplit ',' f }) "INSTS")
310
   "exclude given instances from any moves",
311
   OptComplManyInstances)
312

    
313
oExTags :: OptType
314
oExTags =
315
  (Option "" ["exclusion-tags"]
316
   (ReqArg (\ f opts -> Ok opts { optExTags = Just $ sepSplit ',' f })
317
    "TAG,...") "Enable instance exclusion based on given tag prefix",
318
   OptComplString)
319

    
320
oExecJobs :: OptType
321
oExecJobs =
322
  (Option "X" ["exec"]
323
   (NoArg (\ opts -> Ok opts { optExecJobs = True}))
324
   "execute the suggested moves via Luxi (only available when using\
325
   \ it for data gathering)",
326
   OptComplNone)
327

    
328
oForce :: OptType
329
oForce =
330
  (Option "f" ["force"]
331
   (NoArg (\ opts -> Ok opts {optForce = True}))
332
   "force the execution of this program, even if warnings would\
333
   \ otherwise prevent it",
334
   OptComplNone)
335

    
336
oGroup :: OptType
337
oGroup =
338
  (Option "G" ["group"]
339
   (ReqArg (\ f o -> Ok o { optGroup = Just f }) "ID")
340
   "the target node group (name or UUID)",
341
   OptComplOneGroup)
342

    
343
oIAllocSrc :: OptType
344
oIAllocSrc =
345
  (Option "I" ["ialloc-src"]
346
   (ReqArg (\ f opts -> Ok opts { optIAllocSrc = Just f }) "FILE")
347
   "Specify an iallocator spec as the cluster data source",
348
   OptComplFile)
349

    
350
oJobDelay :: OptType
351
oJobDelay =
352
  (Option "" ["job-delay"]
353
   (reqWithConversion (tryRead "job delay")
354
    (\d opts -> Ok opts { optJobDelay = d }) "SECONDS")
355
   "insert this much delay before the execution of repair jobs\
356
   \ to allow the tool to continue processing instances",
357
   OptComplFloat)
358

    
359
genOLuxiSocket :: String -> OptType
360
genOLuxiSocket defSocket =
361
  (Option "L" ["luxi"]
362
   (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
363
            fromMaybe defSocket) "SOCKET")
364
   ("collect data via Luxi, optionally using the given SOCKET path [" ++
365
    defSocket ++ "]"),
366
   OptComplFile)
367

    
368
oLuxiSocket :: IO OptType
369
oLuxiSocket = liftM genOLuxiSocket Path.defaultLuxiSocket
370

    
371
oMachineReadable :: OptType
372
oMachineReadable =
373
  (Option "" ["machine-readable"]
374
   (OptArg (\ f opts -> do
375
              flag <- parseYesNo True f
376
              return $ opts { optMachineReadable = flag }) "CHOICE")
377
   "enable machine readable output (pass either 'yes' or 'no' to\
378
   \ explicitly control the flag, or without an argument defaults to\
379
   \ yes",
380
   optComplYesNo)
381

    
382
oMaxCpu :: OptType
383
oMaxCpu =
384
  (Option "" ["max-cpu"]
385
   (reqWithConversion (tryRead "parsing max-cpu")
386
    (\mcpu opts -> do
387
       when (mcpu <= 0) $
388
            fail "Invalid value of the max-cpu ratio, expected >0"
389
       return $ opts { optMcpu = Just mcpu }) "RATIO")
390
   "maximum virtual-to-physical cpu ratio for nodes (from 0\
391
   \ upwards) [default read from cluster]",
392
   OptComplFloat)
393

    
394
oMaxSolLength :: OptType
395
oMaxSolLength =
396
  (Option "l" ["max-length"]
397
   (reqWithConversion (tryRead "max solution length")
398
    (\i opts -> Ok opts { optMaxLength = i }) "N")
399
   "cap the solution at this many balancing or allocation\
400
   \ rounds (useful for very unbalanced clusters or empty\
401
   \ clusters)",
402
   OptComplInteger)
403

    
404
oMinDisk :: OptType
405
oMinDisk =
406
  (Option "" ["min-disk"]
407
   (reqWithConversion (tryRead "min free disk space")
408
    (\n opts -> Ok opts { optMdsk = n }) "RATIO")
409
   "minimum free disk space for nodes (between 0 and 1) [0]",
410
   OptComplFloat)
411

    
412
oMinGain :: OptType
413
oMinGain =
414
  (Option "g" ["min-gain"]
415
   (reqWithConversion (tryRead "min gain")
416
    (\g opts -> Ok opts { optMinGain = g }) "DELTA")
417
   "minimum gain to aim for in a balancing step before giving up",
418
   OptComplFloat)
419

    
420
oMinGainLim :: OptType
421
oMinGainLim =
422
  (Option "" ["min-gain-limit"]
423
   (reqWithConversion (tryRead "min gain limit")
424
    (\g opts -> Ok opts { optMinGainLim = g }) "SCORE")
425
   "minimum cluster score for which we start checking the min-gain",
426
   OptComplFloat)
427

    
428
oMinScore :: OptType
429
oMinScore =
430
  (Option "e" ["min-score"]
431
   (reqWithConversion (tryRead "min score")
432
    (\e opts -> Ok opts { optMinScore = e }) "EPSILON")
433
   "mininum score to aim for",
434
   OptComplFloat)
435

    
436
oNoHeaders :: OptType
437
oNoHeaders =
438
  (Option "" ["no-headers"]
439
   (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
440
   "do not show a header line",
441
   OptComplNone)
442

    
443
oNoSimulation :: OptType
444
oNoSimulation =
445
  (Option "" ["no-simulation"]
446
   (NoArg (\opts -> Ok opts {optNoSimulation = True}))
447
   "do not perform rebalancing simulation",
448
   OptComplNone)
449

    
450
oNodeSim :: OptType
451
oNodeSim =
452
  (Option "" ["simulate"]
453
   (ReqArg (\ f o -> Ok o { optNodeSim = f:optNodeSim o }) "SPEC")
454
   "simulate an empty cluster, given as\
455
   \ 'alloc_policy,num_nodes,disk,ram,cpu'",
456
   OptComplString)
457

    
458
oNodeTags :: OptType
459
oNodeTags =
460
  (Option "" ["node-tags"]
461
   (ReqArg (\ f opts -> Ok opts { optNodeTags = Just $ sepSplit ',' f })
462
    "TAG,...") "Restrict to nodes with the given tags",
463
   OptComplString)
464
     
465
oOfflineNode :: OptType
466
oOfflineNode =
467
  (Option "O" ["offline"]
468
   (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
469
   "set node as offline",
470
   OptComplOneNode)
471

    
472
oOutputDir :: OptType
473
oOutputDir =
474
  (Option "d" ["output-dir"]
475
   (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
476
   "directory in which to write output files",
477
   OptComplDir)
478

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

    
490
oPrintInsts :: OptType
491
oPrintInsts =
492
  (Option "" ["print-instances"]
493
   (NoArg (\ opts -> Ok opts { optShowInsts = True }))
494
   "print the final instance map",
495
   OptComplNone)
496

    
497
oPrintNodes :: OptType
498
oPrintNodes =
499
  (Option "p" ["print-nodes"]
500
   (OptArg ((\ f opts ->
501
               let (prefix, realf) = case f of
502
                                       '+':rest -> (["+"], rest)
503
                                       _ -> ([], f)
504
                   splitted = prefix ++ sepSplit ',' realf
505
               in Ok opts { optShowNodes = Just splitted }) .
506
            fromMaybe []) "FIELDS")
507
   "print the final node list",
508
   OptComplNone)
509

    
510
oQuiet :: OptType
511
oQuiet =
512
  (Option "q" ["quiet"]
513
   (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
514
   "decrease the verbosity level",
515
   OptComplNone)
516

    
517
oRapiMaster :: OptType
518
oRapiMaster =
519
  (Option "m" ["master"]
520
   (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
521
   "collect data via RAPI at the given ADDRESS",
522
   OptComplHost)
523

    
524
oSaveCluster :: OptType
525
oSaveCluster =
526
  (Option "S" ["save"]
527
   (ReqArg (\ f opts -> Ok opts { optSaveCluster = Just f }) "FILE")
528
   "Save cluster state at the end of the processing to FILE",
529
   OptComplNone)
530

    
531
oStdSpec :: OptType
532
oStdSpec =
533
  (Option "" ["standard-alloc"]
534
   (ReqArg (\ inp opts -> do
535
              tspec <- parseISpecString "standard" inp
536
              return $ opts { optStdSpec = Just tspec } )
537
    "STDSPEC")
538
   "enable standard specs allocation, given as 'disk,ram,cpu'",
539
   OptComplString)
540

    
541
oTieredSpec :: OptType
542
oTieredSpec =
543
  (Option "" ["tiered-alloc"]
544
   (ReqArg (\ inp opts -> do
545
              tspec <- parseISpecString "tiered" inp
546
              return $ opts { optTieredSpec = Just tspec } )
547
    "TSPEC")
548
   "enable tiered specs allocation, given as 'disk,ram,cpu'",
549
   OptComplString)
550

    
551
oVerbose :: OptType
552
oVerbose =
553
  (Option "v" ["verbose"]
554
   (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
555
   "increase the verbosity level",
556
   OptComplNone)
557

    
558
oPriority :: OptType
559
oPriority =
560
  (Option "" ["priority"]
561
   (ReqArg (\ inp opts -> do
562
              prio <- parseSubmitPriority inp
563
              Ok opts { optPriority = Just prio }) "PRIO")
564
   "set the priority of submitted jobs",
565
    OptComplChoices (map fmtSubmitPriority [minBound..maxBound]))
566

    
567
-- | Generic options.
568
genericOpts :: [GenericOptType Options]
569
genericOpts =  [ oShowVer
570
               , oShowHelp
571
               , oShowComp
572
               ]
573

    
574
-- * Functions
575

    
576
-- | Wrapper over 'Common.parseOpts' with our custom options.
577
parseOpts :: [String]               -- ^ The command line arguments
578
          -> String                 -- ^ The program name
579
          -> [OptType]              -- ^ The supported command line options
580
          -> [ArgCompletion]        -- ^ The supported command line arguments
581
          -> IO (Options, [String]) -- ^ The resulting options and leftover
582
                                    -- arguments
583
parseOpts = Common.parseOpts defaultOptions
584

    
585

    
586
-- | A shell script template for autogenerated scripts.
587
shTemplate :: String
588
shTemplate =
589
  printf "#!/bin/sh\n\n\
590
         \# Auto-generated script for executing cluster rebalancing\n\n\
591
         \# To stop, touch the file /tmp/stop-htools\n\n\
592
         \set -e\n\n\
593
         \check() {\n\
594
         \  if [ -f /tmp/stop-htools ]; then\n\
595
         \    echo 'Stop requested, exiting'\n\
596
         \    exit 0\n\
597
         \  fi\n\
598
         \}\n\n"
599

    
600
-- | Optionally print the node list.
601
maybePrintNodes :: Maybe [String]       -- ^ The field list
602
                -> String               -- ^ Informational message
603
                -> ([String] -> String) -- ^ Function to generate the listing
604
                -> IO ()
605
maybePrintNodes Nothing _ _ = return ()
606
maybePrintNodes (Just fields) msg fn = do
607
  hPutStrLn stderr ""
608
  hPutStrLn stderr (msg ++ " status:")
609
  hPutStrLn stderr $ fn fields
610

    
611
-- | Optionally print the instance list.
612
maybePrintInsts :: Bool   -- ^ Whether to print the instance list
613
                -> String -- ^ Type of the instance map (e.g. initial)
614
                -> String -- ^ The instance data
615
                -> IO ()
616
maybePrintInsts do_print msg instdata =
617
  when do_print $ do
618
    hPutStrLn stderr ""
619
    hPutStrLn stderr $ msg ++ " instance map:"
620
    hPutStr stderr instdata
621

    
622
-- | Function to display warning messages from parsing the cluster
623
-- state.
624
maybeShowWarnings :: [String] -- ^ The warning messages
625
                  -> IO ()
626
maybeShowWarnings fix_msgs =
627
  unless (null fix_msgs) $ do
628
    hPutStrLn stderr "Warning: cluster has inconsistent data:"
629
    hPutStrLn stderr . unlines . map (printf "  - %s") $ fix_msgs
630

    
631
-- | Format a list of key, value as a shell fragment.
632
printKeys :: String              -- ^ Prefix to printed variables
633
          -> [(String, String)]  -- ^ List of (key, value) pairs to be printed
634
          -> IO ()
635
printKeys prefix =
636
  mapM_ (\(k, v) ->
637
           printf "%s_%s=%s\n" prefix (map toUpper k) (ensureQuoted v))
638

    
639
-- | Prints the final @OK@ marker in machine readable output.
640
printFinal :: String    -- ^ Prefix to printed variable
641
           -> Bool      -- ^ Whether output should be machine readable;
642
                        -- note: if not, there is nothing to print
643
           -> IO ()
644
printFinal prefix True =
645
  -- this should be the final entry
646
  printKeys prefix [("OK", "1")]
647

    
648
printFinal _ False = return ()
649

    
650
-- | Potentially set the node as offline based on passed offline list.
651
setNodeOffline :: [Ndx] -> Node.Node -> Node.Node
652
setNodeOffline offline_indices n =
653
  if Node.idx n `elem` offline_indices
654
    then Node.setOffline n True
655
    else n
656

    
657
-- | Set node properties based on command line options.
658
setNodeStatus :: Options -> Node.List -> IO Node.List
659
setNodeStatus opts fixed_nl = do
660
  let offline_passed = optOffline opts
661
      all_nodes = Container.elems fixed_nl
662
      offline_lkp = map (lookupName (map Node.name all_nodes)) offline_passed
663
      offline_wrong = filter (not . goodLookupResult) offline_lkp
664
      offline_names = map lrContent offline_lkp
665
      offline_indices = map Node.idx $
666
                        filter (\n -> Node.name n `elem` offline_names)
667
                               all_nodes
668
      m_cpu = optMcpu opts
669
      m_dsk = optMdsk opts
670

    
671
  unless (null offline_wrong) .
672
         exitErr $ printf "wrong node name(s) set as offline: %s\n"
673
                   (commaJoin (map lrContent offline_wrong))
674
  let setMCpuFn = case m_cpu of
675
                    Nothing -> id
676
                    Just new_mcpu -> flip Node.setMcpu new_mcpu
677
  let nm = Container.map (setNodeOffline offline_indices .
678
                          flip Node.setMdsk m_dsk .
679
                          setMCpuFn) fixed_nl
680
  return nm