Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ 8eb148ae

History | View | Annotate | Download (22.7 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2006, 2007 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
# pylint: disable-msg=W0401,W0614
23
# W0401: Wildcard import ganeti.cli
24
# W0614: Unused import %s from wildcard import (since we need cli)
25

    
26
import sys
27
from optparse import make_option
28
import os.path
29

    
30
from ganeti.cli import *
31
from ganeti import opcodes
32
from ganeti import constants
33
from ganeti import errors
34
from ganeti import utils
35
from ganeti import bootstrap
36
from ganeti import ssh
37

    
38

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

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

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

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

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

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

    
72
  hvparams = dict(opts.hvparams)
73

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

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

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

    
96
  for hv in hvlist:
97
    if hv not in constants.HYPER_TYPES:
98
      ToStderr("invalid hypervisor: %s", hv)
99
      return 1
100

    
101
  bootstrap.InitCluster(cluster_name=args[0],
102
                        secondary_ip=opts.secondary_ip,
103
                        vg_name=vg_name,
104
                        mac_prefix=opts.mac_prefix,
105
                        def_bridge=opts.def_bridge,
106
                        master_netdev=opts.master_netdev,
107
                        file_storage_dir=opts.file_storage_dir,
108
                        enabled_hypervisors=hvlist,
109
                        default_hypervisor=opts.default_hypervisor,
110
                        hvparams=hvparams,
111
                        beparams=beparams,
112
                        candidate_pool_size=opts.candidate_pool_size,
113
                        )
114
  return 0
115

    
116

    
117
@UsesRPC
118
def DestroyCluster(opts, args):
119
  """Destroy the cluster.
120

    
121
  @param opts: the command line options selected by the user
122
  @type args: list
123
  @param args: should be an empty list
124
  @rtype: int
125
  @return: the desired exit code
126

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

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

    
140

    
141
def RenameCluster(opts, args):
142
  """Rename the cluster.
143

    
144
  @param opts: the command line options selected by the user
145
  @type args: list
146
  @param args: should contain only one element, the new cluster name
147
  @rtype: int
148
  @return: the desired exit code
149

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

    
160
  op = opcodes.OpRenameCluster(name=name)
161
  SubmitOpCode(op)
162
  return 0
163

    
164

    
165
def RedistributeConfig(opts, args):
166
  """Forces push of the cluster configuration.
167

    
168
  @param opts: the command line options selected by the user
169
  @type args: list
170
  @param args: empty list
171
  @rtype: int
172
  @return: the desired exit code
173

    
174
  """
175
  op = opcodes.OpRedistributeConfig()
176
  SubmitOrSend(op, opts)
177
  return 0
178

    
179

    
180
def ShowClusterVersion(opts, args):
181
  """Write version of ganeti software to the standard output.
182

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

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

    
199

    
200
def ShowClusterMaster(opts, args):
201
  """Write name of master node to the standard output.
202

    
203
  @param opts: the command line options selected by the user
204
  @type args: list
205
  @param args: should be an empty list
206
  @rtype: int
207
  @return: the desired exit code
208

    
209
  """
210
  master = bootstrap.GetMaster()
211
  ToStdout(master)
212
  return 0
213

    
214

    
215
def ShowClusterConfig(opts, args):
216
  """Shows cluster information.
217

    
218
  @param opts: the command line options selected by the user
219
  @type args: list
220
  @param args: should be an empty list
221
  @rtype: int
222
  @return: the desired exit code
223

    
224
  """
225
  cl = GetClient()
226
  result = cl.QueryClusterInfo()
227

    
228
  ToStdout("Cluster name: %s", result["name"])
229

    
230
  ToStdout("Master node: %s", result["master"])
231

    
232
  ToStdout("Architecture (this node): %s (%s)",
233
           result["architecture"][0], result["architecture"][1])
234

    
235
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
236
  ToStdout("Enabled hypervisors: %s", ", ".join(result["enabled_hypervisors"]))
237

    
238
  ToStdout("Hypervisor parameters:")
239
  for hv_name, hv_dict in result["hvparams"].items():
240
    ToStdout("  - %s:", hv_name)
241
    for item, val in hv_dict.iteritems():
242
      ToStdout("      %s: %s", item, val)
243

    
244
  ToStdout("Cluster parameters:")
245
  ToStdout("  - candidate pool size: %s", result["candidate_pool_size"])
246

    
247
  ToStdout("Default instance parameters:")
248
  for gr_name, gr_dict in result["beparams"].items():
249
    ToStdout("  - %s:", gr_name)
250
    for item, val in gr_dict.iteritems():
251
      ToStdout("      %s: %s", item, val)
252

    
253
  return 0
254

    
255

    
256
def ClusterCopyFile(opts, args):
257
  """Copy a file from master to some nodes.
258

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

    
266
  """
267
  filename = args[0]
268
  if not os.path.exists(filename):
269
    raise errors.OpPrereqError("No such filename '%s'" % filename)
270

    
271
  cl = GetClient()
272

    
273
  myname = utils.HostInfo().name
274

    
275
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
276

    
277
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl)
278
  results = [name for name in results if name != myname]
279

    
280
  srun = ssh.SshRunner(cluster_name=cluster_name)
281
  for node in results:
282
    if not srun.CopyFileToNode(node, filename):
283
      ToStderr("Copy of file %s to node %s failed", filename, node)
284

    
285
  return 0
286

    
287

    
288
def RunClusterCommand(opts, args):
289
  """Run a command on some nodes.
290

    
291
  @param opts: the command line options selected by the user
292
  @type args: list
293
  @param args: should contain the command to be run and its arguments
294
  @rtype: int
295
  @return: the desired exit code
296

    
297
  """
298
  cl = GetClient()
299

    
300
  command = " ".join(args)
301

    
302
  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
303

    
304
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
305
                                                    "master_node"])
306

    
307
  srun = ssh.SshRunner(cluster_name=cluster_name)
308

    
309
  # Make sure master node is at list end
310
  if master_node in nodes:
311
    nodes.remove(master_node)
312
    nodes.append(master_node)
313

    
314
  for name in nodes:
315
    result = srun.Run(name, "root", command)
316
    ToStdout("------------------------------------------------")
317
    ToStdout("node: %s", name)
318
    ToStdout("%s", result.output)
319
    ToStdout("return code = %s", result.exit_code)
320

    
321
  return 0
322

    
323

    
324
def VerifyCluster(opts, args):
325
  """Verify integrity of cluster, performing various test on nodes.
326

    
327
  @param opts: the command line options selected by the user
328
  @type args: list
329
  @param args: should be an empty list
330
  @rtype: int
331
  @return: the desired exit code
332

    
333
  """
334
  skip_checks = []
335
  if opts.skip_nplusone_mem:
336
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
337
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
338
  if SubmitOpCode(op):
339
    return 0
340
  else:
341
    return 1
342

    
343

    
344
def VerifyDisks(opts, args):
345
  """Verify integrity of cluster disks.
346

    
347
  @param opts: the command line options selected by the user
348
  @type args: list
349
  @param args: should be an empty list
350
  @rtype: int
351
  @return: the desired exit code
352

    
353
  """
354
  op = opcodes.OpVerifyDisks()
355
  result = SubmitOpCode(op)
356
  if not isinstance(result, (list, tuple)) or len(result) != 4:
357
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
358

    
359
  nodes, nlvm, instances, missing = result
360

    
361
  if nodes:
362
    ToStdout("Nodes unreachable or with bad data:")
363
    for name in nodes:
364
      ToStdout("\t%s", name)
365
  retcode = constants.EXIT_SUCCESS
366

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

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

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

    
405
  return retcode
406

    
407

    
408
@UsesRPC
409
def MasterFailover(opts, args):
410
  """Failover the master node.
411

    
412
  This command, when run on a non-master node, will cause the current
413
  master to cease being master, and the non-master to become new
414
  master.
415

    
416
  @param opts: the command line options selected by the user
417
  @type args: list
418
  @param args: should be an empty list
419
  @rtype: int
420
  @return: the desired exit code
421

    
422
  """
423
  return bootstrap.MasterFailover()
424

    
425

    
426
def SearchTags(opts, args):
427
  """Searches the tags on all the cluster.
428

    
429
  @param opts: the command line options selected by the user
430
  @type args: list
431
  @param args: should contain only one element, the tag pattern
432
  @rtype: int
433
  @return: the desired exit code
434

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

    
445

    
446
def SetClusterParams(opts, args):
447
  """Modify the cluster.
448

    
449
  @param opts: the command line options selected by the user
450
  @type args: list
451
  @param args: should be an empty list
452
  @rtype: int
453
  @return: the desired exit code
454

    
455
  """
456
  if not (not opts.lvm_storage or opts.vg_name or
457
          opts.enabled_hypervisors or opts.hvparams or
458
          opts.beparams or opts.candidate_pool_size is not None):
459
    ToStderr("Please give at least one of the parameters.")
460
    return 1
461

    
462
  vg_name = opts.vg_name
463
  if not opts.lvm_storage and opts.vg_name:
464
    ToStdout("Options --no-lvm-storage and --vg-name conflict.")
465
    return 1
466

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

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

    
476
  beparams = opts.beparams
477
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
478

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

    
487

    
488
def QueueOps(opts, args):
489
  """Queue operations.
490

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

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

    
513
  return 0
514

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

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

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