Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / HTools / CLI.hs @ 7c3a6391

History | View | Annotate | Download (26.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
  , maybeSaveCommands
40
  , maybePrintNodes
41
  , maybePrintInsts
42
  , maybeShowWarnings
43
  , printKeys
44
  , printFinal
45
  , setNodeStatus
46
  -- * The options
47
  , oDataFile
48
  , oDiskMoves
49
  , oDiskTemplate
50
  , oSpindleUse
51
  , oDynuFile
52
  , oMonD
53
  , oMonDDataFile
54
  , oEvacMode
55
  , oExInst
56
  , oExTags
57
  , oExecJobs
58
  , oForce
59
  , oFullEvacuation
60
  , oGroup
61
  , oIAllocSrc
62
  , oIgnoreDyn 
63
  , oIgnoreNonRedundant
64
  , oInstMoves
65
  , oJobDelay
66
  , genOLuxiSocket
67
  , oLuxiSocket
68
  , oMachineReadable
69
  , oMaxCpu
70
  , oMaxSolLength
71
  , oMinDisk
72
  , oMinGain
73
  , oMinGainLim
74
  , oMinResources
75
  , oMinScore
76
  , oNoHeaders
77
  , oNoSimulation
78
  , oNodeSim
79
  , oNodeTags
80
  , oOfflineMaintenance
81
  , oOfflineNode
82
  , oOneStepOnly
83
  , oOutputDir
84
  , oPrintCommands
85
  , oPrintInsts
86
  , oPrintMoves
87
  , oPrintNodes
88
  , oQuiet
89
  , oRapiMaster
90
  , oSaveCluster
91
  , oSelInst
92
  , oShowHelp
93
  , oShowVer
94
  , oShowComp
95
  , oSkipNonRedundant
96
  , oStdSpec
97
  , oTargetResources
98
  , oTieredSpec
99
  , oVerbose
100
  , oPriority
101
  , genericOpts
102
  ) where
103

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

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

    
120
-- * Data types
121

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

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

    
240
-- | Abbreviation for the option type.
241
type OptType = GenericOptType Options
242

    
243
instance StandardOptions Options where
244
  helpRequested = optShowHelp
245
  verRequested  = optShowVer
246
  compRequested = optShowComp
247
  requestHelp o = o { optShowHelp = True }
248
  requestVer  o = o { optShowVer  = True }
249
  requestComp o = o { optShowComp = True }
250

    
251
-- * Helper functions
252

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

    
275
-- | Disk template choices.
276
optComplDiskTemplate :: OptCompletion
277
optComplDiskTemplate = OptComplChoices $
278
                       map diskTemplateToRaw [minBound..maxBound]
279

    
280
-- * Command line options
281

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

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

    
297
oMonD :: OptType
298
oMonD =
299
  (Option "" ["mond"]
300
   (NoArg (\ opts -> Ok opts {optMonD = True}))
301
   "Query MonDs",
302
   OptComplNone)
303

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
445
oLuxiSocket :: IO OptType
446
oLuxiSocket = liftM genOLuxiSocket Path.defaultLuxiSocket
447

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

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

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

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

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

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

    
505
oMinResources :: OptType
506
oMinResources =
507
  (Option "" ["minimal-resources"]
508
   (reqWithConversion (tryRead "minimal resources")
509
    (\d opts -> Ok opts { optMinResources = d}) "FACTOR")
510
   "minimal resources to be present on each in multiples of\ 
511
   \ the standard allocation for not onlining standby nodes",
512
   OptComplFloat)
513

    
514
oMinScore :: OptType
515
oMinScore =
516
  (Option "e" ["min-score"]
517
   (reqWithConversion (tryRead "min score")
518
    (\e opts -> Ok opts { optMinScore = e }) "EPSILON")
519
   "mininum score to aim for",
520
   OptComplFloat)
521

    
522
oNoHeaders :: OptType
523
oNoHeaders =
524
  (Option "" ["no-headers"]
525
   (NoArg (\ opts -> Ok opts { optNoHeaders = True }))
526
   "do not show a header line",
527
   OptComplNone)
