Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ 83ec7961

History | View | Annotate | Download (23.9 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

    
29
from ganeti.cli import *
30
from ganeti import opcodes
31
from ganeti import constants
32
from ganeti import errors
33
from ganeti import utils
34
from ganeti import bootstrap
35
from ganeti import ssh
36
from ganeti import objects
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
  hvlist = hvlist.split(",")
61

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

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

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

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

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

    
98

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

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

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

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

    
122

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

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

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

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

    
146

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

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

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

    
161

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

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

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

    
181

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

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

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

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

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

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

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

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

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

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

    
223
  ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
224
  ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))
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
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
232
  ToStdout("Enabled hypervisors: %s", ", ".join(result["enabled_hypervisors"]))
233

    
234
  ToStdout("Hypervisor parameters:")
235
  _PrintGroupedParams(result["hvparams"])
236

    
237
  ToStdout("Cluster parameters:")
238
  ToStdout("  - candidate pool size: %s", result["candidate_pool_size"])
239
  ToStdout("  - master netdev: %s", result["master_netdev"])
240
  ToStdout("  - lvm volume group: %s", result["volume_group_name"])
241
  ToStdout("  - file storage path: %s", result["file_storage_dir"])
242

    
243
  ToStdout("Default instance parameters:")
244
  _PrintGroupedParams(result["beparams"])
245

    
246
  ToStdout("Default nic parameters:")
247
  _PrintGroupedParams(result["nicparams"])
248

    
249
  return 0
250

    
251

    
252
def ClusterCopyFile(opts, args):
253
  """Copy a file from master to some nodes.
254

    
255
  @param opts: the command line options selected by the user
256
  @type args: list
257
  @param args: should contain only one element, the path of
258
      the file to be copied
259
  @rtype: int
260
  @return: the desired exit code
261

    
262
  """
263
  filename = args[0]
264
  if not os.path.exists(filename):
265
    raise errors.OpPrereqError("No such filename '%s'" % filename)
266

    
267
  cl = GetClient()
268

    
269
  myname = utils.HostInfo().name
270

    
271
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
272

    
273
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl)
274
  results = [name for name in results if name != myname]
275

    
276
  srun = ssh.SshRunner(cluster_name=cluster_name)
277
  for node in results:
278
    if not srun.CopyFileToNode(node, filename):
279
      ToStderr("Copy of file %s to node %s failed", filename, node)
280

    
281
  return 0
282

    
283

    
284
def RunClusterCommand(opts, args):
285
  """Run a command on some nodes.
286

    
287
  @param opts: the command line options selected by the user
288
  @type args: list
289
  @param args: should contain the command to be run and its arguments
290
  @rtype: int
291
  @return: the desired exit code
292

    
293
  """
294
  cl = GetClient()
295

    
296
  command = " ".join(args)
297

    
298
  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
299

    
300
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
301
                                                    "master_node"])
302

    
303
  srun = ssh.SshRunner(cluster_name=cluster_name)
304

    
305
  # Make sure master node is at list end
306
  if master_node in nodes:
307
    nodes.remove(master_node)
308
    nodes.append(master_node)
309

    
310
  for name in nodes:
311
    result = srun.Run(name, "root", command)
312
    ToStdout("------------------------------------------------")
313
    ToStdout("node: %s", name)
314
    ToStdout("%s", result.output)
315
    ToStdout("return code = %s", result.exit_code)
316

    
317
  return 0
318

    
319

    
320
def VerifyCluster(opts, args):
321
  """Verify integrity of cluster, performing various test on nodes.
322

    
323
  @param opts: the command line options selected by the user
324
  @type args: list
325
  @param args: should be an empty list
326
  @rtype: int
327
  @return: the desired exit code
328

    
329
  """
330
  skip_checks = []
331
  if opts.skip_nplusone_mem:
332
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
333
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
334
  if SubmitOpCode(op):
335
    return 0
336
  else:
337
    return 1
338

    
339

    
340
def VerifyDisks(opts, args):
341
  """Verify integrity of cluster disks.
342

    
343
  @param opts: the command line options selected by the user
344
  @type args: list
345
  @param args: should be an empty list
346
  @rtype: int
347
  @return: the desired exit code
348

    
349
  """
350
  op = opcodes.OpVerifyDisks()
351
  result = SubmitOpCode(op)
352
  if not isinstance(result, (list, tuple)) or len(result) != 3:
353
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
354

    
355
  bad_nodes, instances, missing = result
356

    
357
  retcode = constants.EXIT_SUCCESS
358

    
359
  if bad_nodes:
360
    for node, text in bad_nodes.items():
361
      ToStdout("Error gathering data on node %s: %s",
362
               node, utils.SafeEncode(text[-400:]))
363
      retcode |= 1
364
      ToStdout("You need to fix these nodes first before fixing instances")
365

    
366
  if instances:
367
    for iname in instances:
368
      if iname in missing:
369
        continue
370
      op = opcodes.OpActivateInstanceDisks(instance_name=iname)
371
      try:
372
        ToStdout("Activating disks for instance '%s'", iname)
373
        SubmitOpCode(op)
374
      except errors.GenericError, err:
375
        nret, msg = FormatError(err)
376
        retcode |= nret
377
        ToStderr("Error activating disks for instance %s: %s", iname, msg)
378

    
379
  if missing:
380
    for iname, ival in missing.iteritems():
