Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ 771734c9

History | View | Annotate | Download (24.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
  hvlist = hvlist.split(",")
62

    
63
  hvparams = dict(opts.hvparams)
64
  beparams = opts.beparams
65
  nicparams = opts.nicparams
66

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

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

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

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

    
99

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

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

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

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

    
123

    
124
def RenameCluster(opts, args):
125
  """Rename the cluster.
126

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

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

    
143
  op = opcodes.OpRenameCluster(name=name)
144
  SubmitOpCode(op)
145
  return 0
146

    
147

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

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

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

    
162

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

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

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

    
182

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

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

    
192
  """
193
  master = bootstrap.GetMaster()
194
  ToStdout(master)
195
  return 0
196

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

    
200
  @type paramsdict: dict of dicts
201
  @param paramsdict: {group: {param: value, ...}, ...}
202

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

    
209
def ShowClusterConfig(opts, args):
210
  """Shows cluster information.
211

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

    
218
  """
219
  cl = GetClient()
220
  result = cl.QueryClusterInfo()
221

    
222
  ToStdout("Cluster name: %s", result["name"])
223

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

    
227
  ToStdout("Master node: %s", result["master"])
228

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

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

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

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

    
242
  ToStdout("Hypervisor parameters:")
243
  _PrintGroupedParams(result["hvparams"])
244

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

    
251
  ToStdout("Default instance parameters:")
252
  _PrintGroupedParams(result["beparams"])
253

    
254
  ToStdout("Default nic parameters:")
255
  _PrintGroupedParams(result["nicparams"])
256

    
257
  return 0
258

    
259

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

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

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

    
275
  cl = GetClient()
276

    
277
  myname = utils.HostInfo().name
278

    
279
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
280

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

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

    
289
  return 0
290

    
291

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

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

    
301
  """
302
  cl = GetClient()
303

    
304
  command = " ".join(args)
305

    
306
  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
307

    
308
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
309
                                                    "master_node"])
310

    
311
  srun = ssh.SshRunner(cluster_name=cluster_name)
312

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

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

    
325
  return 0
326

    
327

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

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

    
337
  """
338
  skip_checks = []
339
  if opts.skip_nplusone_mem:
340
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
341
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
342
                               verbose=opts.verbose,
343
                               error_codes=opts.error_codes,
344
                               debug_simulate_errors=opts.simulate_errors)
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) != 3:
364
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
365

    
366
  bad_nodes, instances, missing = result
367

    
368
  retcode = constants.EXIT_SUCCESS
369

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

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

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

    
408
  return retcode
409

    
410

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

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

    
420
  """
421
  op = opcodes.OpRepairDiskSizes(instances=args)
422
  SubmitOpCode(op)
423

    
424

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

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

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

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

    
448
  return bootstrap.MasterFailover(no_voting=opts.no_voting)
449

    
450

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

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

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

    
470

    
471
def SetClusterParams(opts, args):
472
  """Modify the cluster.
473

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

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

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

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

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

    
504
  beparams = opts.beparams
505
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
506

    
507
  nicparams = opts.nicparams
508
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
509

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

    
519

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

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

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

    
545
  return 0
546

    
547

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

    
554

    
555
def WatcherOps(opts, args):
556
  """Watcher operations.
557

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

    
564
  """
565
  command = args[0]
566
  client = GetClient()
567

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

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

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

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

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

    
586
  return 0
587

    
588

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

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