528

    
529
oNoSimulation :: OptType
530
oNoSimulation =
531
  (Option "" ["no-simulation"]
532
   (NoArg (\opts -> Ok opts {optNoSimulation = True}))
533
   "do not perform rebalancing simulation",
534
   OptComplNone)
535

    
536
oNodeSim :: OptType
537
oNodeSim =
538
  (Option "" ["simulate"]
539
   (ReqArg (\ f o -> Ok o { optNodeSim = f:optNodeSim o }) "SPEC")
540
   "simulate an empty cluster, given as\
541
   \ 'alloc_policy,num_nodes,disk,ram,cpu'",
542
   OptComplString)
543

    
544
oNodeTags :: OptType
545
oNodeTags =
546
  (Option "" ["node-tags"]
547
   (ReqArg (\ f opts -> Ok opts { optNodeTags = Just $ sepSplit ',' f })
548
    "TAG,...") "Restrict to nodes with the given tags",
549
   OptComplString)
550
     
551
oOfflineMaintenance :: OptType
552
oOfflineMaintenance =
553
  (Option "" ["offline-maintenance"]
554
   (NoArg (\ opts -> Ok opts {optOfflineMaintenance = True}))
555
   "Schedule offline maintenance, i.e., pretend that all instance are\
556
   \ offline.",
557
   OptComplNone)
558

    
559
oOfflineNode :: OptType
560
oOfflineNode =
561
  (Option "O" ["offline"]
562
   (ReqArg (\ n o -> Ok o { optOffline = n:optOffline o }) "NODE")
563
   "set node as offline",
564
   OptComplOneNode)
565

    
566
oOneStepOnly :: OptType
567
oOneStepOnly =
568
  (Option "" ["one-step-only"]
569
   (NoArg (\ opts -> Ok opts {optOneStepOnly = True}))
570
   "Only do the first step",
571
   OptComplNone)
572

    
573
oOutputDir :: OptType
574
oOutputDir =
575
  (Option "d" ["output-dir"]
576
   (ReqArg (\ d opts -> Ok opts { optOutPath = d }) "PATH")
577
   "directory in which to write output files",
578
   OptComplDir)
579

    
580
oPrintCommands :: OptType
581
oPrintCommands =
582
  (Option "C" ["print-commands"]
583
   (OptArg ((\ f opts -> Ok opts { optShowCmds = Just f }) .
584
            fromMaybe "-")
585
    "FILE")
586
   "print the ganeti command list for reaching the solution,\
587
   \ if an argument is passed then write the commands to a\
588
   \ file named as such",
589
   OptComplNone)
590

    
591
oPrintInsts :: OptType
592
oPrintInsts =
593
  (Option "" ["print-instances"]
594
   (NoArg (\ opts -> Ok opts { optShowInsts = True }))
595
   "print the final instance map",
596
   OptComplNone)
597

    
598
oPrintMoves :: OptType
599
oPrintMoves =
600
  (Option "" ["print-moves"]
601
   (NoArg (\ opts -> Ok opts { optPrintMoves = True }))
602
   "print the moves of the instances",
603
   OptComplNone)
604

    
605
oPrintNodes :: OptType
606
oPrintNodes =
607
  (Option "p" ["print-nodes"]
608
   (OptArg ((\ f opts ->
609
               let (prefix, realf) = case f of
610
                                       '+':rest -> (["+"], rest)
611
                                       _ -> ([], f)
612
                   splitted = prefix ++ sepSplit ',' realf
613
               in Ok opts { optShowNodes = Just splitted }) .
614
            fromMaybe []) "FIELDS")
615
   "print the final node list",
616
   OptComplNone)
617

    
618
oQuiet :: OptType
619
oQuiet =
620
  (Option "q" ["quiet"]
621
   (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts - 1 }))
622
   "decrease the verbosity level",
623
   OptComplNone)
624

    
625
oRapiMaster :: OptType
626
oRapiMaster =
627
  (Option "m" ["master"]
628
   (ReqArg (\ m opts -> Ok opts { optMaster = m }) "ADDRESS")
629
   "collect data via RAPI at the given ADDRESS",
630
   OptComplHost)
