Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ 4fbc93dd

History | View | Annotate | Download (23.2 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2006, 2007 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
# pylint: disable-msg=W0401,W0614
23
# W0401: Wildcard import ganeti.cli
24
# W0614: Unused import %s from wildcard import (since we need cli)
25

    
26
import sys
27
import os.path
28
import time
29

    
30
from ganeti.cli import *
31
from ganeti import opcodes
32
from ganeti import constants
33
from ganeti import errors
34
from ganeti import utils
35
from ganeti import bootstrap
36
from ganeti import ssh
37
from ganeti import objects
38

    
39

    
40
@UsesRPC
41
def InitCluster(opts, args):
42
  """Initialize the cluster.
43

    
44
  @param opts: the command line options selected by the user
45
  @type args: list
46
  @param args: should contain only one element, the desired
47
      cluster name
48
  @rtype: int
49
  @return: the desired exit code
50

    
51
  """
52
  if not opts.lvm_storage and opts.vg_name:
53
    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
54
    return 1
55

    
56
  vg_name = opts.vg_name
57
  if opts.lvm_storage and not opts.vg_name:
58
    vg_name = constants.DEFAULT_VG
59

    
60
  hvlist = opts.enabled_hypervisors
61
  if hvlist is None:
62
    hvlist = constants.DEFAULT_ENABLED_HYPERVISOR
63
  hvlist = hvlist.split(",")
64

    
65
  hvparams = dict(opts.hvparams)
66
  beparams = opts.beparams
67
  nicparams = opts.nicparams
68

    
69
  # prepare beparams dict
70
  beparams = objects.FillDict(constants.BEC_DEFAULTS, beparams)
71
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
72

    
73
  # prepare nicparams dict
74
  nicparams = objects.FillDict(constants.NICC_DEFAULTS, nicparams)
75
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
76

    
77
  # prepare hvparams dict
78
  for hv in constants.HYPER_TYPES:
79
    if hv not in hvparams:
80
      hvparams[hv] = {}
81
    hvparams[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], hvparams[hv])
82
    utils.ForceDictType(hvparams[hv], constants.HVS_PARAMETER_TYPES)
83

    
84
  bootstrap.InitCluster(cluster_name=args[0],
85
                        secondary_ip=opts.secondary_ip,
86
                        vg_name=vg_name,
87
                        mac_prefix=opts.mac_prefix,
88
                        master_netdev=opts.master_netdev,
89
                        file_storage_dir=opts.file_storage_dir,
90
                        enabled_hypervisors=hvlist,
91
                        hvparams=hvparams,
92
                        beparams=beparams,
93
                        nicparams=nicparams,
94
                        candidate_pool_size=opts.candidate_pool_size,
95
                        modify_etc_hosts=opts.modify_etc_hosts,
96
                        )
97
  op = opcodes.OpPostInitCluster()
98
  SubmitOpCode(op)
99
  return 0
100

    
101

    
102
@UsesRPC
103
def DestroyCluster(opts, args):
104
  """Destroy the cluster.
105

    
106
  @param opts: the command line options selected by the user
107
  @type args: list
108
  @param args: should be an empty list
109
  @rtype: int
110
  @return: the desired exit code
111

    
112
  """
113
  if not opts.yes_do_it:
114
    ToStderr("Destroying a cluster is irreversible. If you really want"
115
             " destroy this cluster, supply the --yes-do-it option.")
116
    return 1
117

    
118
  op = opcodes.OpDestroyCluster()
119
  master = SubmitOpCode(op)
120
  # if we reached this, the opcode didn't fail; we can proceed to
121
  # shutdown all the daemons
122
  bootstrap.FinalizeClusterDestroy(master)
123
  return 0
124

    
125

    
126
def RenameCluster(opts, args):
127
  """Rename the cluster.
128

    
129
  @param opts: the command line options selected by the user
130
  @type args: list
131
  @param args: should contain only one element, the new cluster name
132
  @rtype: int
133
  @return: the desired exit code
134

    
135
  """
136
  name = args[0]
137
  if not opts.force:
138
    usertext = ("This will rename the cluster to '%s'. If you are connected"
139
                " over the network to the cluster name, the operation is very"
140
                " dangerous as the IP address will be removed from the node"
141
                " and the change may not go through. Continue?") % name
