Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ d3cfe525

History | View | Annotate | Download (23 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
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 not None:
62
    hvlist = hvlist.split(",")
63
  else:
64
    hvlist = [opts.default_hypervisor]
65

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

    
73
  hvparams = dict(opts.hvparams)
74
  beparams = opts.beparams
75

    
76
  # prepare beparams dict
77
  beparams = objects.FillDict(constants.BEC_DEFAULTS, beparams)
78
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
79

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

    
87
  for hv in hvlist:
88
    if hv not in constants.HYPER_TYPES:
89
      ToStderr("invalid hypervisor: %s", hv)
90
      return 1
91

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

    
108

    
109
@UsesRPC
110
def DestroyCluster(opts, args):
111
  """Destroy the cluster.
112

    
113
  @param opts: the command line options selected by the user
114
  @type args: list
115
  @param args: should be an empty list
116
  @rtype: int
117
  @return: the desired exit code
118

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

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

    
132

    
133
def RenameCluster(opts, args):
134
  """Rename the cluster.
135

    
136
  @param opts: the command line options selected by the user
137
  @type args: list
138
  @param args: should contain only one element, the new cluster name
139
  @rtype: int
140
  @return: the desired exit code
141

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

    
152
  op = opcodes.OpRenameCluster(name=name)
153
  SubmitOpCode(op)
154
  return 0
155

    
156

    
157
def RedistributeConfig(opts, args):
158
  """Forces push of the cluster configuration.
159

    
160
  @param opts: the command line options selected by the user
161
  @type args: list
162
  @param args: empty list
163
  @rtype: int
164
  @return: the desired exit code
165

    
166
  """
167
  op = opcodes.OpRedistributeConfig()
168
  SubmitOrSend(op, opts)
169
  return 0
170

    
171

    
172
def ShowClusterVersion(opts, args):
173
  """Write version of ganeti software to the standard output.
174

    
175
  @param opts: the command line options selected by the user
176
  @type args: list
177
  @param args: should be an empty list
178
  @rtype: int
179
  @return: the desired exit code
180

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

    
191

    
192
def ShowClusterMaster(opts, args):
193
  """Write name of master node to the standard output.
194

    
195
  @param opts: the command line options selected by the user
196
  @type args: list
197
  @param args: should be an empty list
198
  @rtype: int
199
  @return: the desired exit code
200

    
201
  """
202
  master = bootstrap.GetMaster()
203
  ToStdout(master)
204
  return 0
205

    
206

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

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

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

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

    
222
  ToStdout("Master node: %s", result["master"])
223

    
224
  ToStdout("Architecture (this node): %s (%s)",
225
           result["architecture"][0], result["architecture"][1])
226

    
227
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
228
  ToStdout("Enabled hypervisors: %s", ", ".join(result["enabled_hypervisors"]))
229

    
230
  ToStdout("Hypervisor parameters:")
231
  for hv_name, hv_dict in result["hvparams"].items():
232
    ToStdout("  - %s:", hv_name)
233
    for item, val in hv_dict.iteritems():
234
      ToStdout("      %s: %s", item, val)
235

    
236
  ToStdout("Cluster parameters:")
237
  ToStdout("  - candidate pool size: %s", result["candidate_pool_size"])
238
  ToStdout("  - master netdev: %s", result["master_netdev"])
239
  ToStdout("  - default bridge: %s", result["default_bridge"])
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
  for gr_name, gr_dict in result["beparams"].items():
245
    ToStdout("  - %s:", gr_name)
246
    for item, val in gr_dict.iteritems():
247
      ToStdout("      %s: %s", item, val)
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) != 4:
353
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
354

    
355
  nodes, nlvm, instances, missing = result
356

    
357
  if nodes:
358
    ToStdout("Nodes unreachable or with bad data:")
359
    for name in nodes:
360
      ToStdout("\t%s", name)
361
  retcode = constants.EXIT_SUCCESS
362

    
363
  if nlvm:
364
    for node, text in nlvm.iteritems():
365
      ToStdout("Error on node %s: LVM error: %s",
366
               node, utils.SafeEncode(text[-400:]))
367
      retcode |= 1
368
      ToStdout("You need to fix these nodes first before fixing instances")
369

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

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

    
401
  return retcode
402

    
403

    
404
@UsesRPC
405
def MasterFailover(opts, args):
406
  """Failover the master node.
407

    
408
  This command, when run on a non-master node, will cause the current
409
  master to cease being master, and the non-master to become new
410
  master.
411

    
412
  @param opts: the command line options selected by the user
413
  @type args: list
414
  @param args: should be an empty list
415
  @rtype: int
416
  @return: the desired exit code
417

    
418
  """
419
  return bootstrap.MasterFailover()
420

    
421

    
422
def SearchTags(opts, args):
423
  """Searches the tags on all the cluster.
424

    
425
  @param opts: the command line options selected by the user
426
  @type args: list
427
  @param args: should contain only one element, the tag pattern
428
  @rtype: int
429
  @return: the desired exit code
430

    
431
  """
432
  op = opcodes.OpSearchTags(pattern=args[0])
433
  result = SubmitOpCode(op)
434
  if not result:
435
    return 1
436
  result = list(result)
437
  result.sort()
438
  for path, tag in result:
439
    ToStdout("%s %s", path, tag)
440

    
441

    
442
def SetClusterParams(opts, args):
443
  """Modify the cluster.
444

    
445
  @param opts: the command line options selected by the user
446
  @type args: list
447
  @param args: should be an empty list
448
  @rtype: int
449
  @return: the desired exit code
450

    
451
  """
452
  if not (not opts.lvm_storage or opts.vg_name or
453
          opts.enabled_hypervisors or opts.hvparams or
454
          opts.beparams or opts.candidate_pool_size is not None):
455
    ToStderr("Please give at least one of the parameters.")
456
    return 1
457

    
458
  vg_name = opts.vg_name
459
  if not opts.lvm_storage and opts.vg_name:
460
    ToStdout("Options --no-lvm-storage and --vg-name conflict.")
461
    return 1
462
  elif not opts.lvm_storage:
463
    vg_name = ''
464

    
465
  hvlist = opts.enabled_hypervisors
466
  if hvlist is not None:
467
    hvlist = hvlist.split(",")
468

    
469
  # a list of (name, dict) we can pass directly to dict() (or [])
470
  hvparams = dict(opts.hvparams)
471
  for hv, hv_params in hvparams.iteritems():
472
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
473

    
474
  beparams = opts.beparams
475
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
476

    
477
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
478
                                  enabled_hypervisors=hvlist,
479
                                  hvparams=hvparams,
480
                                  beparams=beparams,
481
                                  candidate_pool_size=opts.candidate_pool_size)
482
  SubmitOpCode(op)
483
  return 0
484

    
485

    
486
def QueueOps(opts, args):
487
  """Queue operations.
488

    
489
  @param opts: the command line options selected by the user
490
  @type args: list
491
  @param args: should contain only one element, the subcommand
492
  @rtype: int
493
  @return: the desired exit code
494

    
495
  """
496
  command = args[0]
497
  client = GetClient()
498
  if command in ("drain", "undrain"):
499
    drain_flag = command == "drain"
500
    client.SetQueueDrainFlag(drain_flag)
501
  elif command == "info":
502
    result = client.QueryConfigValues(["drain_flag"])
503
    if result[0]:
504
      val = "set"
505
    else:
506
      val = "unset"
507
    ToStdout("The drain flag is %s" % val)
508
  else:
509
    raise errors.OpPrereqError("Command '%s' is not valid." % command)
510

    
511
  return 0
512

    
513
# this is an option common to more than one command, so we declare
514
# it here and reuse it
515
node_option = make_option("-n", "--node", action="append", dest="nodes",
516
                          help="Node to copy to (if not given, all nodes),"
517
                               " can be given multiple times",
518
                          metavar="<node>", default=[])
519

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

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