Statistics
| Branch: | Tag: | Revision:

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

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

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

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

    
116
-- * Data types
117

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

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

    
229
-- | Abbreviation for the option type.
230
type OptType = GenericOptType Options
231

    
232
instance StandardOptions Options where
233
  helpRequested = optShowHelp
234
  verRequested  = optShowVer
235
  compRequested = optShowComp
236
  requestHelp o = o { optShowHelp = True }
237
  requestVer  o = o { optShowVer  = True }
238
  requestComp o = o { optShowComp = True }
239

    
240
-- * Helper functions
241

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

    
264
-- | Disk template choices.
265
optComplDiskTemplate :: OptCompletion
266
optComplDiskTemplate = OptComplChoices $
267
                       map diskTemplateToRaw [minBound..maxBound]
268

    
269
-- * Command line options
270

    
271
oDataFile :: OptType
272
oDataFile =
273
  (Option "t" ["text-data"]
274
   (ReqArg (\ f o -> Ok o { optDataFile = Just f }) "FILE")
275
   "the cluster data FILE",
276
   OptComplFile)
277

    
278
oDiskMoves :: OptType
279
oDiskMoves =
280
  (Option "" ["no-disk-moves"]
281
   (NoArg (\ opts -> Ok opts { optDiskMoves = False}))
282
   "disallow disk moves from the list of allowed instance changes,\
283
   \ thus allowing only the 'cheap' failover/migrate operations",
284
   OptComplNone)
285

    
286
oMonD :: OptType
287
oMonD =
288
  (Option "" ["mond"]
289
   (NoArg (\ opts -> Ok opts {optMonD = True}))
290
   "Query MonDs",
291
   OptComplNone)
292

    
293
oDiskTemplate :: OptType
294
oDiskTemplate =
295
  (Option "" ["disk-template"]
296
   (reqWithConversion diskTemplateFromRaw
297
    (\dt opts -> Ok opts { optDiskTemplate = Just dt })
298
    "TEMPLATE") "select the desired disk template",
299
   optComplDiskTemplate)
300

    
301
oSpindleUse :: OptType
302
oSpindleUse =
303
  (Option "" ["spindle-use"]
304
   (reqWithConversion (tryRead "parsing spindle-use")
305
    (\su opts -> do
306
       when (su < 0) $
307
            fail "Invalid value of the spindle-use (expected >= 0)"
308
       return $ opts { optSpindleUse = Just su })
309
    "SPINDLES") "select how many virtual spindle instances use\
310
                \ [default read from cluster]",
311
   OptComplFloat)
312

    
313
oSelInst :: OptType
314
oSelInst =
315
  (Option "" ["select-instances"]
316
   (ReqArg (\ f opts -> Ok opts { optSelInst = sepSplit ',' f }) "INSTS")
317
   "only select given instances for any moves",
318
   OptComplManyInstances)
319

    
320
oInstMoves :: OptType
321
oInstMoves =
322
  (Option "" ["no-instance-moves"]
323
   (NoArg (\ opts -> Ok opts { optInstMoves = False}))
324
   "disallow instance (primary node) moves from the list of allowed,\
325
   \ instance changes, thus allowing only slower, but sometimes\
326
   \ safer, drbd secondary changes",
327
   OptComplNone)
328

    
329
oDynuFile :: OptType
330
oDynuFile =
331
  (Option "U" ["dynu-file"]
332
   (ReqArg (\ f opts -> Ok opts { optDynuFile = Just f }) "FILE")
333
   "Import dynamic utilisation data from the given FILE",
334
   OptComplFile)
335

    
336
oIgnoreDyn :: OptType
337
oIgnoreDyn =
338
  (Option "" ["ignore-dynu"]
339
   (NoArg (\ opts -> Ok opts {optIgnoreDynu = True}))
340
   "Ignore any dynamic utilisation information",
341
   OptComplNone)
342

    
343
oEvacMode :: OptType
344
oEvacMode =
345
  (Option "E" ["evac-mode"]
346
   (NoArg (\opts -> Ok opts { optEvacMode = True }))
347
   "enable evacuation mode, where the algorithm only moves\
348
   \ instances away from offline and drained nodes",
349
   OptComplNone)
