Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / HTools / CLI.hs @ a12b230c

History | View | Annotate | Download (24.1 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
  , oFullEvacuation
57
  , oGroup
58
  , oIAllocSrc
59
  , oIgnoreNonRedundant
60
  , oInstMoves
61
  , oJobDelay
62
  , genOLuxiSocket
63
  , oLuxiSocket
64
  , oMachineReadable
65
  , oMaxCpu
66
  , oMaxSolLength
67
  , oMinDisk
68
  , oMinGain
69
  , oMinGainLim
70
  , oMinScore
71
  , oNoHeaders
72
  , oNoSimulation
73
  , oNodeSim
74
  , oNodeTags
75
  , oOfflineMaintenance
76
  , oOfflineNode
77
  , oOneStepOnly
78
  , oOutputDir
79
  , oPrintCommands
80
  , oPrintInsts
81
  , oPrintMoves
82
  , oPrintNodes
83
  , oQuiet
84
  , oRapiMaster
85
  , oSaveCluster
86
  , oSelInst
87
  , oShowHelp
88
  , oShowVer
89
  , oShowComp
90
  , oSkipNonRedundant
91
  , oStdSpec
92
  , oTieredSpec
93
  , oVerbose
94
  , oPriority
95
  , genericOpts
96
  ) where
97

    
98
import Control.Monad
99
import Data.Char (toUpper)
100
import Data.Maybe (fromMaybe)
101
import System.Console.GetOpt
102
import System.IO
103
import Text.Printf (printf)
104

    
105
import qualified Ganeti.HTools.Container as Container
106
import qualified Ganeti.HTools.Node as Node
107
import qualified Ganeti.Path as Path
108
import Ganeti.HTools.Types
109
import Ganeti.BasicTypes
110
import Ganeti.Common as Common
111
import Ganeti.Types
112
import Ganeti.Utils
113

    
114
-- * Data types
115

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

    
169
-- | Default values for the command line options.
170
defaultOptions :: Options
171
defaultOptions  = Options
172
  { optDataFile    = Nothing
173
  , optDiskMoves   = True
174
  , optInstMoves   = True
175
  , optDiskTemplate = Nothing
176
  , optSpindleUse  = Nothing
177
  , optDynuFile    = Nothing
178
  , optEvacMode    = False
179
  , optExInst      = []
180
  , optExTags      = Nothing
181
  , optExecJobs    = False
182
  , optForce       = False
183
  , optFullEvacuation = False
184
  , optGroup       = Nothing
185
  , optIAllocSrc   = Nothing
186
  , optIgnoreNonRedundant = False
187
  , optSelInst     = []
188
  , optLuxi        = Nothing
189
  , optJobDelay    = 10
190
  , optMachineReadable = False
191
  , optMaster      = ""
192
  , optMaxLength   = -1
193
  , optMcpu        = Nothing
194
  , optMdsk        = defReservedDiskRatio
195
  , optMinGain     = 1e-2
196
  , optMinGainLim  = 1e-1
197
  , optMinScore    = 1e-9
198
  , optNoHeaders   = False
199
  , optNoSimulation = False
200
  , optNodeSim     = []
201
  , optNodeTags    = Nothing
202
  , optSkipNonRedundant = False
203
  , optOffline     = []
204
  , optOfflineMaintenance = False
205
  , optOneStepOnly = False
206
  , optOutPath     = "."
207
  , optPrintMoves  = False
208
  , optSaveCluster = Nothing
209
  , optShowCmds    = Nothing
210
  , optShowHelp    = False
211
  , optShowComp    = False
212
  , optShowInsts   = False
213
  , optShowNodes   = Nothing
214
  , optShowVer     = False
215
  , optStdSpec     = Nothing
216
  , optTestCount   = Nothing
217
  , optTieredSpec  = Nothing
218
  , optReplay      = Nothing
219
  , optVerbose     = 1
220
  , optPriority    = Nothing
221
  }
222

    
223
-- | Abbreviation for the option type.
224
type OptType = GenericOptType Options
225

    
226
instance StandardOptions Options where
227
  helpRequested = optShowHelp
228
  verRequested  = optShowVer
229
  compRequested = optShowComp
230
  requestHelp o = o { optShowHelp = True }
231
  requestVer  o = o { optShowVer  = True }