142
    if not AskUser(usertext):
143
      return 1
144

    
145
  op = opcodes.OpRenameCluster(name=name)
146
  SubmitOpCode(op)
147
  return 0
148

    
149

    
150
def RedistributeConfig(opts, args):
151
  """Forces push of the cluster configuration.
152

    
153
  @param opts: the command line options selected by the user
154
  @type args: list
155
  @param args: empty list
156
  @rtype: int
157
  @return: the desired exit code
158

    
159
  """
160
  op = opcodes.OpRedistributeConfig()
161
  SubmitOrSend(op, opts)
162
  return 0
163

    
164

    
165
def ShowClusterVersion(opts, args):
166
  """Write version of ganeti software to the standard output.
167

    
168
  @param opts: the command line options selected by the user
169
  @type args: list
170
  @param args: should be an empty list
171
  @rtype: int
172
  @return: the desired exit code
173

    
174
  """
175
  cl = GetClient()
176
  result = cl.QueryClusterInfo()
177
  ToStdout("Software version: %s", result["software_version"])
178
  ToStdout("Internode protocol: %s", result["protocol_version"])
179
  ToStdout("Configuration format: %s", result["config_version"])
180
  ToStdout("OS api version: %s", result["os_api_version"])
181
  ToStdout("Export interface: %s", result["export_version"])
182
  return 0
183

    
184

    
185
def ShowClusterMaster(opts, args):
186
  """Write name of master node to the standard output.
187

    
188
  @param opts: the command line options selected by the user
189
  @type args: list
190
  @param args: should be an empty list
191
  @rtype: int
192
  @return: the desired exit code
193

    
194
  """
195
  master = bootstrap.GetMaster()
196
  ToStdout(master)
197
  return 0
198

    
199
def _PrintGroupedParams(paramsdict):
200
  """Print Grouped parameters (be, nic, disk) by group.
201

    
202
  @type paramsdict: dict of dicts
203
  @param paramsdict: {group: {param: value, ...}, ...}
204

    
205
  """
206
  for gr_name, gr_dict in paramsdict.items():
207
    ToStdout("  - %s:", gr_name)
208
    for item, val in gr_dict.iteritems():
209
      ToStdout("      %s: %s", item, val)
210

    
211
def ShowClusterConfig(opts, args):
212
  """Shows cluster information.
213

    
214
  @param opts: the command line options selected by the user
215
  @type args: list
216
  @param args: should be an empty list
217
  @rtype: int
218
  @return: the desired exit code
219

    
220
  """
221
  cl = GetClient()
222
  result = cl.QueryClusterInfo()
223

    
224
  ToStdout("Cluster name: %s", result["name"])
225

    
226
  ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
227
  ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))
228

    
229
  ToStdout("Master node: %s", result["master"])
230

    
231
  ToStdout("Architecture (this node): %s (%s)",
232
           result["architecture"][0], result["architecture"][1])
233

    
234
  if result["tags"]:
235
    tags = ", ".join(utils.NiceSort(result["tags"]))
236
  else:
237
    tags = "(none)"
238

    
239
  ToStdout("Tags: %s", tags)
240

    
241
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
242
  ToStdout("Enabled hypervisors: %s", ", ".join(result["enabled_hypervisors"]))
243

    
244
  ToStdout("Hypervisor parameters:")
245
  _PrintGroupedParams(result["hvparams"])
246

    
247
  ToStdout("Cluster parameters:")
248
  ToStdout("  - candidate pool size: %s", result["candidate_pool_size"])
249
  ToStdout("  - master netdev: %s", result["master_netdev"])
250
  ToStdout("  - lvm volume group: %s", result["volume_group_name"])
251
  ToStdout("  - file storage path: %s", result["file_storage_dir"])
252

    
253
  ToStdout("Default instance parameters:")
254
  _PrintGroupedParams(result["beparams"])
255

    
256
  ToStdout("Default nic parameters:")
257
  _PrintGroupedParams(result["nicparams"])
258

    
259
  return 0
260

    
261

    
262
def ClusterCopyFile(opts, args):
263
  """Copy a file from master to some nodes.
264

    
265
  @param opts: the command line options selected by the user
266
  @type args: list
267
  @param args: should contain only one element, the path of
268
      the file to be copied
269
  @rtype: int
270
  @return: the desired exit code
271

    
272
  """