350

    
351
oExInst :: OptType
352
oExInst =
353
  (Option "" ["exclude-instances"]
354
   (ReqArg (\ f opts -> Ok opts { optExInst = sepSplit ',' f }) "INSTS")
355
   "exclude given instances from any moves",
356
   OptComplManyInstances)
357

    
358
oExTags :: OptType
359
oExTags =
360
  (Option "" ["exclusion-tags"]
361
   (ReqArg (\ f opts -> Ok opts { optExTags = Just $ sepSplit ',' f })
362
    "TAG,...") "Enable instance exclusion based on given tag prefix",
363
   OptComplString)
364

    
365
oExecJobs :: OptType
366
oExecJobs =
367
  (Option "X" ["exec"]
368
   (NoArg (\ opts -> Ok opts { optExecJobs = True}))
369
   "execute the suggested moves via Luxi (only available when using\
370
   \ it for data gathering)",
371
   OptComplNone)
372

    
373
oForce :: OptType
374
oForce =
375
  (Option "f" ["force"]
376
   (NoArg (\ opts -> Ok opts {optForce = True}))
377
   "force the execution of this program, even if warnings would\
378
   \ otherwise prevent it",
379
   OptComplNone)
380

    
381
oFullEvacuation :: OptType
382
oFullEvacuation =
383
  (Option "" ["full-evacuation"]
384
   (NoArg (\ opts -> Ok opts { optFullEvacuation = True}))
385
   "fully evacuate the nodes to be rebooted",
386
   OptComplNone)
387

    
388
oGroup :: OptType
389
oGroup =
390
  (Option "G" ["group"]
391
   (ReqArg (\ f o -> Ok o { optGroup = Just f }) "ID")
392
   "the target node group (name or UUID)",
393
   OptComplOneGroup)
394

    
395
oIAllocSrc :: OptType
396
oIAllocSrc =
397
  (Option "I" ["ialloc-src"]
398
   (ReqArg (\ f opts -> Ok opts { optIAllocSrc = Just f }) "FILE")
399
   "Specify an iallocator spec as the cluster data source",
400
   OptComplFile)
401

    
402
oIgnoreNonRedundant :: OptType
403
oIgnoreNonRedundant =
404
  (Option "" ["ignore-non-redundant"]
405
   (NoArg (\ opts -> Ok opts { optIgnoreNonRedundant = True }))
406
    "Pretend that there are no non-redundant instances in the cluster",
407
    OptComplNone)
408

    
409
oJobDelay :: OptType
410
oJobDelay =
411
  (Option "" ["job-delay"]
412
   (reqWithConversion (tryRead "job delay")
413
    (\d opts -> Ok opts { optJobDelay = d }) "SECONDS")
414
   "insert this much delay before the execution of repair jobs\
415
   \ to allow the tool to continue processing instances",
416
   OptComplFloat)
417

    
418
genOLuxiSocket :: String -> OptType
419
genOLuxiSocket defSocket =
420
  (Option "L" ["luxi"]
421
   (OptArg ((\ f opts -> Ok opts { optLuxi = Just f }) .
422
            fromMaybe defSocket) "SOCKET")
423
   ("collect data via Luxi, optionally using the given SOCKET path [" ++
424
    defSocket ++ "]"),
425
   OptComplFile)
426

    
427
oLuxiSocket :: IO OptType
428
oLuxiSocket = liftM genOLuxiSocket Path.defaultLuxiSocket
429

    
430
oMachineReadable :: OptType
431
oMachineReadable =
432
  (Option "" ["machine-readable"]
433
   (OptArg (\ f opts -> do
434
              flag <- parseYesNo True f
435
              return $ opts { optMachineReadable = flag }) "CHOICE")
436
   "enable machine readable output (pass either 'yes' or 'no' to\
437
   \ explicitly control the flag, or without an argument defaults to\
438
   \ yes)",
439
   optComplYesNo)
440

    
441
oMaxCpu :: OptType
442
oMaxCpu =
443
  (Option "" ["max-cpu"]
444
   (reqWithConversion (tryRead "parsing max-cpu")
445
    (\mcpu opts -> do
446
       when (mcpu <= 0) $
447
            fail "Invalid value of the max-cpu ratio, expected >0"
448
       return $ opts { optMcpu = Just mcpu }) "RATIO")
449
   "maximum virtual-to-physical cpu ratio for nodes (from 0\
450
   \ upwards) [default read from cluster]",
451
   OptComplFloat)