232
  requestComp o = o { optShowComp = True }
233

    
234
-- * Helper functions
235

    
236
parseISpecString :: String -> String -> Result RSpec
237
parseISpecString descr inp = do
238
  let sp = sepSplit ',' inp
239
      err = Bad ("Invalid " ++ descr ++ " specification: '" ++ inp ++
240
                 "', expected disk,ram,cpu")
241
  when (length sp < 3 || length sp > 4) err
242
  prs <- mapM (\(fn, val) -> fn val) $
243
         zip [ annotateResult (descr ++ " specs disk") . parseUnit
244
             , annotateResult (descr ++ " specs memory") . parseUnit
245
             , tryRead (descr ++ " specs cpus")
246
             , tryRead (descr ++ " specs spindles")
247
             ] sp
248
  case prs of
249
    {- Spindles are optional, so that they are not needed when exclusive storage
250
       is disabled. When exclusive storage is disabled, spindles are ignored,
251
       so the actual value doesn't matter. We use 1 as a default so that in
252
       case someone forgets and exclusive storage is enabled, we don't run into
253
       weird situations. -}
254
    [dsk, ram, cpu] -> return $ RSpec cpu ram dsk 1
255
    [dsk, ram, cpu, spn] -> return $ RSpec cpu ram dsk spn
256
    _ -> err
257

    
258
-- | Disk template choices.
259
optComplDiskTemplate :: OptCompletion
260
optComplDiskTemplate = OptComplChoices $
261
                       map diskTemplateToRaw [minBound..maxBound]
262

    
263
-- * Command line options
264

    
265
oDataFile :: OptType
266
oDataFile =
267
  (Option "t" ["text-data"]
268
   (ReqArg (\ f o -> Ok o { optDataFile = Just f }) "FILE")
269
   "the cluster data FILE",
270
   OptComplFile)
271

    
272
oDiskMoves :: OptType
273
oDiskMoves =
274
  (Option "" ["no-disk-moves"]
275
   (NoArg (\ opts -> Ok opts { optDiskMoves = False}))
276
   "disallow disk moves from the list of allowed instance changes,\
277
   \ thus allowing only the 'cheap' failover/migrate operations",
278
   OptComplNone)
279

    
280
oDiskTemplate :: OptType
281
oDiskTemplate =
282
  (Option "" ["disk-template"]
283
   (reqWithConversion diskTemplateFromRaw
284
    (\dt opts -> Ok opts { optDiskTemplate = Just dt })
285
    "TEMPLATE") "select the desired disk template",
286
   optComplDiskTemplate)
287

    
288
oSpindleUse :: OptType
289
oSpindleUse =
290
  (Option "" ["spindle-use"]
291
   (reqWithConversion (tryRead "parsing spindle-use")
292
    (\su opts -> do
293
       when (su < 0) $
294
            fail "Invalid value of the spindle-use (expected >= 0)"
295
       return $ opts { optSpindleUse = Just su })
296
    "SPINDLES") "select how many virtual spindle instances use\
297
                \ [default read from cluster]",
298
   OptComplFloat)
299

    
300
oSelInst :: OptType
301
oSelInst =
302
  (Option "" ["select-instances"]
303
   (ReqArg (\ f opts -> Ok opts { optSelInst = sepSplit ',' f }) "INSTS")
304
   "only select given instances for any moves",
305
   OptComplManyInstances)
306

    
307
oInstMoves :: OptType
308
oInstMoves =
309
  (Option "" ["no-instance-moves"]
310
   (NoArg (\ opts -> Ok opts { optInstMoves = False}))
311
   "disallow instance (primary node) moves from the list of allowed,\
312
   \ instance changes, thus allowing only slower, but sometimes\
313
   \ safer, drbd secondary changes",
314
   OptComplNone)