273
  filename = args[0]
274
  if not os.path.exists(filename):
275
    raise errors.OpPrereqError("No such filename '%s'" % filename)
276

    
277
  cl = GetClient()
278

    
279
  myname = utils.HostInfo().name
280

    
281
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
282

    
283
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl)
284
  results = [name for name in results if name != myname]
285

    
286
  srun = ssh.SshRunner(cluster_name=cluster_name)
287
  for node in results:
288
    if not srun.CopyFileToNode(node, filename):
289
      ToStderr("Copy of file %s to node %s failed", filename, node)
290

    
291
  return 0
292

    
293

    
294
def RunClusterCommand(opts, args):
295
  """Run a command on some nodes.
296

    
297
  @param opts: the command line options selected by the user
298
  @type args: list
299
  @param args: should contain the command to be run and its arguments
300
  @rtype: int
301
  @return: the desired exit code
302

    
303
  """
304
  cl = GetClient()
305

    
306
  command = " ".join(args)
307

    
308
  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
309

    
310
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
311
                                                    "master_node"])
312

    
313
  srun = ssh.SshRunner(cluster_name=cluster_name)
314

    
315
  # Make sure master node is at list end
316
  if master_node in nodes:
317
    nodes.remove(master_node)
318
    nodes.append(master_node)
319

    
320
  for name in nodes:
321
    result = srun.Run(name, "root", command)
322
    ToStdout("------------------------------------------------")
323
    ToStdout("node: %s", name)
324
    ToStdout("%s", result.output)
325
    ToStdout("return code = %s", result.exit_code)
326

    
327
  return 0
328

    
329

    
330
def VerifyCluster(opts, args):
331
  """Verify integrity of cluster, performing various test on nodes.
332

    
333
  @param opts: the command line options selected by the user
334
  @type args: list
335
  @param args: should be an empty list
336
  @rtype: int
337
  @return: the desired exit code
338

    
339
  """
340
  skip_checks = []
341
  if opts.skip_nplusone_mem:
342
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
343
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
344
                               verbose=opts.verbose,
345
                               error_codes=opts.error_codes,
346
                               debug_simulate_errors=opts.simulate_errors)
347
  if SubmitOpCode(op):
348
    return 0
349
  else:
350
    return 1
351

    
352

    
353
def VerifyDisks(opts, args):
354
  """Verify integrity of cluster disks.
355

    
356
  @param opts: the command line options selected by the user
357
  @type args: list
358
  @param args: should be an empty list
359
  @rtype: int
360
  @return: the desired exit code
361

    
362
  """
363
  op = opcodes.OpVerifyDisks()
364
  result = SubmitOpCode(op)
365
  if not isinstance(result, (list, tuple)) or len(result) != 3:
366
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
367

    
368
  bad_nodes, instances, missing = result
369

    
370
  retcode = constants.EXIT_SUCCESS
371

    
372
  if bad_nodes:
373
    for node, text in bad_nodes.items():
374
      ToStdout("Error gathering data on node %s: %s",
375
               node, utils.SafeEncode(text[-400:]))
376
      retcode |= 1
377
      ToStdout("You need to fix these nodes first before fixing instances")
378

    
379
  if instances:
380
    for iname in instances:
381
      if iname in missing:
382
        continue
383
      op = opcodes.OpActivateInstanceDisks(instance_name=iname)
384
      try:
385
        ToStdout("Activating disks for instance '%s'", iname)
386
        SubmitOpCode(op)
387
      except errors.GenericError, err:
388
        nret, msg = FormatError(err)
389
        retcode |= nret
390
        ToStderr("Error activating disks for instance %s: %s", iname, msg)
391

    
392
  if missing:
393
    for iname, ival in missing.iteritems():
394
      all_missing = utils.all(ival, lambda x: x[0] in bad_nodes)
395
      if all_missing:
396
        ToStdout("Instance %s cannot be verified as it lives on"
397
                 " broken nodes", iname)
398
      else:
399
        ToStdout("Instance %s has missing logical volumes:", iname)
400
        ival.sort()
401
        for node, vol in ival:
402
          if node in bad_nodes:
403
            ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
404
          else:
405
            ToStdout("\t%s /dev/xenvg/%s", node, vol)
406
    ToStdout("You need to run replace_disks for all the above"
407
           " instances, if this message persist after fixing nodes.")
408
    retcode |= 1
409

    
410
  return retcode
411

    
412

    
413
def RepairDiskSizes(opts, args):
414
  """Verify sizes of cluster disks.
415

    
416
  @param opts: the command line options selected by the user
417
  @type args: list
418
  @param args: optional list of instances to restrict check to
419
  @rtype: int
420
  @return: the desired exit code
421

    
422
  """
423
  op = opcodes.OpRepairDiskSizes(instances=args)
424
  SubmitOpCode(op)
425

    
426

    
427
@UsesRPC
428
def MasterFailover(opts, args):
429
  """Failover the master node.
430

    
431
  This command, when run on a non-master node, will cause the current
432
  master to cease being master, and the non-master to become new
433
  master.
434

    
435
  @param opts: the command line options selected by the user
436
  @type args: list
437
  @param args: should be an empty list
438
  @rtype: int
439
  @return: the desired exit code
440

    
441
  """
442
  if opts.no_voting:
443
    usertext = ("This will perform the failover even if most other nodes"
444
                " are down, or if this node is outdated. This is dangerous"
445
                " as it can lead to a non-consistent cluster. Check the"
446
                " gnt-cluster(8) man page before proceeding. Continue?")
447
    if not AskUser(usertext):
448
      return 1
449

    
450
  return bootstrap.MasterFailover(no_voting=opts.no_voting)
451

    
452

    
453
def SearchTags(opts, args):
454
  """Searches the tags on all the cluster.
455

    
456
  @param opts: the command line options selected by the user
457
  @type args: list
458
  @param args: should contain only one element, the tag pattern
459
  @rtype: int
460
  @return: the desired exit code
461

    
462
  """
463
  op = opcodes.OpSearchTags(pattern=args[0])
464
  result = SubmitOpCode(op)
465
  if not result:
466
    return 1
467
  result = list(result)
468
  result.sort()
469
  for path, tag in result:
470
    ToStdout("%s %s", path, tag)
471

    
472

    
473
def SetClusterParams(opts, args):
474
  """Modify the cluster.
475

    
476
  @param opts: the command line options selected by the user
477
  @type args: list
478
  @param args: should be an empty list
479
  @rtype: int
480
  @return: the desired exit code
481

    
482
  """
483
  if not (not opts.lvm_storage or opts.vg_name or
484
          opts.enabled_hypervisors or opts.hvparams or
485
          opts.beparams or opts.nicparams or
486
          opts.candidate_pool_size is not None):
487
    ToStderr("Please give at least one of the parameters.")
488
    return 1
489

    
490
  vg_name = opts.vg_name
491
  if not opts.lvm_storage and opts.vg_name:
492
    ToStdout("Options --no-lvm-storage and --vg-name conflict.")
493
    return 1
494
  elif not opts.lvm_storage:
495
    vg_name = ''
496

    
497
  hvlist = opts.enabled_hypervisors
498
  if hvlist is not None:
499
    hvlist = hvlist.split(",")
500

    
501
  # a list of (name, dict) we can pass directly to dict() (or [])
502
  hvparams = dict(opts.hvparams)
503
  for hv, hv_params in hvparams.iteritems():
504
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
505

    
506
  beparams = opts.beparams
507
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
508

    
509
  nicparams = opts.nicparams
510
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
511

    
512
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
513
                                  enabled_hypervisors=hvlist,
514
                                  hvparams=hvparams,
515
                                  beparams=beparams,
516
                                  nicparams=nicparams,
517
                                  candidate_pool_size=opts.candidate_pool_size)
518
  SubmitOpCode(op)
519
  return 0
520

    
521

    
522
def QueueOps(opts, args):
523
  """Queue operations.
524

    
525
  @param opts: the command line options selected by the user
526
  @type args: list
527
  @param args: should contain only one element, the subcommand
528
  @rtype: int
529
  @return: the desired exit code
530

    
531
  """
532
  command = args[0]
533
  client = GetClient()
534
  if command in ("drain", "undrain"):
