Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ a5728081

History | View | Annotate | Download (22.8 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 = opts.hvparams
73
  if hvparams:
74
    # a list of (name, dict) we can pass directly to dict()
75
    hvparams = dict(opts.hvparams)
76
  else:
77
    # otherwise init as empty dict
78
    hvparams = {}
79

    
80
  beparams = opts.beparams
81
  # check for invalid parameters
82
  for parameter in beparams:
83
    if parameter not in constants.BES_PARAMETERS:
84
      ToStderr("Invalid backend parameter: %s", parameter)
85
      return 1
86

    
87
  # prepare beparams dict
88
  for parameter in constants.BES_PARAMETERS:
89
    if parameter not in beparams:
90
      beparams[parameter] = constants.BEC_DEFAULTS[parameter]
91
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
92

    
93
  # prepare hvparams dict
94
  for hv in constants.HYPER_TYPES:
95
    if hv not in hvparams:
96
      hvparams[hv] = {}
97
    for parameter in constants.HVC_DEFAULTS[hv]:
98
      if parameter not in hvparams[hv]:
99
        hvparams[hv][parameter] = constants.HVC_DEFAULTS[hv][parameter]
100
    utils.ForceDictType(hvparams[hv], constants.HVS_PARAMETER_TYPES)
101

    
102
  for hv in hvlist:
103
    if hv not in constants.HYPER_TYPES:
104
      ToStderr("invalid hypervisor: %s", hv)
105
      return 1
106

    
107
  bootstrap.InitCluster(cluster_name=args[0],
108
                        secondary_ip=opts.secondary_ip,
109
                        vg_name=vg_name,
110
                        mac_prefix=opts.mac_prefix,
111
                        def_bridge=opts.def_bridge,
112
                        master_netdev=opts.master_netdev,
113
                        file_storage_dir=opts.file_storage_dir,
114
                        enabled_hypervisors=hvlist,
115
                        default_hypervisor=opts.default_hypervisor,
116
                        hvparams=hvparams,
117
                        beparams=beparams,
118
                        candidate_pool_size=opts.candidate_pool_size,
119
                        )
120
  return 0
121

    
122

    
123
@UsesRPC
124
def DestroyCluster(opts, args):
125
  """Destroy the cluster.
126

    
127
  @param opts: the command line options selected by the user
128
  @type args: list
129
  @param args: should be an empty list
130
  @rtype: int
131
  @return: the desired exit code
132

    
133
  """
134
  if not opts.yes_do_it:
135
    ToStderr("Destroying a cluster is irreversible. If you really want"
136
             " destroy this cluster, supply the --yes-do-it option.")
137
    return 1
138

    
139
  op = opcodes.OpDestroyCluster()
140
  master = SubmitOpCode(op)
141
  # if we reached this, the opcode didn't fail; we can proceed to
142
  # shutdown all the daemons
143
  bootstrap.FinalizeClusterDestroy(master)
144
  return 0
145

    
146

    
147
def RenameCluster(opts, args):
148
  """Rename the cluster.
149

    
150
  @param opts: the command line options selected by the user
151
  @type args: list
152
  @param args: should contain only one element, the new cluster name
153
  @rtype: int
154
  @return: the desired exit code
155

    
156
  """
157
  name = args[0]
158
  if not opts.force:
159
    usertext = ("This will rename the cluster to '%s'. If you are connected"
160
                " over the network to the cluster name, the operation is very"
161
                " dangerous as the IP address will be removed from the node"
162
                " and the change may not go through. Continue?") % name
163
    if not AskUser(usertext):
164
      return 1
165

    
166
  op = opcodes.OpRenameCluster(name=name)
167
  SubmitOpCode(op)
168
  return 0
169

    
170

    
171
def RedistributeConfig(opts, args):
172
  """Forces push of the cluster configuration.
173

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

    
180
  """
181
  op = opcodes.OpRedistributeConfig()
182
  SubmitOrSend(op, opts)
183
  return 0
184

    
185

    
186
def ShowClusterVersion(opts, args):
187
  """Write version of ganeti software to the standard output.
188

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

    
195
  """
196
  cl = GetClient()
197
  result = cl.QueryClusterInfo()
198
  ToStdout("Software version: %s", result["software_version"])
199
  ToStdout("Internode protocol: %s", result["protocol_version"])
200
  ToStdout("Configuration format: %s", result["config_version"])
201
  ToStdout("OS api version: %s", result["os_api_version"])
202
  ToStdout("Export interface: %s", result["export_version"])
203
  return 0
204

    
205

    
206
def ShowClusterMaster(opts, args):
207
  """Write name of master node to the standard output.
208

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

    
215
  """
216
  ToStdout("%s", GetClient().QueryConfigValues(["master_node"])[0])
217
  return 0
218

    
219

    
220
def ShowClusterConfig(opts, args):
221
  """Shows cluster information.
222

    
223
  @param opts: the command line options selected by the user
224
  @type args: list
225
  @param args: should be an empty list
226
  @rtype: int
227
  @return: the desired exit code
228

    
229
  """
230
  cl = GetClient()
231
  result = cl.QueryClusterInfo()
232

    
233
  ToStdout("Cluster name: %s", result["name"])
234

    
235
  ToStdout("Master node: %s", result["master"])
236

    
237
  ToStdout("Architecture (this node): %s (%s)",
238
           result["architecture"][0], result["architecture"][1])
239

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

    
243
  ToStdout("Hypervisor parameters:")
244
  for hv_name, hv_dict in result["hvparams"].items():
245
    ToStdout("  - %s:", hv_name)
246
    for item, val in hv_dict.iteritems():
247
      ToStdout("      %s: %s", item, val)
248

    
249
  ToStdout("Cluster parameters:")
250
  ToStdout("  - candidate pool size: %s", result["candidate_pool_size"])
251

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

    
258
  return 0
259

    
260

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

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

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

    
276
  cl = GetClient()
277

    
278
  myname = utils.HostInfo().name
279

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

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

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

    
290
  return 0
291

    
292

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

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

    
302
  """
303
  cl = GetClient()
304

    
305
  command = " ".join(args)
306

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

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

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

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

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

    
326
  return 0
327

    
328

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

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

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

    
348

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

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

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

    
364
  nodes, nlvm, instances, missing = result
365

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

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

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

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

    
410
  return retcode
411

    
412

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

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

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

    
427
  """
428
  return bootstrap.MasterFailover()
429

    
430

    
431
def SearchTags(opts, args):
432
  """Searches the tags on all the cluster.
433

    
434
  @param opts: the command line options selected by the user
435
  @type args: list
436
  @param args: should contain only one element, the tag pattern
437
  @rtype: int
438
  @return: the desired exit code
439

    
440
  """
441
  op = opcodes.OpSearchTags(pattern=args[0])
442
  result = SubmitOpCode(op)
443
  if not result:
444
    return 1
445
  result = list(result)
446
  result.sort()
447
  for path, tag in result:
448
    ToStdout("%s %s", path, tag)
449

    
450

    
451
def SetClusterParams(opts, args):
452
  """Modify the cluster.
453

    
454
  @param opts: the command line options selected by the user
455
  @type args: list
456
  @param args: should be an empty list
457
  @rtype: int
458
  @return: the desired exit code
459

    
460
  """
461
  if not (not opts.lvm_storage or opts.vg_name or
462
          opts.enabled_hypervisors or opts.hvparams or
463
          opts.beparams or opts.candidate_pool_size is not None):
464
    ToStderr("Please give at least one of the parameters.")
465
    return 1
466

    
467
  vg_name = opts.vg_name
468
  if not opts.lvm_storage and opts.vg_name:
469
    ToStdout("Options --no-lvm-storage and --vg-name conflict.")
470
    return 1
471

    
472
  hvlist = opts.enabled_hypervisors
473
  if hvlist is not None:
474
    hvlist = hvlist.split(",")
475

    
476
  hvparams = opts.hvparams
477
  if hvparams:
478
    # a list of (name, dict) we can pass directly to dict()
479
    hvparams = dict(opts.hvparams)
480
  for hv, hv_params in hvparams.iteritems():
481
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
482

    
483
  beparams = opts.beparams
484
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
485

    
486
  op = opcodes.OpSetClusterParams(vg_name=opts.vg_name,
487
                                  enabled_hypervisors=hvlist,
488
                                  hvparams=hvparams,
489
                                  beparams=beparams,
490
                                  candidate_pool_size=opts.candidate_pool_size)
491
  SubmitOpCode(op)
492
  return 0
493

    
494

    
495
def QueueOps(opts, args):
496
  """Queue operations.
497

    
498
  @param opts: the command line options selected by the user
499
  @type args: list
500
  @param args: should contain only one element, the subcommand
501
  @rtype: int
502
  @return: the desired exit code
503

    
504
  """
505
  command = args[0]
506
  client = GetClient()
507
  if command in ("drain", "undrain"):
508
    drain_flag = command == "drain"
509
    client.SetQueueDrainFlag(drain_flag)
510
  elif command == "info":
511
    result = client.QueryConfigValues(["drain_flag"])
512
    if result[0]:
513
      val = "set"
514
    else:
515
      val = "unset"
516
    ToStdout("The drain flag is %s" % val)
517
  return 0
518

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

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

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