Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (25 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
  , oTieredSpec
96
  , oVerbose
97
  , oPriority
98
  , genericOpts
99
  ) where
100

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

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

    
117
-- * Data types
118

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

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

    
233
-- | Abbreviation for the option type.
234
type OptType = GenericOptType Options
235

    
236
instance StandardOptions Options where
237
  helpRequested = optShowHelp
238
  verRequested  = optShowVer
239
  compRequested = optShowComp
240
  requestHelp o = o { optShowHelp = True }
241
  requestVer  o = o { optShowVer  = True }
242
  requestComp o = o { optShowComp = True }
243

    
244
-- * Helper functions
245

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

    
268
-- | Disk template choices.
269
optComplDiskTemplate :: OptCompletion
270
optComplDiskTemplate = OptComplChoices $
271
                       map diskTemplateToRaw [minBound..maxBound]
272

    
273
-- * Command line options
274

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
438
oLuxiSocket :: IO OptType
439
oLuxiSocket = liftM genOLuxiSocket Path.defaultLuxiSocket
440

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
640
oTieredSpec :: OptType
641
oTieredSpec =
642
  (Option "" ["tiered-alloc"]
643
   (ReqArg (\ inp opts -> do
644
              tspec <- parseISpecString "tiered" inp
645
              return $ opts { optTieredSpec = Just tspec } )
646
    "TSPEC")
647
   "enable tiered specs allocation, given as 'disk,ram,cpu'",
648
   OptComplString)
649

    
650
oVerbose :: OptType
651
oVerbose =
652
  (Option "v" ["verbose"]
653
   (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
654
   "increase the verbosity level",
655
   OptComplNone)
656

    
657
oPriority :: OptType
658
oPriority =
659
  (Option "" ["priority"]
660
   (ReqArg (\ inp opts -> do
661
              prio <- parseSubmitPriority inp
662
              Ok opts { optPriority = Just prio }) "PRIO")
663
   "set the priority of submitted jobs",
664
    OptComplChoices (map fmtSubmitPriority [minBound..maxBound]))
665

    
666
-- | Generic options.
667
genericOpts :: [GenericOptType Options]
668
genericOpts =  [ oShowVer
669
               , oShowHelp
670
               , oShowComp
671
               ]
672

    
673
-- * Functions
674

    
675
-- | Wrapper over 'Common.parseOpts' with our custom options.
676
parseOpts :: [String]               -- ^ The command line arguments
677
          -> String                 -- ^ The program name
678
          -> [OptType]              -- ^ The supported command line options
679
          -> [ArgCompletion]        -- ^ The supported command line arguments
680
          -> IO (Options, [String]) -- ^ The resulting options and leftover
681
                                    -- arguments
682
parseOpts = Common.parseOpts defaultOptions
683

    
684

    
685
-- | A shell script template for autogenerated scripts.
686
shTemplate :: String
687
shTemplate =
688
  printf "#!/bin/sh\n\n\
689
         \# Auto-generated script for executing cluster rebalancing\n\n\
690
         \# To stop, touch the file /tmp/stop-htools\n\n\
691
         \set -e\n\n\
692
         \check() {\n\
693
         \  if [ -f /tmp/stop-htools ]; then\n\
694
         \    echo 'Stop requested, exiting'\n\
695
         \    exit 0\n\
696
         \  fi\n\
697
         \}\n\n"
698

    
699
-- | Optionally print the node list.
700
maybePrintNodes :: Maybe [String]       -- ^ The field list
701
                -> String               -- ^ Informational message
702
                -> ([String] -> String) -- ^ Function to generate the listing
703
                -> IO ()
704
maybePrintNodes Nothing _ _ = return ()
705
maybePrintNodes (Just fields) msg fn = do
706
  hPutStrLn stderr ""
707
  hPutStrLn stderr (msg ++ " status:")
708
  hPutStrLn stderr $ fn fields
709

    
710
-- | Optionally print the instance list.
711
maybePrintInsts :: Bool   -- ^ Whether to print the instance list
712
                -> String -- ^ Type of the instance map (e.g. initial)
713
                -> String -- ^ The instance data
714
                -> IO ()
715
maybePrintInsts do_print msg instdata =
716
  when do_print $ do
717
    hPutStrLn stderr ""
718
    hPutStrLn stderr $ msg ++ " instance map:"
719
    hPutStr stderr instdata
720

    
721
-- | Function to display warning messages from parsing the cluster
722
-- state.
723
maybeShowWarnings :: [String] -- ^ The warning messages
724
                  -> IO ()
725
maybeShowWarnings fix_msgs =
726
  unless (null fix_msgs) $ do
727
    hPutStrLn stderr "Warning: cluster has inconsistent data:"
728
    hPutStrLn stderr . unlines . map (printf "  - %s") $ fix_msgs
729

    
730
-- | Format a list of key, value as a shell fragment.
731
printKeys :: String              -- ^ Prefix to printed variables
732
          -> [(String, String)]  -- ^ List of (key, value) pairs to be printed
733
          -> IO ()
734
printKeys prefix =
735
  mapM_ (\(k, v) ->
736
           printf "%s_%s=%s\n" prefix (map toUpper k) (ensureQuoted v))
737

    
738
-- | Prints the final @OK@ marker in machine readable output.
739
printFinal :: String    -- ^ Prefix to printed variable
740
           -> Bool      -- ^ Whether output should be machine readable;
741
                        -- note: if not, there is nothing to print
742
           -> IO ()
743
printFinal prefix True =
744
  -- this should be the final entry
745
  printKeys prefix [("OK", "1")]
746

    
747
printFinal _ False = return ()
748

    
749
-- | Potentially set the node as offline based on passed offline list.
750
setNodeOffline :: [Ndx] -> Node.Node -> Node.Node
751
setNodeOffline offline_indices n =
752
  if Node.idx n `elem` offline_indices
753
    then Node.setOffline n True
754
    else n
755

    
756
-- | Set node properties based on command line options.
757
setNodeStatus :: Options -> Node.List -> IO Node.List
758
setNodeStatus opts fixed_nl = do
759
  let offline_passed = optOffline opts
760
      all_nodes = Container.elems fixed_nl
761
      offline_lkp = map (lookupName (map Node.name all_nodes)) offline_passed
762
      offline_wrong = filter (not . goodLookupResult) offline_lkp
763
      offline_names = map lrContent offline_lkp
764
      offline_indices = map Node.idx $
765
                        filter (\n -> Node.name n `elem` offline_names)
766
                               all_nodes
767
      m_cpu = optMcpu opts
768
      m_dsk = optMdsk opts
769

    
770
  unless (null offline_wrong) .
771
         exitErr $ printf "wrong node name(s) set as offline: %s\n"
772
                   (commaJoin (map lrContent offline_wrong))
773
  let setMCpuFn = case m_cpu of
774
                    Nothing -> id
775
                    Just new_mcpu -> flip Node.setMcpu new_mcpu
776
  let nm = Container.map (setNodeOffline offline_indices .
777
                          flip Node.setMdsk m_dsk .
778
                          setMCpuFn) fixed_nl
779
  return nm