Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / HTools / CLI.hs @ 7839bb67

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

    
102
import Control.Monad
103
import Data.Char (toUpper)
104
import Data.Maybe (fromMaybe)
105
import System.Console.GetOpt
106
import System.IO
107
import Text.Printf (printf)
108

    
109
import qualified Ganeti.HTools.Container as Container
110
import qualified Ganeti.HTools.Node as Node
111
import qualified Ganeti.Path as Path
112
import Ganeti.HTools.Types
113
import Ganeti.BasicTypes
114
import Ganeti.Common as Common
115
import Ganeti.Types
116
import Ganeti.Utils
117

    
118
-- * Data types
119

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

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

    
236
-- | Abbreviation for the option type.
237
type OptType = GenericOptType Options
238

    
239
instance StandardOptions Options where
240
  helpRequested = optShowHelp
241
  verRequested  = optShowVer
242
  compRequested = optShowComp
243
  requestHelp o = o { optShowHelp = True }
244
  requestVer  o = o { optShowVer  = True }
245
  requestComp o = o { optShowComp = True }
246

    
247
-- * Helper functions
248

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

    
271
-- | Disk template choices.
272
optComplDiskTemplate :: OptCompletion
273
optComplDiskTemplate = OptComplChoices $
274
                       map diskTemplateToRaw [minBound..maxBound]
275

    
276
-- * Command line options
277

    
278
oDataFile :: OptType
279
oDataFile =
280
  (Option "t" ["text-data"]
281
   (ReqArg (\ f o -> Ok o { optDataFile = Just f }) "FILE")
282
   "the cluster data FILE",
283
   OptComplFile)
284

    
285
oDiskMoves :: OptType
286
oDiskMoves =
287
  (Option "" ["no-disk-moves"]
288
   (NoArg (\ opts -> Ok opts { optDiskMoves = False}))
289
   "disallow disk moves from the list of allowed instance changes,\
290
   \ thus allowing only the 'cheap' failover/migrate operations",
291
   OptComplNone)
292

    
293
oMonD :: OptType
294
oMonD =
295
  (Option "" ["mond"]
296
   (NoArg (\ opts -> Ok opts {optMonD = True}))
297
   "Query MonDs",
298
   OptComplNone)
299

    
300
oMonDDataFile :: OptType
301
oMonDDataFile =
302
  (Option "" ["mond-data"]
303
   (ReqArg (\ f opts -> Ok opts { optMonDFile = Just f }) "FILE")
304
   "Import data provided by MonDs from the given FILE",
305
   OptComplFile)
306

    
307
oDiskTemplate :: OptType
308
oDiskTemplate =
309
  (Option "" ["disk-template"]
310
   (reqWithConversion diskTemplateFromRaw
311
    (\dt opts -> Ok opts { optDiskTemplate = Just dt })
312
    "TEMPLATE") "select the desired disk template",
313
   optComplDiskTemplate)
314

    
315
oSpindleUse :: OptType
316
oSpindleUse =
317
  (Option "" ["spindle-use"]
318
   (reqWithConversion (tryRead "parsing spindle-use")
319
    (\su opts -> do
320
       when (su < 0) $
321
            fail "Invalid value of the spindle-use (expected >= 0)"
322
       return $ opts { optSpindleUse = Just su })
323
    "SPINDLES") "select how many virtual spindle instances use\
324
                \ [default read from cluster]",
325
   OptComplFloat)
326

    
327
oSelInst :: OptType
328
oSelInst =
329
  (Option "" ["select-instances"]
330
   (ReqArg (\ f opts -> Ok opts { optSelInst = sepSplit ',' f }) "INSTS")
331
   "only select given instances for any moves",
332
   OptComplManyInstances)
333

    
334
oInstMoves :: OptType
335
oInstMoves =
336
  (Option "" ["no-instance-moves"]
337
   (NoArg (\ opts -> Ok opts { optInstMoves = False}))
338
   "disallow instance (primary node) moves from the list of allowed,\
339
   \ instance changes, thus allowing only slower, but sometimes\
340
   \ safer, drbd secondary changes",
341
   OptComplNone)