315

    
316
oDynuFile :: OptType
317
oDynuFile =
318
  (Option "U" ["dynu-file"]
319
   (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
320
   "Import dynamic utilisation data from the given FILE",
321
   OptComplFile)
322

    
323
oEvacMode :: OptType
324
oEvacMode =
325
  (Option "E" ["evac-mode"]
326
   (NoArg (\opts -> Ok opts { optEvacMode = True }))
327
   "enable evacuation mode, where the algorithm only moves\
328
   \ instances away from offline and drained nodes",
329
   OptComplNone)
330

    
331
oExInst :: OptType
332
oExInst =
333
  (Option "" ["exclude-instances"]
334
   (ReqArg (\ f opts -> Ok opts { optExInst = sepSplit ',' f }) "INSTS")
335
   "exclude given instances from any moves",
336
   OptComplManyInstances)
337

    
338
oExTags :: OptType
339
oExTags =
340
  (Option "" ["exclusion-tags"]
341
   (ReqArg (\ f opts -> Ok opts { optExTags = Just $ sepSplit ',' f })
342
    "TAG,...") "Enable instance exclusion based on given tag prefix",
343
   OptComplString)
344

    
345
oExecJobs :: OptType
346
oExecJobs =
347
  (Option "X" ["exec"]
348
   (NoArg (\ opts -> Ok opts { optExecJobs = True}))
349
   "execute the suggested moves via Luxi (only available when using\
350
   \ it for data gathering)",
351
   OptComplNone)
352

    
353
oForce :: OptType
354
oForce =
355
  (Option "f" ["force"]
356
   (NoArg (\ opts -> Ok opts {optForce = True}))
357
   "force the execution of this program, even if warnings would\
358
   \ otherwise prevent it",
359
   OptComplNone)
360

    
361
oFullEvacuation :: OptType
362
oFullEvacuation =
363
  (Option "" ["full-evacuation"]
364
   (NoArg (\ opts -> Ok opts { optFullEvacuation = True}))
365
   "fully evacuate the nodes to be rebooted",
366
   OptComplNone)
367

    
368
oGroup :: OptType
369
oGroup =
370
  (Option "G" ["group"]
371
   (ReqArg (\ f o -> Ok o { optGroup = Just f }) "ID")
372
   "the target node group (name or UUID)",
373
   OptComplOneGroup)
374

    
375
oIAllocSrc :: OptType
376
oIAllocSrc =
377
  (Option "I" ["ialloc-src"]
378
   (ReqArg (\ f opts -> Ok opts { optIAllocSrc = Just f }) "FILE")
379
   "Specify an iallocator spec as the cluster data source",
380
   OptComplFile)
381

    
382
oIgnoreNonRedundant :: OptType
383
oIgnoreNonRedundant =
384
  (Option "" ["ignore-non-redundant"]
385
   (NoArg (\ opts -> Ok opts { optIgnoreNonRedundant = True }))
386
    "Pretend that there are no non-redundant instances in the cluster",
387
    OptComplNone)
388

    
389
oJobDelay :: OptType
390
oJobDelay =
391
  (Option "" ["job-delay"]
392
   (reqWithConversion (tryRead "job delay")
393
    (\d opts -> Ok opts { optJobDelay = d }) "SECONDS")
394
   "insert this much delay before the execution of repair jobs\
395
   \ to allow the tool to continue processing instances",
396
   OptComplFloat)
397

    
398
genOLuxiSocket :: String -> OptType
399
genOLuxiSocket defSocket =
400
  (Option "L" ["luxi"]
401
   (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
402
            fromMaybe defSocket) "SOCKET")
403
   ("collect data via Luxi, optionally using the given SOCKET path [" ++
404
    defSocket ++ "]"),
405
   OptComplFile)
406

    
407
oLuxiSocket :: IO OptType
408
oLuxiSocket = liftM genOLuxiSocket Path.defaultLuxiSocket
409

    
410
oMachineReadable :: OptType
411
oMachineReadable =
412
  (Option "" ["machine-readable"]
413
   (OptArg (\ f opts -> do
414
              flag <- parseYesNo True f
415
              return $ opts { optMachineReadable = flag }) "CHOICE")
416
   "enable machine readable output (pass either 'yes' or 'no' to\
417
   \ explicitly control the flag, or without an argument defaults to\
418
   \ yes)",
419
   optComplYesNo)
420

    
421
oMaxCpu :: OptType
422
oMaxCpu =
423
  (Option "" ["max-cpu"]
424
   (reqWithConversion (tryRead "parsing max-cpu")
425
    (\mcpu opts -> do
426
       when (mcpu <= 0) $
427
            fail "Invalid value of the max-cpu ratio, expected >0"
428
       return $ opts { optMcpu = Just mcpu }) "RATIO")
429
   "maximum virtual-to-physical cpu ratio for nodes (from 0\
430
   \ upwards) [default read from cluster]",