381
      all_missing = utils.all(ival, lambda x: x[0] in bad_nodes)
382
      if all_missing:
383
        ToStdout("Instance %s cannot be verified as it lives on"
384
                 " broken nodes", iname)
385
      else:
386
        ToStdout("Instance %s has missing logical volumes:", iname)
387
        ival.sort()
388
        for node, vol in ival:
389
          if node in bad_nodes:
390
            ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
391
          else:
392
            ToStdout("\t%s /dev/xenvg/%s", node, vol)
393
    ToStdout("You need to run replace_disks for all the above"
394
           " instances, if this message persist after fixing nodes.")
395
    retcode |= 1
396

    
397
  return retcode
398

    
399

    
400
def RepairDiskSizes(opts, args):
401
  """Verify sizes of cluster disks.
402

    
403
  @param opts: the command line options selected by the user
404
  @type args: list
405
  @param args: optional list of instances to restrict check to
406
  @rtype: int
407
  @return: the desired exit code
408

    
409
  """
410
  op = opcodes.OpRepairDiskSizes(instances=args)
411
  SubmitOpCode(op)
412

    
413

    
414
@UsesRPC
415
def MasterFailover(opts, args):
416
  """Failover the master node.
417

    
418
  This command, when run on a non-master node, will cause the current
419
  master to cease being master, and the non-master to become new
420
  master.
421

    
422
  @param opts: the command line options selected by the user
423
  @type args: list
424
  @param args: should be an empty list
425
  @rtype: int
426
  @return: the desired exit code
427

    
428
  """
429
  if opts.no_voting:
430
    usertext = ("This will perform the failover even if most other nodes"
431
                " are down, or if this node is outdated. This is dangerous"
432
                " as it can lead to a non-consistent cluster. Check the"
433
                " gnt-cluster(8) man page before proceeding. Continue?")
434
    if not AskUser(usertext):
435
      return 1
436

    
437
  return bootstrap.MasterFailover(no_voting=opts.no_voting)
438

    
439

    
440
def SearchTags(opts, args):
441
  """Searches the tags on all the cluster.
442

    
443
  @param opts: the command line options selected by the user
444
  @type args: list
445
  @param args: should contain only one element, the tag pattern
446
  @rtype: int
447
  @return: the desired exit code
448

    
449
  """
450
  op = opcodes.OpSearchTags(pattern=args[0])
451
  result = SubmitOpCode(op)
452
  if not result:
453
    return 1
454
  result = list(result)
455
  result.sort()
456
  for path, tag in result:
457
    ToStdout("%s %s", path, tag)
458

    
459

    
460
def SetClusterParams(opts, args):
461
  """Modify the cluster.
462

    
463
  @param opts: the command line options selected by the user
464
  @type args: list
465
  @param args: should be an empty list
466
  @rtype: int
467
  @return: the desired exit code
468

    
469
  """
470
  if not (not opts.lvm_storage or opts.vg_name or
471
          opts.enabled_hypervisors or opts.hvparams or
472
          opts.beparams or opts.nicparams or
473
          opts.candidate_pool_size is not None):
474
    ToStderr("Please give at least one of the parameters.")
475
    return 1
476

    
477
  vg_name = opts.vg_name
478
  if not opts.lvm_storage and opts.vg_name:
479
    ToStdout("Options --no-lvm-storage and --vg-name conflict.")
480
    return 1
481
  elif not opts.lvm_storage:
482
    vg_name = ''
483

    
484
  hvlist = opts.enabled_hypervisors
485
  if hvlist is not None:
486
    hvlist = hvlist.split(",")
487

    
488
  # a list of (name, dict) we can pass directly to dict() (or [])
489
  hvparams = dict(opts.hvparams)
490
  for hv, hv_params in hvparams.iteritems():
491
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
492

    
493
  beparams = opts.beparams
494
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
495

    
496
  nicparams = opts.nicparams
497
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
498

    
499
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
500
                                  enabled_hypervisors=hvlist,
501
                                  hvparams=hvparams,
502
                                  beparams=beparams,
503
                                  nicparams=nicparams,
504
                                  candidate_pool_size=opts.candidate_pool_size)
505
  SubmitOpCode(op)
506
  return 0
507

    
508

    
509
def QueueOps(opts, args):
510
  """Queue operations.
511

    
512
  @param opts: the command line options selected by the user
513
  @type args: list
514
  @param args: should contain only one element, the subcommand
515
  @rtype: int
516
  @return: the desired exit code
517

    
518
  """
519
  command = args[0]
520
  client = GetClient()
521
  if command in ("drain", "undrain"):
522
    drain_flag = command == "drain"
523
    client.SetQueueDrainFlag(drain_flag)
524
  elif command == "info":
525
    result = client.QueryConfigValues(["drain_flag"])
526
    if result[0]:
527
      val = "set"
528
    else:
529
      val = "unset"
530
    ToStdout("The drain flag is %s" % val)
531
  else:
532
    raise errors.OpPrereqError("Command '%s' is not valid." % command)
533

    
534
  return 0
535

    
536
# this is an option common to more than one command, so we declare
537
# it here and reuse it
538
node_option = cli_option("-n", "--node", action="append", dest="nodes",
539
                         help="Node to copy to (if not given, all nodes),"
540
                              " can be given multiple times",
541
                         metavar="<node>", default=[])
542

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

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