535
    drain_flag = command == "drain"
536
    client.SetQueueDrainFlag(drain_flag)
537
  elif command == "info":
538
    result = client.QueryConfigValues(["drain_flag"])
539
    if result[0]:
540
      val = "set"
541
    else:
542
      val = "unset"
543
    ToStdout("The drain flag is %s" % val)
544
  else:
545
    raise errors.OpPrereqError("Command '%s' is not valid." % command)
546

    
547
  return 0
548

    
549

    
550
def _ShowWatcherPause(until):
551
  if until is None or until < time.time():
552
    ToStdout("The watcher is not paused.")
553
  else:
554
    ToStdout("The watcher is paused until %s.", time.ctime(until))
555

    
556

    
557
def WatcherOps(opts, args):
558
  """Watcher operations.
559

    
560
  @param opts: the command line options selected by the user
561
  @type args: list
562
  @param args: should contain only one element, the subcommand
563
  @rtype: int
564
  @return: the desired exit code
565

    
566
  """
567
  command = args[0]
568
  client = GetClient()
569

    
570
  if command == "continue":
571
    client.SetWatcherPause(None)
572
    ToStdout("The watcher is no longer paused.")
573

    
574
  elif command == "pause":
575
    if len(args) < 2:
576
      raise errors.OpPrereqError("Missing pause duration")
577

    
578
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
579
    _ShowWatcherPause(result)
580

    
581
  elif command == "info":
582
    result = client.QueryConfigValues(["watcher_pause"])
583
    _ShowWatcherPause(result)
584

    
585
  else:
586
    raise errors.OpPrereqError("Command '%s' is not valid." % command)
587

    
588
  return 0
589

    
590

    
591
commands = {
592
  'init': (InitCluster, [ArgHost(min=1, max=1)],
593
           [DEBUG_OPT,
594
            SECONDARY_IP_OPT,
595
            cli_option("-m", "--mac-prefix", dest="mac_prefix",
596
                       help="Specify the mac prefix for the instance IP"
597
                       " addresses, in the format XX:XX:XX",
598
                       metavar="PREFIX",
599
                       default=constants.DEFAULT_MAC_PREFIX,),
600
            cli_option("-g", "--vg-name", dest="vg_name",
601
                       help="Specify the volume group name "
602
                       " (cluster-wide) for disk allocation [xenvg]",
603
                       metavar="VG",
604
                       default=None,),
605
            cli_option("--master-netdev", dest="master_netdev",
606
                       help="Specify the node interface (cluster-wide)"
607
                         " on which the master IP address will be added "
608
                         " [%s]" % constants.DEFAULT_BRIDGE,
609
                       metavar="NETDEV",
610
                       default=constants.DEFAULT_BRIDGE,),
611
            cli_option("--file-storage-dir", dest="file_storage_dir",
612
                       help="Specify the default directory (cluster-wide)"
613
                            " for storing the file-based disks [%s]" %
614
                            constants.DEFAULT_FILE_STORAGE_DIR,
615
                       metavar="DIR",
616
                       default=constants.DEFAULT_FILE_STORAGE_DIR,),
617
            NOLVM_STORAGE_OPT,
618
            cli_option("--no-etc-hosts", dest="modify_etc_hosts",
619
                       help="Don't modify /etc/hosts"
620
                            " (cluster-wide)",
621
                       action="store_false", default=True,),
622
            ENABLED_HV_OPT,
623
            HVLIST_OPT,
624
            BACKEND_OPT,
625
            NIC_PARAMS_OPT,
626
            cli_option("-C", "--candidate-pool-size",
627
                       default=constants.MASTER_POOL_SIZE_DEFAULT,
628
                       help="Set the candidate pool size",
629
                       dest="candidate_pool_size", type="int"),
630
            ],
631
           "[opts...] <cluster_name>",
632
           "Initialises a new cluster configuration"),
633
  'destroy': (DestroyCluster, ARGS_NONE,
634
              [DEBUG_OPT,
635
               cli_option("--yes-do-it", dest="yes_do_it",
636
                          help="Destroy cluster",
637
                          action="store_true"),
638
              ],
639
              "", "Destroy cluster"),
640
  'rename': (RenameCluster, [ArgHost(min=1, max=1)],
641
             [DEBUG_OPT, FORCE_OPT],
642
             "<new_name>",
643
             "Renames the cluster"),
644
  'redist-conf': (RedistributeConfig, ARGS_NONE, [DEBUG_OPT, SUBMIT_OPT],
645
                  "",
646
                  "Forces a push of the configuration file and ssconf files"
647
                  " to the nodes in the cluster"),
648
  'verify': (VerifyCluster, ARGS_NONE,
649
             [DEBUG_OPT, VERBOSE_OPT, DEBUG_SIMERR_OPT,
650
              cli_option("--error-codes", dest="error_codes",
651
                         help="Enable parseable error messages",
652
                         action="store_true", default=False),
653
              cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
654
                         help="Skip N+1 memory redundancy tests",
655
                         action="store_true", default=False),
656
              ],
657
             "", "Does a check on the cluster configuration"),
