Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ c118d1f4

History | View | Annotate | Download (24.4 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
from optparse import make_option
28
import os.path
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

    
38

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

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

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

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

    
59
  hvlist = opts.enabled_hypervisors
60
  if hvlist is not None:
61
    hvlist = hvlist.split(",")
62
  else:
63
    hvlist = [opts.default_hypervisor]
64

    
65
  # avoid an impossible situation
66
  if opts.default_hypervisor not in hvlist:
67
    ToStderr("The default hypervisor requested (%s) is not"
68
             " within the enabled hypervisor list (%s)" %
69
             (opts.default_hypervisor, hvlist))
70
    return 1
71

    
72
  hvparams = dict(opts.hvparams)
73

    
74
  beparams = opts.beparams
75
  # check for invalid parameters
76
  for parameter in beparams:
77
    if parameter not in constants.BES_PARAMETERS:
78
      ToStderr("Invalid backend parameter: %s", parameter)
79
      return 1
80

    
81
  # prepare beparams dict
82
  for parameter in constants.BES_PARAMETERS:
83
    if parameter not in beparams:
84
      beparams[parameter] = constants.BEC_DEFAULTS[parameter]
85
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
86

    
87
  # prepare hvparams dict
88
  for hv in constants.HYPER_TYPES:
89
    if hv not in hvparams:
90
      hvparams[hv] = {}
91
    for parameter in constants.HVC_DEFAULTS[hv]:
92
      if parameter not in hvparams[hv]:
93
        hvparams[hv][parameter] = constants.HVC_DEFAULTS[hv][parameter]
94
    utils.ForceDictType(hvparams[hv], constants.HVS_PARAMETER_TYPES)
95

    
96
  bootstrap.InitCluster(cluster_name=args[0],
97
                        secondary_ip=opts.secondary_ip,
98
                        vg_name=vg_name,
99
                        mac_prefix=opts.mac_prefix,
100
                        def_bridge=opts.def_bridge,
101
                        master_netdev=opts.master_netdev,
102
                        file_storage_dir=opts.file_storage_dir,
103
                        enabled_hypervisors=hvlist,
104
                        default_hypervisor=opts.default_hypervisor,
105
                        hvparams=hvparams,
106
                        beparams=beparams,
107
                        candidate_pool_size=opts.candidate_pool_size,
108
                        modify_etc_hosts=opts.modify_etc_hosts,
109
                        )
110
  return 0
111

    
112

    
113
@UsesRPC
114
def DestroyCluster(opts, args):
115
  """Destroy the cluster.
116

    
117
  @param opts: the command line options selected by the user
118
  @type args: list
119
  @param args: should be an empty list
120
  @rtype: int
121
  @return: the desired exit code
122

    
123
  """
124
  if not opts.yes_do_it:
125
    ToStderr("Destroying a cluster is irreversible. If you really want"
126
             " destroy this cluster, supply the --yes-do-it option.")
127
    return 1
128

    
129
  op = opcodes.OpDestroyCluster()
130
  master = SubmitOpCode(op)
131
  # if we reached this, the opcode didn't fail; we can proceed to
132
  # shutdown all the daemons
133
  bootstrap.FinalizeClusterDestroy(master)
134
  return 0
135

    
136

    
137
def RenameCluster(opts, args):
138
  """Rename the cluster.
139

    
140
  @param opts: the command line options selected by the user
141
  @type args: list
142
  @param args: should contain only one element, the new cluster name
143
  @rtype: int
144
  @return: the desired exit code
145

    
146
  """
147
  name = args[0]
148
  if not opts.force:
149
    usertext = ("This will rename the cluster to '%s'. If you are connected"
150
                " over the network to the cluster name, the operation is very"
151
                " dangerous as the IP address will be removed from the node"
152
                " and the change may not go through. Continue?") % name
153
    if not AskUser(usertext):
154
      return 1
155

    
156
  op = opcodes.OpRenameCluster(name=name)
157
  SubmitOpCode(op)
158
  return 0
159

    
160

    
161
def RedistributeConfig(opts, args):
162
  """Forces push of the cluster configuration.
163

    
164
  @param opts: the command line options selected by the user
165
  @type args: list
166
  @param args: empty list
167
  @rtype: int
168
  @return: the desired exit code
169

    
170
  """
171
  op = opcodes.OpRedistributeConfig()
172
  SubmitOrSend(op, opts)
173
  return 0
174

    
175

    
176
def ShowClusterVersion(opts, args):
177
  """Write version of ganeti software to the standard output.
178

    
179
  @param opts: the command line options selected by the user
180
  @type args: list
181
  @param args: should be an empty list
182
  @rtype: int
183
  @return: the desired exit code
184

    
185
  """
186
  cl = GetClient()
187
  result = cl.QueryClusterInfo()
188
  ToStdout("Software version: %s", result["software_version"])
189
  ToStdout("Internode protocol: %s", result["protocol_version"])
190
  ToStdout("Configuration format: %s", result["config_version"])
191
  ToStdout("OS api version: %s", result["os_api_version"])
192
  ToStdout("Export interface: %s", result["export_version"])
193
  return 0
194

    
195

    
196
def ShowClusterMaster(opts, args):
197
  """Write name of master node to the standard output.
198

    
199
  @param opts: the command line options selected by the user
200
  @type args: list
201
  @param args: should be an empty list
202
  @rtype: int
203
  @return: the desired exit code
204

    
205
  """
206
  master = bootstrap.GetMaster()
207
  ToStdout(master)
208
  return 0
209

    
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("Master node: %s", result["master"])
227

    
228
  ToStdout("Architecture (this node): %s (%s)",
229
           result["architecture"][0], result["architecture"][1])
230

    
231
  if result["tags"]:
232
    tags = ", ".join(utils.NiceSort(result["tags"]))
233
  else:
234
    tags = "(none)"
235

    
236
  ToStdout("Tags: %s", tags)
237

    
238
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
239
  ToStdout("Enabled hypervisors: %s", ", ".join(result["enabled_hypervisors"]))
240

    
241
  ToStdout("Hypervisor parameters:")
242
  for hv_name, hv_dict in result["hvparams"].items():
243
    ToStdout("  - %s:", hv_name)
244
    for item, val in hv_dict.iteritems():
245
      ToStdout("      %s: %s", item, val)
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("  - default bridge: %s", result["default_bridge"])
251
  ToStdout("  - lvm volume group: %s", result["volume_group_name"])
252
  ToStdout("  - file storage path: %s", result["file_storage_dir"])
253

    
254
  ToStdout("Default instance parameters:")
255
  for gr_name, gr_dict in result["beparams"].items():
256
    ToStdout("  - %s:", gr_name)
257
    for item, val in gr_dict.iteritems():
258
      ToStdout("      %s: %s", item, val)
259

    
260
  return 0
261

    
262

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

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

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

    
278
  cl = GetClient()
279

    
280
  myname = utils.HostInfo().name
281

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

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

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

    
292
  return 0
293

    
294

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

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

    
304
  """
305
  cl = GetClient()
306

    
307
  command = " ".join(args)
308

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

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

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

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

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

    
328
  return 0
329

    
330

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

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

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

    
350

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

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

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

    
366
  nodes, nlvm, instances, missing = result
367

    
368
  if nodes:
369
    ToStdout("Nodes unreachable or with bad data:")
370
    for name in nodes:
371
      ToStdout("\t%s", name)
372
  retcode = constants.EXIT_SUCCESS
373

    
374
  if nlvm:
375
    for node, text in nlvm.iteritems():
376
      ToStdout("Error on node %s: LVM error: %s",
377
               node, utils.SafeEncode(text[-400:]))
378
      retcode |= 1
379
      ToStdout("You need to fix these nodes first before fixing instances")
380

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

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

    
412
  return retcode
413

    
414

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

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

    
424
  """
425
  op = opcodes.OpRepairDiskSizes(instances=args)
426
  SubmitOpCode(op)
427

    
428

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

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

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

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

    
452
  return bootstrap.MasterFailover(no_voting=opts.no_voting)
453

    
454

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

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

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

    
474

    
475
def SetClusterParams(opts, args):
476
  """Modify the cluster.
477

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

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

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

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

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

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

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

    
518

    
519
def QueueOps(opts, args):
520
  """Queue operations.
521

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

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

    
544
  return 0
545

    
546
# this is an option common to more than one command, so we declare
547
# it here and reuse it
548
node_option = make_option("-n", "--node", action="append", dest="nodes",
549
                          help="Node to copy to (if not given, all nodes),"
550
                               " can be given multiple times",
551
                          metavar="<node>", default=[])
552

    
553
commands = {
554
  'init': (InitCluster, ARGS_ONE,
555
           [DEBUG_OPT,
556
            make_option("-s", "--secondary-ip", dest="secondary_ip",
557
                        help="Specify the secondary ip for this node;"
558
                        " if given, the entire cluster must have secondary"
559
                        " addresses",
560
                        metavar="ADDRESS", default=None),
561
            make_option("-m", "--mac-prefix", dest="mac_prefix",
562
                        help="Specify the mac prefix for the instance IP"
563
                        " addresses, in the format XX:XX:XX",
564
                        metavar="PREFIX",
565
                        default=constants.DEFAULT_MAC_PREFIX,),
566
            make_option("-g", "--vg-name", dest="vg_name",
567
                        help="Specify the volume group name "
568
                        " (cluster-wide) for disk allocation [xenvg]",
569
                        metavar="VG",
570
                        default=None,),
571
            make_option("-b", "--bridge", dest="def_bridge",
572
                        help="Specify the default bridge name (cluster-wide)"
573
                          " to connect the instances to [%s]" %
574
                          constants.DEFAULT_BRIDGE,
575
                        metavar="BRIDGE",
576
                        default=constants.DEFAULT_BRIDGE,),
577
            make_option("--master-netdev", dest="master_netdev",
578
                        help="Specify the node interface (cluster-wide)"
579
                          " on which the master IP address will be added "
580
                          " [%s]" % constants.DEFAULT_BRIDGE,
581
                        metavar="NETDEV",
582
                        default=constants.DEFAULT_BRIDGE,),
583
            make_option("--file-storage-dir", dest="file_storage_dir",
584
                        help="Specify the default directory (cluster-wide)"
585
                             " for storing the file-based disks [%s]" %
586
                             constants.DEFAULT_FILE_STORAGE_DIR,
587
                        metavar="DIR",
588
                        default=constants.DEFAULT_FILE_STORAGE_DIR,),
589
            make_option("--no-lvm-storage", dest="lvm_storage",
590
                        help="No support for lvm based instances"
591
                             " (cluster-wide)",
592
                        action="store_false", default=True,),
593
            make_option("--no-etc-hosts", dest="modify_etc_hosts",
594
                        help="Don't modify /etc/hosts"
595
                             " (cluster-wide)",
596
                        action="store_false", default=True,),
597
            make_option("--enabled-hypervisors", dest="enabled_hypervisors",
598
                        help="Comma-separated list of hypervisors",
599
                        type="string", default=None),
600
            make_option("-t", "--default-hypervisor",
601
                        dest="default_hypervisor",
602
                        help="Default hypervisor to use for instance creation",
603
                        choices=list(constants.HYPER_TYPES),
604
                        default=constants.DEFAULT_ENABLED_HYPERVISOR),
605
            ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
606
                       help="Hypervisor and hypervisor options, in the"
607
                         " format"
608
                       " hypervisor:option=value,option=value,...",
609
                       default=[],
610
                       action="append",
611
                       type="identkeyval"),
612
            keyval_option("-B", "--backend-parameters", dest="beparams",
613
                          type="keyval", default={},
614
                          help="Backend parameters"),
615
            make_option("-C", "--candidate-pool-size",
616
                        default=constants.MASTER_POOL_SIZE_DEFAULT,
617
                        help="Set the candidate pool size",
618
                        dest="candidate_pool_size", type="int"),
619
            ],
620
           "[opts...] <cluster_name>",
621
           "Initialises a new cluster configuration"),
622
  'destroy': (DestroyCluster, ARGS_NONE,
623
              [DEBUG_OPT,
624
               make_option("--yes-do-it", dest="yes_do_it",
625
                           help="Destroy cluster",
626
                           action="store_true"),
627
              ],
628
              "", "Destroy cluster"),
629
  'rename': (RenameCluster, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
630
               "<new_name>",
631
               "Renames the cluster"),
632
  'redist-conf': (RedistributeConfig, ARGS_NONE, [DEBUG_OPT, SUBMIT_OPT],
633
                  "",
634
                  "Forces a push of the configuration file and ssconf files"
635
                  " to the nodes in the cluster"),
636
  'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT,
637
             make_option("--no-nplus1-mem", dest="skip_nplusone_mem",
638
                         help="Skip N+1 memory redundancy tests",
639
                         action="store_true",
640
                         default=False,),
641
             ],