342

    
343
oDynuFile :: OptType
344
oDynuFile =
345
  (Option "U" ["dynu-file"]
346
   (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
347
   "Import dynamic utilisation data from the given FILE",
348
   OptComplFile)
349

    
350
oIgnoreDyn :: OptType
351
oIgnoreDyn =
352
  (Option "" ["ignore-dynu"]
353
   (NoArg (\ opts -> Ok opts {optIgnoreDynu = True}))
354
   "Ignore any dynamic utilisation information",
355
   OptComplNone)
356

    
357
oEvacMode :: OptType
358
oEvacMode =
359
  (Option "E" ["evac-mode"]
360
   (NoArg (\opts -> Ok opts { optEvacMode = True }))
361
   "enable evacuation mode, where the algorithm only moves\
362
   \ instances away from offline and drained nodes",
363
   OptComplNone)
364

    
365
oExInst :: OptType
366
oExInst =
367
  (Option "" ["exclude-instances"]
368
   (ReqArg (\ f opts -> Ok opts { optExInst = sepSplit ',' f }) "INSTS")
369
   "exclude given instances from any moves",
370
   OptComplManyInstances)
371

    
372
oExTags :: OptType
373
oExTags =
374
  (Option "" ["exclusion-tags"]
375
   (ReqArg (\ f opts -> Ok opts { optExTags = Just $ sepSplit ',' f })
376
    "TAG,...") "Enable instance exclusion based on given tag prefix",
377
   OptComplString)
378

    
379
oExecJobs :: OptType
380
oExecJobs =
381
  (Option "X" ["exec"]
382
   (NoArg (\ opts -> Ok opts { optExecJobs = True}))
383
   "execute the suggested moves via Luxi (only available when using\
384
   \ it for data gathering)",
385
   OptComplNone)
386

    
387
oForce :: OptType
388
oForce =
389
  (Option "f" ["force"]
390
   (NoArg (\ opts -> Ok opts {optForce = True}))
391
   "force the execution of this program, even if warnings would\
392
   \ otherwise prevent it",
393
   OptComplNone)
394

    
395
oFullEvacuation :: OptType
396
oFullEvacuation =
397
  (Option "" ["full-evacuation"]
398
   (NoArg (\ opts -> Ok opts { optFullEvacuation = True}))
399
   "fully evacuate the nodes to be rebooted",
400
   OptComplNone)
401

    
402
oGroup :: OptType
403
oGroup =
404
  (Option "G" ["group"]
405
   (ReqArg (\ f o -> Ok o { optGroup = Just f }) "ID")
406
   "the target node group (name or UUID)",
407
   OptComplOneGroup)
408

    
409
oIAllocSrc :: OptType
410
oIAllocSrc =
411
  (Option "I" ["ialloc-src"]
412
   (ReqArg (\ f opts -> Ok opts { optIAllocSrc = Just f }) "FILE")
413
   "Specify an iallocator spec as the cluster data source",
414
   OptComplFile)
415

    
416
oIgnoreNonRedundant :: OptType
417
oIgnoreNonRedundant =
418
  (Option "" ["ignore-non-redundant"]
419
   (NoArg (\ opts -> Ok opts { optIgnoreNonRedundant = True }))
420
    "Pretend that there are no non-redundant instances in the cluster",
421
    OptComplNone)
422

    
423
oJobDelay :: OptType
424
oJobDelay =
425
  (Option "" ["job-delay"]
426
   (reqWithConversion (tryRead "job delay")
427
    (\d opts -> Ok opts { optJobDelay = d }) "SECONDS")
428
   "insert this much delay before the execution of repair jobs\
429
   \ to allow the tool to continue processing instances",
430
   OptComplFloat)
431

    
432
genOLuxiSocket :: String -> OptType
433
genOLuxiSocket defSocket =
434
  (Option "L" ["luxi"]
435
   (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
436
            fromMaybe defSocket) "SOCKET")
437
   ("collect data via Luxi, optionally using the given SOCKET path [" ++
438
    defSocket ++ "]"),
439
   OptComplFile)
440

    
441
oLuxiSocket :: IO OptType
442
oLuxiSocket = liftM genOLuxiSocket Path.defaultLuxiSocket
443

    
444
oMachineReadable :: OptType
445
oMachineReadable =
446
  (Option "" ["machine-readable"]
447
   (OptArg (\ f opts -> do
448
              flag <- parseYesNo True f
449
              return $ opts { optMachineReadable = flag }) "CHOICE")
450
   "enable machine readable output (pass either 'yes' or 'no' to\
451
   \ explicitly control the flag, or without an argument defaults to\
452
   \ yes)",
453
   optComplYesNo)