658
  'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
659
                   "", "Does a check on the cluster disk status"),
660
  'repair-disk-sizes': (RepairDiskSizes, ARGS_MANY_INSTANCES, [DEBUG_OPT],
661
                   "", "Updates mismatches in recorded disk sizes"),
662
  'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT,
663
                     cli_option("--no-voting", dest="no_voting",
664
                                help="Skip node agreement check (dangerous)",
665
                                action="store_true",
666
                                default=False,),
667
                     ],
668
                     "", "Makes the current node the master"),
669
  'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
670
              "", "Shows the cluster version"),
671
  'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
672
                "", "Shows the cluster master"),
673
  'copyfile': (ClusterCopyFile, [ArgFile(min=1, max=1)],
674
               [DEBUG_OPT, NODE_LIST_OPT],
675
               "[-n node...] <filename>",
676
               "Copies a file to all (or only some) nodes"),
677
  'command': (RunClusterCommand, [ArgCommand(min=1)],
678
              [DEBUG_OPT, NODE_LIST_OPT],
679
              "[-n node...] <command>",
680
              "Runs a command on all (or only some) nodes"),
681
  'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
682
           "", "Show cluster configuration"),
683
  'list-tags': (ListTags, ARGS_NONE,
684
                [DEBUG_OPT], "", "List the tags of the cluster"),
685
  'add-tags': (AddTags, [ArgUnknown()], [DEBUG_OPT, TAG_SRC_OPT],
686
               "tag...", "Add tags to the cluster"),
687
  'remove-tags': (RemoveTags, [ArgUnknown()], [DEBUG_OPT, TAG_SRC_OPT],
688
                  "tag...", "Remove tags from the cluster"),
689
  'search-tags': (SearchTags, [ArgUnknown(min=1, max=1)],
690
                  [DEBUG_OPT], "", "Searches the tags on all objects on"
691
                  " the cluster for a given pattern (regex)"),
692
  'queue': (QueueOps,
693
            [ArgChoice(min=1, max=1, choices=["drain", "undrain", "info"])],
694
            [DEBUG_OPT],
695
            "drain|undrain|info", "Change queue properties"),
696
  'watcher': (WatcherOps,
697
              [ArgChoice(min=1, max=1,
698
                         choices=["pause", "continue", "info"]),
699
               ArgSuggest(min=0, max=1, choices=["30m", "1h", "4h"])],
700
              [DEBUG_OPT],
701
              "{pause <timespec>|continue|info}", "Change watcher properties"),
702
  'modify': (SetClusterParams, ARGS_NONE,
703
             [DEBUG_OPT,
704
              cli_option("-g", "--vg-name", dest="vg_name",
705
                         help="Specify the volume group name "
706
                         " (cluster-wide) for disk allocation "
707
                         "and enable lvm based storage",
708
                         metavar="VG",),
709
              NOLVM_STORAGE_OPT,
710
              ENABLED_HV_OPT,
711
              HVLIST_OPT,
712
              BACKEND_OPT,
713
              NIC_PARAMS_OPT,
714
              cli_option("-C", "--candidate-pool-size", default=None,
715
                         help="Set the candidate pool size",
716
                         dest="candidate_pool_size", type="int"),
717
              ],
718
             "[opts...]",
719
             "Alters the parameters of the cluster"),
720
  }
721

    
722
if __name__ == '__main__':
723
  sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))