452

    
453
oMaxSolLength :: OptType
454
oMaxSolLength =
455
  (Option "l" ["max-length"]
456
   (reqWithConversion (tryRead "max solution length")
457
    (\i opts -> Ok opts { optMaxLength = i }) "N")
458
   "cap the solution at this many balancing or allocation\
459
   \ rounds (useful for very unbalanced clusters or empty\
460
   \ clusters)",
461
   OptComplInteger)
462

    
463
oMinDisk :: OptType
464
oMinDisk =
465
  (Option "" ["min-disk"]
466
   (reqWithConversion (tryRead "min free disk space")
467
    (\n opts -> Ok opts { optMdsk = n }) "RATIO")
468
   "minimum free disk space for nodes (between 0 and 1) [0]",
469
   OptComplFloat)
470

    
471
oMinGain :: OptType
472
oMinGain =
473
  (Option "g" ["min-gain"]
474
   (reqWithConversion (tryRead "min gain")
475
    (\g opts -> Ok opts { optMinGain = g }) "DELTA")
476
   "minimum gain to aim for in a balancing step before giving up",
477
   OptComplFloat)
478

    
479
oMinGainLim :: OptType
480
oMinGainLim =
481
  (Option "" ["min-gain-limit"]
482
   (reqWithConversion (tryRead "min gain limit")
483
    (\g opts -> Ok opts { optMinGainLim = g }) "SCORE")
484
   "minimum cluster score for which we start checking the min-gain",
485
   OptComplFloat)
486

    
487
oMinScore :: OptType
488
oMinScore =
489
  (Option "e" ["min-score"]
490
   (reqWithConversion (tryRead "min score")
491
    (\e opts -> Ok opts { optMinScore = e }) "EPSILON")
492
   "mininum score to aim for",
493
   OptComplFloat)