454

    
455
oMaxCpu :: OptType
456
oMaxCpu =
457
  (Option "" ["max-cpu"]
458
   (reqWithConversion (tryRead "parsing max-cpu")
459
    (\mcpu opts -> do
460
       when (mcpu <= 0) $
461
            fail "Invalid value of the max-cpu ratio, expected >0"
462
       return $ opts { optMcpu = Just mcpu }) "RATIO")
463
   "maximum virtual-to-physical cpu ratio for nodes (from 0\
464
   \ upwards) [default read from cluster]",
465
   OptComplFloat)
466

    
467
oMaxSolLength :: OptType
468
oMaxSolLength =
469
  (Option "l" ["max-length"]
470
   (reqWithConversion (tryRead "max solution length")
471
    (\i opts -> Ok opts { optMaxLength = i }) "N")
472
   "cap the solution at this many balancing or allocation\
473
   \ rounds (useful for very unbalanced clusters or empty\
474
   \ clusters)",
475
   OptComplInteger)
476

    
477
oMinDisk :: OptType
478
oMinDisk =
479
  (Option "" ["min-disk"]
480
   (reqWithConversion (tryRead "min free disk space")
481
    (\n opts -> Ok opts { optMdsk = n }) "RATIO")
482
   "minimum free disk space for nodes (between 0 and 1) [0]",
483
   OptComplFloat)
484

    
485
oMinGain :: OptType
486
oMinGain =
487
  (Option "g" ["min-gain"]
488
   (reqWithConversion (tryRead "min gain")
489
    (\g opts -> Ok opts { optMinGain = g }) "DELTA")
490
   "minimum gain to aim for in a balancing step before giving up",
491
   OptComplFloat)
492

    
493
oMinGainLim :: OptType
494
oMinGainLim =
495
  (Option "" ["min-gain-limit"]
496
   (reqWithConversion (tryRead "min gain limit")
497
    (\g opts -> Ok opts { optMinGainLim = g }) "SCORE")
498
   "minimum cluster score for which we start checking the min-gain",
499
   OptComplFloat)
500

    
501
oMinScore :: OptType
502
oMinScore =
503
  (Option "e" ["min-score"]
504
   (reqWithConversion (tryRead "min score")
505
    (\e opts -> Ok opts { optMinScore = e }) "EPSILON")
506
   "mininum score to aim for",
507
   OptComplFloat)