642
             "", "Does a check on the cluster configuration"),
643
  'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
644
                   "", "Does a check on the cluster disk status"),
645
  'repair-disk-sizes': (RepairDiskSizes, ARGS_ANY, [DEBUG_OPT],
646
                   "", "Updates mismatches in recorded disk sizes"),
647
  'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT,
648
                     make_option("--no-voting", dest="no_voting",
649
                                 help="Skip node agreement check (dangerous)",
650
                                 action="store_true",
651
                                 default=False,),
652
                     ],
653
                     "", "Makes the current node the master"),
654
  'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
655
              "", "Shows the cluster version"),
656
  'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
657
                "", "Shows the cluster master"),
658
  'copyfile': (ClusterCopyFile, ARGS_ONE, [DEBUG_OPT, node_option],
659
               "[-n node...] <filename>",
660
               "Copies a file to all (or only some) nodes"),
661
  'command': (RunClusterCommand, ARGS_ATLEAST(1), [DEBUG_OPT, node_option],
662
              "[-n node...] <command>",
663
              "Runs a command on all (or only some) nodes"),
664
  'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
665
                 "", "Show cluster configuration"),
666
  'list-tags': (ListTags, ARGS_NONE,
667
                [DEBUG_OPT], "", "List the tags of the cluster"),