431
   OptComplFloat)
432

    
433
oMaxSolLength :: OptType
434
oMaxSolLength =
435
  (Option "l" ["max-length"]
436
   (reqWithConversion (tryRead "max solution length")
437
    (\i opts -> Ok opts { optMaxLength = i }) "N")
438
   "cap the solution at this many balancing or allocation\
439
   \ rounds (useful for very unbalanced clusters or empty\
440
   \ clusters)",
441
   OptComplInteger)
442

    
443
oMinDisk :: OptType
444
oMinDisk =
445
  (Option "" ["min-disk"]
446
   (reqWithConversion (tryRead "min free disk space")
447
    (\n opts -> Ok opts { optMdsk = n }) "RATIO")
448
   "minimum free disk space for nodes (between 0 and 1) [0]",
449
   OptComplFloat)
450

    
451
oMinGain :: OptType
452
oMinGain =
453
  (Option "g" ["min-gain"]
454
   (reqWithConversion (tryRead "min gain")
455
    (\g opts -> Ok opts { optMinGain = g }) "DELTA")
456
   "minimum gain to aim for in a balancing step before giving up",
457
   OptComplFloat)
458

    
459
oMinGainLim :: OptType
460
oMinGainLim =
461
  (Option "" ["min-gain-limit"]
462
   (reqWithConversion (tryRead "min gain limit")
463
    (\g opts -> Ok opts { optMinGainLim = g }) "SCORE")
464
   "minimum cluster score for which we start checking the min-gain",
465
   OptComplFloat)
466

    
467
oMinScore :: OptType
468
oMinScore =
469
  (Option "e" ["min-score"]
470
   (reqWithConversion (tryRead "min score")
471
    (\e opts -> Ok opts { optMinScore = e }) "EPSILON")
472
   "mininum score to aim for",
473
   OptComplFloat)