508

    
509
oNoHeaders :: OptType
510
oNoHeaders =
511
  (Option "" ["no-headers"]
512
   (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
513
   "do not show a header line",
514
   OptComplNone)
515

    
516
oNoSimulation :: OptType
517
oNoSimulation =
518
  (Option "" ["no-simulation"]
519
   (NoArg (\opts -> Ok opts {optNoSimulation = True}))
520
   "do not perform rebalancing simulation",
521
   OptComplNone)
522

    
523
oNodeSim :: OptType
524
oNodeSim =
525
  (Option "" ["simulate"]
526
   (ReqArg (\ f o -> Ok o { optNodeSim = f:optNodeSim o }) "SPEC")
527
   "simulate an empty cluster, given as\
528
   \ 'alloc_policy,num_nodes,disk,ram,cpu'",
529
   OptComplString)
530

    
531
oNodeTags :: OptType
532
oNodeTags =
533
  (Option "" ["node-tags"]
534
   (ReqArg (\ f opts -> Ok opts { optNodeTags = Just $ sepSplit ',' f })
535
    "TAG,...") "Restrict to nodes with the given tags",
536
   OptComplString)
537
     
538
oOfflineMaintenance :: OptType
539
oOfflineMaintenance =
540
  (Option "" ["offline-maintenance"]
541
   (NoArg (\ opts -> Ok opts {optOfflineMaintenance = True}))
542
   "Schedule offline maintenance, i.e., pretend that all instance are\
543
   \ offline.",
544
   OptComplNone)
545

    
546
oOfflineNode :: OptType
547
oOfflineNode =
548
  (Option "O" ["offline"]
549
   (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
550
   "set node as offline",
551
   OptComplOneNode)
552

    
553
oOneStepOnly :: OptType
554
oOneStepOnly =
555
  (Option "" ["one-step-only"]
556
   (NoArg (\ opts -> Ok opts {optOneStepOnly = True}))
557
   "Only do the first step",
558
   OptComplNone)
559

    
560
oOutputDir :: OptType
561
oOutputDir =
562
  (Option "d" ["output-dir"]
563
   (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
564
   "directory in which to write output files",
565
   OptComplDir)
566

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

    
578
oPrintInsts :: OptType
579
oPrintInsts =
580
  (Option "" ["print-instances"]
581
   (NoArg (\ opts -> Ok opts { optShowInsts = True }))
582
   "print the final instance map",
583
   OptComplNone)
584

    
585
oPrintMoves :: OptType
586
oPrintMoves =
587
  (Option "" ["print-moves"]
588
   (NoArg (\ opts -> Ok opts { optPrintMoves = True }))
589
   "print the moves of the instances",
590
   OptComplNone)
591

    
592
oPrintNodes :: OptType
593
oPrintNodes =
594
  (Option "p" ["print-nodes"]
595
   (OptArg ((\ f opts ->
596
               let (prefix, realf) = case f of
597
                                       '+':rest -> (["+"], rest)
598
                                       _ -> ([], f)
599
                   splitted = prefix ++ sepSplit ',' realf
600
               in Ok opts { optShowNodes = Just splitted }) .
601
            fromMaybe []) "FIELDS")
602
   "print the final node list",
603
   OptComplNone)
604

    
605
oQuiet :: OptType
606
oQuiet =
607
  (Option "q" ["quiet"]
608
   (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
609
   "decrease the verbosity level",
610
   OptComplNone)
611

    
612
oRapiMaster :: OptType
613
oRapiMaster =
614
  (Option "m" ["master"]
615
   (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
616
   "collect data via RAPI at the given ADDRESS",
617
   OptComplHost)
618

    
619
oSaveCluster :: OptType
620
oSaveCluster =
621
  (Option "S" ["save"]
622
   (ReqArg (\ f opts -> Ok opts { optSaveCluster = Just f }) "FILE")
623
   "Save cluster state at the end of the processing to FILE",
624
   OptComplNone)
625

    
626
oSkipNonRedundant :: OptType
627
oSkipNonRedundant =
628
  (Option "" ["skip-non-redundant"]
629
   (NoArg (\ opts -> Ok opts { optSkipNonRedundant = True }))
630
    "Skip nodes that host a non-redundant instance",
631
    OptComplNone)
632

    
633
oStdSpec :: OptType
634
oStdSpec =
635
  (Option "" ["standard-alloc"]
636
   (ReqArg (\ inp opts -> do
637
              tspec <- parseISpecString "standard" inp
638
              return $ opts { optStdSpec = Just tspec } )
639
    "STDSPEC")
640
   "enable standard specs allocation, given as 'disk,ram,cpu'",
641
   OptComplString)
642

    
643
oTargetResources :: OptType
644
oTargetResources =
645
  (Option "" ["target-resources"]
646
   (reqWithConversion (tryRead "target resources")
647
    (\d opts -> Ok opts { optTargetResources = d}) "FACTOR")
648
   "target resources to be left on each node after squeezing in\
649
   \ multiples of the standard allocation",
650
   OptComplFloat)
651

    
652
oTieredSpec :: OptType
653
oTieredSpec =
654
  (Option "" ["tiered-alloc"]
655
   (ReqArg (\ inp opts -> do
656
              tspec <- parseISpecString "tiered" inp
657
              return $ opts { optTieredSpec = Just tspec } )
658
    "TSPEC")
659
   "enable tiered specs allocation, given as 'disk,ram,cpu'",
660
   OptComplString)
661

    
662
oVerbose :: OptType
663
oVerbose =
664
  (Option "v" ["verbose"]
665
   (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
666
   "increase the verbosity level",
667
   OptComplNone)
668

    
669
oPriority :: OptType
670
oPriority =
671
  (Option "" ["priority"]
672
   (ReqArg (\ inp opts -> do
673
              prio <- parseSubmitPriority inp
674
              Ok opts { optPriority = Just prio }) "PRIO")
675
   "set the priority of submitted jobs",
676
    OptComplChoices (map fmtSubmitPriority [minBound..maxBound]))
677

    
678
-- | Generic options.
679
genericOpts :: [GenericOptType Options]
680
genericOpts =  [ oShowVer
681
               , oShowHelp
682
               , oShowComp
683
               ]
684

    
685
-- * Functions
686

    
687
-- | Wrapper over 'Common.parseOpts' with our custom options.
688
parseOpts :: [String]               -- ^ The command line arguments
689
          -> String                 -- ^ The program name
690
          -> [OptType]              -- ^ The supported command line options
691
          -> [ArgCompletion]        -- ^ The supported command line arguments
692
          -> IO (Options, [String]) -- ^ The resulting options and leftover
693
                                    -- arguments
694
parseOpts = Common.parseOpts defaultOptions
695

    
696

    
697
-- | A shell script template for autogenerated scripts.
698
shTemplate :: String
699
shTemplate =
700
  printf "#!/bin/sh\n\n\
701
         \# Auto-generated script for executing cluster rebalancing\n\n\
702
         \# To stop, touch the file /tmp/stop-htools\n\n\
703
         \set -e\n\n\
704
         \check() {\n\
705
         \  if [ -f /tmp/stop-htools ]; then\n\
706
         \    echo 'Stop requested, exiting'\n\
707
         \    exit 0\n\
708
         \  fi\n\
709
         \}\n\n"
710

    
711
-- | Optionally print the node list.
712
maybePrintNodes :: Maybe [String]       -- ^ The field list
713
                -> String               -- ^ Informational message
714
                -> ([String] -> String) -- ^ Function to generate the listing
715
                -> IO ()
716
maybePrintNodes Nothing _ _ = return ()
717
maybePrintNodes (Just fields) msg fn = do
718
  hPutStrLn stderr ""
719
  hPutStrLn stderr (msg ++ " status:")
720
  hPutStrLn stderr $ fn fields
721

    
722
-- | Optionally print the instance list.
723
maybePrintInsts :: Bool   -- ^ Whether to print the instance list
724
                -> String -- ^ Type of the instance map (e.g. initial)
725
                -> String -- ^ The instance data
726
                -> IO ()
727
maybePrintInsts do_print msg instdata =
728
  when do_print $ do
729
    hPutStrLn stderr ""
730
    hPutStrLn stderr $ msg ++ " instance map:"
731
    hPutStr stderr instdata
732

    
733
-- | Function to display warning messages from parsing the cluster
734
-- state.
735
maybeShowWarnings :: [String] -- ^ The warning messages
736
                  -> IO ()
737
maybeShowWarnings fix_msgs =
738
  unless (null fix_msgs) $ do
739
    hPutStrLn stderr "Warning: cluster has inconsistent data:"
740
    hPutStrLn stderr . unlines . map (printf "  - %s") $ fix_msgs
741

    
742
-- | Format a list of key, value as a shell fragment.
743
printKeys :: String              -- ^ Prefix to printed variables
744
          -> [(String, String)]  -- ^ List of (key, value) pairs to be printed
745
          -> IO ()
746
printKeys prefix =
747
  mapM_ (\(k, v) ->
748
           printf "%s_%s=%s\n" prefix (map toUpper k) (ensureQuoted v))
749

    
750
-- | Prints the final @OK@ marker in machine readable output.
751
printFinal :: String    -- ^ Prefix to printed variable
752
           -> Bool      -- ^ Whether output should be machine readable;
753
                        -- note: if not, there is nothing to print
754
           -> IO ()
755
printFinal prefix True =
756
  -- this should be the final entry
757
  printKeys prefix [("OK", "1")]
758

    
759
printFinal _ False = return ()
760

    
761
-- | Potentially set the node as offline based on passed offline list.
762
setNodeOffline :: [Ndx] -> Node.Node -> Node.Node
763
setNodeOffline offline_indices n =
764
  if Node.idx n `elem` offline_indices
765
    then Node.setOffline n True
766
    else n
767

    
768
-- | Set node properties based on command line options.
769
setNodeStatus :: Options -> Node.List -> IO Node.List
770
setNodeStatus opts fixed_nl = do
771
  let offline_passed = optOffline opts
772
      all_nodes = Container.elems fixed_nl
773
      offline_lkp = map (lookupName (map Node.name all_nodes)) offline_passed
774
      offline_wrong = filter (not . goodLookupResult) offline_lkp
775
      offline_names = map lrContent offline_lkp
776
      offline_indices = map Node.idx $
777
                        filter (\n -> Node.name n `elem` offline_names)
778
                               all_nodes
779
      m_cpu = optMcpu opts
780
      m_dsk = optMdsk opts
781

    
782
  unless (null offline_wrong) .
783
         exitErr $ printf "wrong node name(s) set as offline: %s\n"
784
                   (commaJoin (map lrContent offline_wrong))
785
  let setMCpuFn = case m_cpu of
786
                    Nothing -> id
787
                    Just new_mcpu -> flip Node.setMcpu new_mcpu
788
  let nm = Container.map (setNodeOffline offline_indices .
789
                          flip Node.setMdsk m_dsk .
790
                          setMCpuFn) fixed_nl
791
  return nm