631

    
632
oSaveCluster :: OptType
633
oSaveCluster =
634
  (Option "S" ["save"]
635
   (ReqArg (\ f opts -> Ok opts { optSaveCluster = Just f }) "FILE")
636
   "Save cluster state at the end of the processing to FILE",
637
   OptComplNone)
638

    
639
oSkipNonRedundant :: OptType
640
oSkipNonRedundant =
641
  (Option "" ["skip-non-redundant"]
642
   (NoArg (\ opts -> Ok opts { optSkipNonRedundant = True }))
643
    "Skip nodes that host a non-redundant instance",
644
    OptComplNone)
645

    
646
oStdSpec :: OptType
647
oStdSpec =
648
  (Option "" ["standard-alloc"]
649
   (ReqArg (\ inp opts -> do
650
              tspec <- parseISpecString "standard" inp
651
              return $ opts { optStdSpec = Just tspec } )
652
    "STDSPEC")
653
   "enable standard specs allocation, given as 'disk,ram,cpu'",
654
   OptComplString)
655

    
656
oTargetResources :: OptType
657
oTargetResources =
658
  (Option "" ["target-resources"]
659
   (reqWithConversion (tryRead "target resources")
660
    (\d opts -> Ok opts { optTargetResources = d}) "FACTOR")
661
   "target resources to be left on each node after squeezing in\
662
   \ multiples of the standard allocation",
663
   OptComplFloat)
664

    
665
oTieredSpec :: OptType
666
oTieredSpec =
667
  (Option "" ["tiered-alloc"]
668
   (ReqArg (\ inp opts -> do
669
              tspec <- parseISpecString "tiered" inp
670
              return $ opts { optTieredSpec = Just tspec } )
671
    "TSPEC")
672
   "enable tiered specs allocation, given as 'disk,ram,cpu'",
673
   OptComplString)