474

    
475
oNoHeaders :: OptType
476
oNoHeaders =
477
  (Option "" ["no-headers"]
478
   (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
479
   "do not show a header line",
480
   OptComplNone)
481

    
482
oNoSimulation :: OptType
483
oNoSimulation =
484
  (Option "" ["no-simulation"]
485
   (NoArg (\opts -> Ok opts {optNoSimulation = True}))
486
   "do not perform rebalancing simulation",
487
   OptComplNone)
488

    
489
oNodeSim :: OptType
490
oNodeSim =
491
  (Option "" ["simulate"]
492
   (ReqArg (\ f o -> Ok o { optNodeSim = f:optNodeSim o }) "SPEC")
493
   "simulate an empty cluster, given as\
494
   \ 'alloc_policy,num_nodes,disk,ram,cpu'",
495
   OptComplString)
496

    
497
oNodeTags :: OptType
498
oNodeTags =
499
  (Option "" ["node-tags"]
500
   (ReqArg (\ f opts -> Ok opts { optNodeTags = Just $ sepSplit ',' f })
501
    "TAG,...") "Restrict to nodes with the given tags",
502
   OptComplString)
503
     
504
oOfflineMaintenance :: OptType
505
oOfflineMaintenance =
506
  (Option "" ["offline-maintenance"]
507
   (NoArg (\ opts -> Ok opts {optOfflineMaintenance = True}))
508
   "Schedule offline maintenance, i.e., pretend that all instance are\
509
   \ offline.",
510
   OptComplNone)
511

    
512
oOfflineNode :: OptType
513
oOfflineNode =
514
  (Option "O" ["offline"]
515
   (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
516
   "set node as offline",
517
   OptComplOneNode)
518

    
519
oOneStepOnly :: OptType
520
oOneStepOnly =
521
  (Option "" ["one-step-only"]
522
   (NoArg (\ opts -> Ok opts {optOneStepOnly = True}))
523
   "Only do the first step",
524
   OptComplNone)
525

    
526
oOutputDir :: OptType
527
oOutputDir =
528
  (Option "d" ["output-dir"]
529
   (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
530
   "directory in which to write output files",
531
   OptComplDir)
532

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

    
544
oPrintInsts :: OptType
545
oPrintInsts =
546
  (Option "" ["print-instances"]
547
   (NoArg (\ opts -> Ok opts { optShowInsts = True }))
548
   "print the final instance map",
549
   OptComplNone)
550

    
551
oPrintMoves :: OptType
552
oPrintMoves =
553
  (Option "" ["print-moves"]
554
   (NoArg (\ opts -> Ok opts { optPrintMoves = True }))
555
   "print the moves of the instances",
556
   OptComplNone)
557

    
558
oPrintNodes :: OptType
559
oPrintNodes =
560
  (Option "p" ["print-nodes"]
561
   (OptArg ((\ f opts ->
562
               let (prefix, realf) = case f of
563
                                       '+':rest -> (["+"], rest)
564
                                       _ -> ([], f)
565
                   splitted = prefix ++ sepSplit ',' realf
566
               in Ok opts { optShowNodes = Just splitted }) .
567
            fromMaybe []) "FIELDS")
568
   "print the final node list",
569
   OptComplNone)
570

    
571
oQuiet :: OptType
572
oQuiet =
573
  (Option "q" ["quiet"]
574
   (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
575
   "decrease the verbosity level",
576
   OptComplNone)
577

    
578
oRapiMaster :: OptType
579
oRapiMaster =
580
  (Option "m" ["master"]
581
   (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
582
   "collect data via RAPI at the given ADDRESS",
583
   OptComplHost)
584

    
585
oSaveCluster :: OptType
586
oSaveCluster =
587
  (Option "S" ["save"]
588
   (ReqArg (\ f opts -> Ok opts { optSaveCluster = Just f }) "FILE")
589
   "Save cluster state at the end of the processing to FILE",
590
   OptComplNone)
591

    
592
oSkipNonRedundant :: OptType
593
oSkipNonRedundant =
594
  (Option "" ["skip-non-redundant"]
595
   (NoArg (\ opts -> Ok opts { optSkipNonRedundant = True }))
596
    "Skip nodes that host a non-redundant instance",
597
    OptComplNone)
598

    
599
oStdSpec :: OptType
600
oStdSpec =
601
  (Option "" ["standard-alloc"]
602
   (ReqArg (\ inp opts -> do
603
              tspec <- parseISpecString "standard" inp
604
              return $ opts { optStdSpec = Just tspec } )
605
    "STDSPEC")
606
   "enable standard specs allocation, given as 'disk,ram,cpu'",
607
   OptComplString)
608

    
609
oTieredSpec :: OptType
610
oTieredSpec =
611
  (Option "" ["tiered-alloc"]
612
   (ReqArg (\ inp opts -> do
613
              tspec <- parseISpecString "tiered" inp
614
              return $ opts { optTieredSpec = Just tspec } )
615
    "TSPEC")
616
   "enable tiered specs allocation, given as 'disk,ram,cpu'",
617
   OptComplString)
618

    
619
oVerbose :: OptType
620
oVerbose =
621
  (Option "v" ["verbose"]
622
   (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
623
   "increase the verbosity level",
624
   OptComplNone)
625

    
626
oPriority :: OptType
627
oPriority =
628
  (Option "" ["priority"]
629
   (ReqArg (\ inp opts -> do
630
              prio <- parseSubmitPriority inp
631
              Ok opts { optPriority = Just prio }) "PRIO")
632
   "set the priority of submitted jobs",
633
    OptComplChoices (map fmtSubmitPriority [minBound..maxBound]))
634

    
635
-- | Generic options.
636
genericOpts :: [GenericOptType Options]
637
genericOpts =  [ oShowVer
638
               , oShowHelp
639
               , oShowComp
640
               ]
641

    
642
-- * Functions
643

    
644
-- | Wrapper over 'Common.parseOpts' with our custom options.
645
parseOpts :: [String]               -- ^ The command line arguments
646
          -> String                 -- ^ The program name
647
          -> [OptType]              -- ^ The supported command line options
648
          -> [ArgCompletion]        -- ^ The supported command line arguments
649
          -> IO (Options, [String]) -- ^ The resulting options and leftover
650
                                    -- arguments
651
parseOpts = Common.parseOpts defaultOptions
652

    
653

    
654
-- | A shell script template for autogenerated scripts.
655
shTemplate :: String
656
shTemplate =
657
  printf "#!/bin/sh\n\n\
658
         \# Auto-generated script for executing cluster rebalancing\n\n\
659
         \# To stop, touch the file /tmp/stop-htools\n\n\
660
         \set -e\n\n\
661
         \check() {\n\
662
         \  if [ -f /tmp/stop-htools ]; then\n\
663
         \    echo 'Stop requested, exiting'\n\
664
         \    exit 0\n\
665
         \  fi\n\
666
         \}\n\n"
667

    
668
-- | Optionally print the node list.
669
maybePrintNodes :: Maybe [String]       -- ^ The field list
670
                -> String               -- ^ Informational message
671
                -> ([String] -> String) -- ^ Function to generate the listing
672
                -> IO ()
673
maybePrintNodes Nothing _ _ = return ()
674
maybePrintNodes (Just fields) msg fn = do
675
  hPutStrLn stderr ""
676
  hPutStrLn stderr (msg ++ " status:")
677
  hPutStrLn stderr $ fn fields
678

    
679
-- | Optionally print the instance list.
680
maybePrintInsts :: Bool   -- ^ Whether to print the instance list
681
                -> String -- ^ Type of the instance map (e.g. initial)
682
                -> String -- ^ The instance data
683
                -> IO ()
684
maybePrintInsts do_print msg instdata =
685
  when do_print $ do
686
    hPutStrLn stderr ""
687
    hPutStrLn stderr $ msg ++ " instance map:"
688
    hPutStr stderr instdata
689

    
690
-- | Function to display warning messages from parsing the cluster
691
-- state.
692
maybeShowWarnings :: [String] -- ^ The warning messages
693
                  -> IO ()
694
maybeShowWarnings fix_msgs =
695
  unless (null fix_msgs) $ do
696
    hPutStrLn stderr "Warning: cluster has inconsistent data:"
697
    hPutStrLn stderr . unlines . map (printf "  - %s") $ fix_msgs
698

    
699
-- | Format a list of key, value as a shell fragment.
700
printKeys :: String              -- ^ Prefix to printed variables
701
          -> [(String, String)]  -- ^ List of (key, value) pairs to be printed
702
          -> IO ()
703
printKeys prefix =
704
  mapM_ (\(k, v) ->
705
           printf "%s_%s=%s\n" prefix (map toUpper k) (ensureQuoted v))
706

    
707
-- | Prints the final @OK@ marker in machine readable output.
708
printFinal :: String    -- ^ Prefix to printed variable
709
           -> Bool      -- ^ Whether output should be machine readable;
710
                        -- note: if not, there is nothing to print
711
           -> IO ()
712
printFinal prefix True =
713
  -- this should be the final entry
714
  printKeys prefix [("OK", "1")]
715

    
716
printFinal _ False = return ()
717

    
718
-- | Potentially set the node as offline based on passed offline list.
719
setNodeOffline :: [Ndx] -> Node.Node -> Node.Node
720
setNodeOffline offline_indices n =
721
  if Node.idx n `elem` offline_indices
722
    then Node.setOffline n True
723
    else n
724

    
725
-- | Set node properties based on command line options.
726
setNodeStatus :: Options -> Node.List -> IO Node.List
727
setNodeStatus opts fixed_nl = do
728
  let offline_passed = optOffline opts
729
      all_nodes = Container.elems fixed_nl
730
      offline_lkp = map (lookupName (map Node.name all_nodes)) offline_passed
731
      offline_wrong = filter (not . goodLookupResult) offline_lkp
732
      offline_names = map lrContent offline_lkp
733
      offline_indices = map Node.idx $
734
                        filter (\n -> Node.name n `elem` offline_names)
735
                               all_nodes
736
      m_cpu = optMcpu opts
737
      m_dsk = optMdsk opts
738

    
739
  unless (null offline_wrong) .
740
         exitErr $ printf "wrong node name(s) set as offline: %s\n"
741
                   (commaJoin (map lrContent offline_wrong))
742
  let setMCpuFn = case m_cpu of
743
                    Nothing -> id
744
                    Just new_mcpu -> flip Node.setMcpu new_mcpu
745
  let nm = Container.map (setNodeOffline offline_indices .
746
                          flip Node.setMdsk m_dsk .
747
                          setMCpuFn) fixed_nl
748
  return nm