494

    
495
oNoHeaders :: OptType
496
oNoHeaders =
497
  (Option "" ["no-headers"]
498
   (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
499
   "do not show a header line",
500
   OptComplNone)
501

    
502
oNoSimulation :: OptType
503
oNoSimulation =
504
  (Option "" ["no-simulation"]
505
   (NoArg (\opts -> Ok opts {optNoSimulation = True}))
506
   "do not perform rebalancing simulation",
507
   OptComplNone)
508

    
509
oNodeSim :: OptType
510
oNodeSim =
511
  (Option "" ["simulate"]
512
   (ReqArg (\ f o -> Ok o { optNodeSim = f:optNodeSim o }) "SPEC")
513
   "simulate an empty cluster, given as\
514
   \ 'alloc_policy,num_nodes,disk,ram,cpu'",
515
   OptComplString)
516

    
517
oNodeTags :: OptType
518
oNodeTags =
519
  (Option "" ["node-tags"]
520
   (ReqArg (\ f opts -> Ok opts { optNodeTags = Just $ sepSplit ',' f })
521
    "TAG,...") "Restrict to nodes with the given tags",
522
   OptComplString)
523
     
524
oOfflineMaintenance :: OptType
525
oOfflineMaintenance =
526
  (Option "" ["offline-maintenance"]
527
   (NoArg (\ opts -> Ok opts {optOfflineMaintenance = True}))
528
   "Schedule offline maintenance, i.e., pretend that all instance are\
529
   \ offline.",
530
   OptComplNone)
531

    
532
oOfflineNode :: OptType
533
oOfflineNode =
534
  (Option "O" ["offline"]
535
   (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
536
   "set node as offline",
537
   OptComplOneNode)
538

    
539
oOneStepOnly :: OptType
540
oOneStepOnly =
541
  (Option "" ["one-step-only"]
542
   (NoArg (\ opts -> Ok opts {optOneStepOnly = True}))
543
   "Only do the first step",
544
   OptComplNone)
545

    
546
oOutputDir :: OptType
547
oOutputDir =
548
  (Option "d" ["output-dir"]
549
   (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
550
   "directory in which to write output files",
551
   OptComplDir)
552

    
553
oPrintCommands :: OptType
554
oPrintCommands =
555
  (Option "C" ["print-commands"]
556
   (OptArg ((\ f opts -> Ok opts { optShowCmds = Just f }) .
557
            fromMaybe "-")
558
    "FILE")
559
   "print the ganeti command list for reaching the solution,\
560
   \ if an argument is passed then write the commands to a\
561
   \ file named as such",
562
   OptComplNone)
563

    
564
oPrintInsts :: OptType
565
oPrintInsts =
566
  (Option "" ["print-instances"]
567
   (NoArg (\ opts -> Ok opts { optShowInsts = True }))
568
   "print the final instance map",
569
   OptComplNone)
570

    
571
oPrintMoves :: OptType
572
oPrintMoves =
573
  (Option "" ["print-moves"]
574
   (NoArg (\ opts -> Ok opts { optPrintMoves = True }))
575
   "print the moves of the instances",
576
   OptComplNone)
577

    
578
oPrintNodes :: OptType
579
oPrintNodes =
580
  (Option "p" ["print-nodes"]
581
   (OptArg ((\ f opts ->
582
               let (prefix, realf) = case f of
583
                                       '+':rest -> (["+"], rest)
584
                                       _ -> ([], f)
585
                   splitted = prefix ++ sepSplit ',' realf
586
               in Ok opts { optShowNodes = Just splitted }) .
587
            fromMaybe []) "FIELDS")
588
   "print the final node list",
589
   OptComplNone)
590

    
591
oQuiet :: OptType
592
oQuiet =
593
  (Option "q" ["quiet"]
594
   (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
595
   "decrease the verbosity level",
596
   OptComplNone)
597

    
598
oRapiMaster :: OptType
599
oRapiMaster =
600
  (Option "m" ["master"]
601
   (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
602
   "collect data via RAPI at the given ADDRESS",
603
   OptComplHost)
604

    
605
oSaveCluster :: OptType
606
oSaveCluster =
607
  (Option "S" ["save"]
608
   (ReqArg (\ f opts -> Ok opts { optSaveCluster = Just f }) "FILE")
609
   "Save cluster state at the end of the processing to FILE",
610
   OptComplNone)
611

    
612
oSkipNonRedundant :: OptType
613
oSkipNonRedundant =
614
  (Option "" ["skip-non-redundant"]
615
   (NoArg (\ opts -> Ok opts { optSkipNonRedundant = True }))
616
    "Skip nodes that host a non-redundant instance",
617
    OptComplNone)
618

    
619
oStdSpec :: OptType
620
oStdSpec =
621
  (Option "" ["standard-alloc"]
622
   (ReqArg (\ inp opts -> do
623
              tspec <- parseISpecString "standard" inp
624
              return $ opts { optStdSpec = Just tspec } )
625
    "STDSPEC")
626
   "enable standard specs allocation, given as 'disk,ram,cpu'",
627
   OptComplString)
628

    
629
oTieredSpec :: OptType
630
oTieredSpec =
631
  (Option "" ["tiered-alloc"]
632
   (ReqArg (\ inp opts -> do
633
              tspec <- parseISpecString "tiered" inp
634
              return $ opts { optTieredSpec = Just tspec } )
635
    "TSPEC")
636
   "enable tiered specs allocation, given as 'disk,ram,cpu'",
637
   OptComplString)
638

    
639
oVerbose :: OptType
640
oVerbose =
641
  (Option "v" ["verbose"]
642
   (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
643
   "increase the verbosity level",
644
   OptComplNone)
645

    
646
oPriority :: OptType
647
oPriority =
648
  (Option "" ["priority"]
649
   (ReqArg (\ inp opts -> do
650
              prio <- parseSubmitPriority inp
651
              Ok opts { optPriority = Just prio }) "PRIO")
652
   "set the priority of submitted jobs",
653
    OptComplChoices (map fmtSubmitPriority [minBound..maxBound]))
654

    
655
-- | Generic options.
656
genericOpts :: [GenericOptType Options]
657
genericOpts =  [ oShowVer
658
               , oShowHelp
659
               , oShowComp
660
               ]
661

    
662
-- * Functions
663

    
664
-- | Wrapper over 'Common.parseOpts' with our custom options.
665
parseOpts :: [String]               -- ^ The command line arguments
666
          -> String                 -- ^ The program name
667
          -> [OptType]              -- ^ The supported command line options
668
          -> [ArgCompletion]        -- ^ The supported command line arguments
669
          -> IO (Options, [String]) -- ^ The resulting options and leftover
670
                                    -- arguments
671
parseOpts = Common.parseOpts defaultOptions
672

    
673

    
674
-- | A shell script template for autogenerated scripts.
675
shTemplate :: String
676
shTemplate =
677
  printf "#!/bin/sh\n\n\
678
         \# Auto-generated script for executing cluster rebalancing\n\n\
679
         \# To stop, touch the file /tmp/stop-htools\n\n\
680
         \set -e\n\n\
681
         \check() {\n\
682
         \  if [ -f /tmp/stop-htools ]; then\n\
683
         \    echo 'Stop requested, exiting'\n\
684
         \    exit 0\n\
685
         \  fi\n\
686
         \}\n\n"
687

    
688
-- | Optionally print the node list.
689
maybePrintNodes :: Maybe [String]       -- ^ The field list
690
                -> String               -- ^ Informational message
691
                -> ([String] -> String) -- ^ Function to generate the listing
692
                -> IO ()
693
maybePrintNodes Nothing _ _ = return ()
694
maybePrintNodes (Just fields) msg fn = do
695
  hPutStrLn stderr ""
696
  hPutStrLn stderr (msg ++ " status:")
697
  hPutStrLn stderr $ fn fields
698

    
699
-- | Optionally print the instance list.
700
maybePrintInsts :: Bool   -- ^ Whether to print the instance list
701
                -> String -- ^ Type of the instance map (e.g. initial)
702
                -> String -- ^ The instance data
703
                -> IO ()
704
maybePrintInsts do_print msg instdata =
705
  when do_print $ do
706
    hPutStrLn stderr ""
707
    hPutStrLn stderr $ msg ++ " instance map:"
708
    hPutStr stderr instdata
709

    
710
-- | Function to display warning messages from parsing the cluster
711
-- state.
712
maybeShowWarnings :: [String] -- ^ The warning messages
713
                  -> IO ()
714
maybeShowWarnings fix_msgs =
715
  unless (null fix_msgs) $ do
716
    hPutStrLn stderr "Warning: cluster has inconsistent data:"
717
    hPutStrLn stderr . unlines . map (printf "  - %s") $ fix_msgs
718

    
719
-- | Format a list of key, value as a shell fragment.
720
printKeys :: String              -- ^ Prefix to printed variables
721
          -> [(String, String)]  -- ^ List of (key, value) pairs to be printed
722
          -> IO ()
723
printKeys prefix =
724
  mapM_ (\(k, v) ->
725
           printf "%s_%s=%s\n" prefix (map toUpper k) (ensureQuoted v))
726

    
727
-- | Prints the final @OK@ marker in machine readable output.
728
printFinal :: String    -- ^ Prefix to printed variable
729
           -> Bool      -- ^ Whether output should be machine readable;
730
                        -- note: if not, there is nothing to print
731
           -> IO ()
732
printFinal prefix True =
733
  -- this should be the final entry
734
  printKeys prefix [("OK", "1")]
735

    
736
printFinal _ False = return ()
737

    
738
-- | Potentially set the node as offline based on passed offline list.
739
setNodeOffline :: [Ndx] -> Node.Node -> Node.Node
740
setNodeOffline offline_indices n =
741
  if Node.idx n `elem` offline_indices
742
    then Node.setOffline n True
743
    else n
744

    
745
-- | Set node properties based on command line options.
746
setNodeStatus :: Options -> Node.List -> IO Node.List
747
setNodeStatus opts fixed_nl = do
748
  let offline_passed = optOffline opts
749
      all_nodes = Container.elems fixed_nl
750
      offline_lkp = map (lookupName (map Node.name all_nodes)) offline_passed
751
      offline_wrong = filter (not . goodLookupResult) offline_lkp
752
      offline_names = map lrContent offline_lkp
753
      offline_indices = map Node.idx $
754
                        filter (\n -> Node.name n `elem` offline_names)
755
                               all_nodes
756
      m_cpu = optMcpu opts
757
      m_dsk = optMdsk opts
758

    
759
  unless (null offline_wrong) .
760
         exitErr $ printf "wrong node name(s) set as offline: %s\n"
761
                   (commaJoin (map lrContent offline_wrong))
762
  let setMCpuFn = case m_cpu of
763
                    Nothing -> id
764
                    Just new_mcpu -> flip Node.setMcpu new_mcpu
765
  let nm = Container.map (setNodeOffline offline_indices .
766
                          flip Node.setMdsk m_dsk .
767
                          setMCpuFn) fixed_nl
768
  return nm