674

    
675
oVerbose :: OptType
676
oVerbose =
677
  (Option "v" ["verbose"]
678
   (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
679
   "increase the verbosity level",
680
   OptComplNone)
681

    
682
oPriority :: OptType
683
oPriority =
684
  (Option "" ["priority"]
685
   (ReqArg (\ inp opts -> do
686
              prio <- parseSubmitPriority inp
687
              Ok opts { optPriority = Just prio }) "PRIO")
688
   "set the priority of submitted jobs",
689
    OptComplChoices (map fmtSubmitPriority [minBound..maxBound]))
690

    
691
-- | Generic options.
692
genericOpts :: [GenericOptType Options]
693
genericOpts =  [ oShowVer
694
               , oShowHelp
695
               , oShowComp
696
               ]
697

    
698
-- * Functions
699

    
700
-- | Wrapper over 'Common.parseOpts' with our custom options.
701
parseOpts :: [String]               -- ^ The command line arguments
702
          -> String                 -- ^ The program name
703
          -> [OptType]              -- ^ The supported command line options
704
          -> [ArgCompletion]        -- ^ The supported command line arguments
705
          -> IO (Options, [String]) -- ^ The resulting options and leftover
706
                                    -- arguments
707
parseOpts = Common.parseOpts defaultOptions
708

    
709

    
710
-- | A shell script template for autogenerated scripts.
711
shTemplate :: String
712
shTemplate =
713
  printf "#!/bin/sh\n\n\
714
         \# Auto-generated script for executing cluster rebalancing\n\n\
715
         \# To stop, touch the file /tmp/stop-htools\n\n\
716
         \set -e\n\n\
717
         \check() {\n\
718
         \  if [ -f /tmp/stop-htools ]; then\n\
719
         \    echo 'Stop requested, exiting'\n\
720
         \    exit 0\n\
721
         \  fi\n\
722
         \}\n\n"
723

    
724
-- | Optionally show or save a list of commands
725
maybeSaveCommands :: String -- ^ Informal description
726
                  -> Options
727
                  -> String -- ^ commands
728
                  -> IO ()
729
maybeSaveCommands msg opts cmds =
730
  case optShowCmds opts of
731
    Nothing -> return ()
732
    Just "-" -> do
733
      putStrLn ""
734
      putStrLn msg
735
      putStr . unlines .  map ("  " ++) . filter (/= "  check") . lines $ cmds
736
    Just out_path -> do
737
      writeFile out_path (shTemplate ++ cmds)
738
      printf "The commands have been written to file '%s'\n" out_path
739

    
740
-- | Optionally print the node list.
741
maybePrintNodes :: Maybe [String]       -- ^ The field list
742
                -> String               -- ^ Informational message
743
                -> ([String] -> String) -- ^ Function to generate the listing
744
                -> IO ()
745
maybePrintNodes Nothing _ _ = return ()
746
maybePrintNodes (Just fields) msg fn = do
747
  hPutStrLn stderr ""
748
  hPutStrLn stderr (msg ++ " status:")
749
  hPutStrLn stderr $ fn fields
750

    
751
-- | Optionally print the instance list.
752
maybePrintInsts :: Bool   -- ^ Whether to print the instance list
753
                -> String -- ^ Type of the instance map (e.g. initial)
754
                -> String -- ^ The instance data
755
                -> IO ()
756
maybePrintInsts do_print msg instdata =
757
  when do_print $ do
758
    hPutStrLn stderr ""
759
    hPutStrLn stderr $ msg ++ " instance map:"
760
    hPutStr stderr instdata
761

    
762
-- | Function to display warning messages from parsing the cluster
763
-- state.
764
maybeShowWarnings :: [String] -- ^ The warning messages
765
                  -> IO ()
766
maybeShowWarnings fix_msgs =
767
  unless (null fix_msgs) $ do
768
    hPutStrLn stderr "Warning: cluster has inconsistent data:"
769
    hPutStrLn stderr . unlines . map (printf "  - %s") $ fix_msgs
770

    
771
-- | Format a list of key, value as a shell fragment.
772
printKeys :: String              -- ^ Prefix to printed variables
773
          -> [(String, String)]  -- ^ List of (key, value) pairs to be printed
774
          -> IO ()
775
printKeys prefix =
776
  mapM_ (\(k, v) ->
777
           printf "%s_%s=%s\n" prefix (map toUpper k) (ensureQuoted v))
778

    
779
-- | Prints the final @OK@ marker in machine readable output.
780
printFinal :: String    -- ^ Prefix to printed variable
781
           -> Bool      -- ^ Whether output should be machine readable;
782
                        -- note: if not, there is nothing to print
783
           -> IO ()
784
printFinal prefix True =
785
  -- this should be the final entry
786
  printKeys prefix [("OK", "1")]
787

    
788
printFinal _ False = return ()
789

    
790
-- | Potentially set the node as offline based on passed offline list.
791
setNodeOffline :: [Ndx] -> Node.Node -> Node.Node
792
setNodeOffline offline_indices n =
793
  if Node.idx n `elem` offline_indices
794
    then Node.setOffline n True
795
    else n
796

    
797
-- | Set node properties based on command line options.
798
setNodeStatus :: Options -> Node.List -> IO Node.List
799
setNodeStatus opts fixed_nl = do
800
  let offline_passed = optOffline opts
801
      all_nodes = Container.elems fixed_nl
802
      offline_lkp = map (lookupName (map Node.name all_nodes)) offline_passed
803
      offline_wrong = filter (not . goodLookupResult) offline_lkp
804
      offline_names = map lrContent offline_lkp
805
      offline_indices = map Node.idx $
806
                        filter (\n -> Node.name n `elem` offline_names)
807
                               all_nodes
808
      m_cpu = optMcpu opts
809
      m_dsk = optMdsk opts
810

    
811
  unless (null offline_wrong) .
812
         exitErr $ printf "wrong node name(s) set as offline: %s\n"
813
                   (commaJoin (map lrContent offline_wrong))
814
  let setMCpuFn = case m_cpu of
815
                    Nothing -> id
816
                    Just new_mcpu -> flip Node.setMcpu new_mcpu
817
  let nm = Container.map (setNodeOffline offline_indices .
818
                          flip Node.setMdsk m_dsk .
819
                          setMCpuFn) fixed_nl
820
  return nm