668
  'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
669
               "tag...", "Add tags to the cluster"),
670
  'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
671
                  "tag...", "Remove tags from the cluster"),
672
  'search-tags': (SearchTags, ARGS_ONE,
673
                  [DEBUG_OPT], "", "Searches the tags on all objects on"
674
                  " the cluster for a given pattern (regex)"),
675
  'queue': (QueueOps, ARGS_ONE, [DEBUG_OPT],
676
            "drain|undrain|info", "Change queue properties"),
677
  'modify': (SetClusterParams, ARGS_NONE,
678
             [DEBUG_OPT,
679
              make_option("-g", "--vg-name", dest="vg_name",
680
                          help="Specify the volume group name "
681
                          " (cluster-wide) for disk allocation "
682
                          "and enable lvm based storage",
683
                          metavar="VG",),
684
              make_option("--no-lvm-storage", dest="lvm_storage",
685
                          help="Disable support for lvm based instances"
686
                               " (cluster-wide)",
687
                          action="store_false", default=True,),
688
              make_option("--enabled-hypervisors", dest="enabled_hypervisors",
689
                          help="Comma-separated list of hypervisors",
690
                          type="string", default=None),
691
              ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
692
                         help="Hypervisor and hypervisor options, in the"
693
                         " format"
694
                         " hypervisor:option=value,option=value,...",
695
                         default=[],
696
                         action="append",
697
                         type="identkeyval"),
698
              keyval_option("-B", "--backend-parameters", dest="beparams",
699
                            type="keyval", default={},
700
                            help="Backend parameters"),
701
              make_option("-C", "--candidate-pool-size", default=None,
702
                          help="Set the candidate pool size",
703
                          dest="candidate_pool_size", type="int"),
704
              ],
705
             "[opts...]",
706
             "Alters the parameters of the cluster"),
707
  }
708

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