Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ 4040a784

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

    
92
  # type wrangling
93
  try:
94
    beparams[constants.BE_VCPUS] = int(beparams[constants.BE_VCPUS])
95
  except ValueError:
96
    ToStderr("%s must be an integer", constants.BE_VCPUS)
97
    return 1
98

    
99
  if not isinstance(beparams[constants.BE_MEMORY], int):
100
    beparams[constants.BE_MEMORY] = utils.ParseUnit(
101
        beparams[constants.BE_MEMORY])
102

    
103
  # prepare hvparams dict
104
  for hv in constants.HYPER_TYPES:
105
    if hv not in hvparams:
106
      hvparams[hv] = {}
107
    for parameter in constants.HVC_DEFAULTS[hv]:
108
      if parameter not in hvparams[hv]:
109
        hvparams[hv][parameter] = constants.HVC_DEFAULTS[hv][parameter]
110

    
111
  for hv in hvlist:
112
    if hv not in constants.HYPER_TYPES:
113
      ToStderr("invalid hypervisor: %s", hv)
114
      return 1
115

    
116
  bootstrap.InitCluster(cluster_name=args[0],
117
                        secondary_ip=opts.secondary_ip,
118
                        vg_name=vg_name,
119
                        mac_prefix=opts.mac_prefix,
120
                        def_bridge=opts.def_bridge,
121
                        master_netdev=opts.master_netdev,
122
                        file_storage_dir=opts.file_storage_dir,
123
                        enabled_hypervisors=hvlist,
124
                        default_hypervisor=opts.default_hypervisor,
125
                        hvparams=hvparams,
126
                        beparams=beparams,
127
                        candidate_pool_size=opts.candidate_pool_size,
128
                        )
129
  return 0
130

    
131

    
132
@UsesRPC
133
def DestroyCluster(opts, args):
134
  """Destroy the cluster.
135

    
136
  @param opts: the command line options selected by the user
137
  @type args: list
138
  @param args: should be an empty list
139
  @rtype: int
140
  @return: the desired exit code
141

    
142
  """
143
  if not opts.yes_do_it:
144
    ToStderr("Destroying a cluster is irreversible. If you really want"
145
             " destroy this cluster, supply the --yes-do-it option.")
146
    return 1
147

    
148
  op = opcodes.OpDestroyCluster()
149
  master = SubmitOpCode(op)
150
  # if we reached this, the opcode didn't fail; we can proceed to
151
  # shutdown all the daemons
152
  bootstrap.FinalizeClusterDestroy(master)
153
  return 0
154

    
155

    
156
def RenameCluster(opts, args):
157
  """Rename the cluster.
158

    
159
  @param opts: the command line options selected by the user
160
  @type args: list
161
  @param args: should contain only one element, the new cluster name
162
  @rtype: int
163
  @return: the desired exit code
164

    
165
  """
166
  name = args[0]
167
  if not opts.force:
168
    usertext = ("This will rename the cluster to '%s'. If you are connected"
169
                " over the network to the cluster name, the operation is very"
170
                " dangerous as the IP address will be removed from the node"
171
                " and the change may not go through. Continue?") % name
172
    if not AskUser(usertext):
173
      return 1
174

    
175
  op = opcodes.OpRenameCluster(name=name)
176
  SubmitOpCode(op)
177
  return 0
178

    
179

    
180
def RedistributeConfig(opts, args):
181
  """Forces push of the cluster configuration.
182

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

    
189
  """
190
  op = opcodes.OpRedistributeConf()
191
  SubmitOrSend(op, opts)
192
  return 0
193

    
194

    
195
def ShowClusterVersion(opts, args):
196
  """Write version of ganeti software to the standard output.
197

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

    
204
  """
205
  op = opcodes.OpQueryClusterInfo()
206
  result = SubmitOpCode(op)
207
  ToStdout("Software version: %s", result["software_version"])
208
  ToStdout("Internode protocol: %s", result["protocol_version"])
209
  ToStdout("Configuration format: %s", result["config_version"])
210
  ToStdout("OS api version: %s", result["os_api_version"])
211
  ToStdout("Export interface: %s", result["export_version"])
212
  return 0
213

    
214

    
215
def ShowClusterMaster(opts, args):
216
  """Write name of master node to the standard output.
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
  ToStdout("%s", GetClient().QueryConfigValues(["master_node"])[0])
226
  return 0
227

    
228

    
229
def ShowClusterConfig(opts, args):
230
  """Shows cluster information.
231

    
232
  @param opts: the command line options selected by the user
233
  @type args: list
234
  @param args: should be an empty list
235
  @rtype: int
236
  @return: the desired exit code
237

    
238
  """
239
  op = opcodes.OpQueryClusterInfo()
240
  result = SubmitOpCode(op)
241

    
242
  ToStdout("Cluster name: %s", result["name"])
243

    
244
  ToStdout("Master node: %s", result["master"])
245

    
246
  ToStdout("Architecture (this node): %s (%s)",
247
           result["architecture"][0], result["architecture"][1])
248

    
249
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
250
  ToStdout("Enabled hypervisors: %s", ", ".join(result["enabled_hypervisors"]))
251

    
252
  ToStdout("Hypervisor parameters:")
253
  for hv_name, hv_dict in result["hvparams"].items():
254
    ToStdout("  - %s:", hv_name)
255
    for item, val in hv_dict.iteritems():
256
      ToStdout("      %s: %s", item, val)
257

    
258
  ToStdout("Cluster parameters:")
259
  ToStdout("  - candidate pool size: %s", result["candidate_pool_size"])
260

    
261
  ToStdout("Default instance parameters:")
262
  for gr_name, gr_dict in result["beparams"].items():
263
    ToStdout("  - %s:", gr_name)
264
    for item, val in gr_dict.iteritems():
265
      ToStdout("      %s: %s", item, val)
266

    
267
  return 0
268

    
269

    
270
def ClusterCopyFile(opts, args):
271
  """Copy a file from master to some nodes.
272

    
273
  @param opts: the command line options selected by the user
274
  @type args: list
275
  @param args: should contain only one element, the path of
276
      the file to be copied
277
  @rtype: int
278
  @return: the desired exit code
279

    
280
  """
281
  filename = args[0]
282
  if not os.path.exists(filename):
283
    raise errors.OpPrereqError("No such filename '%s'" % filename)
284

    
285
  cl = GetClient()
286

    
287
  myname = utils.HostInfo().name
288

    
289
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
290

    
291
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl)
292
  results = [name for name in results if name != myname]
293

    
294
  srun = ssh.SshRunner(cluster_name=cluster_name)
295
  for node in results:
296
    if not srun.CopyFileToNode(node, filename):
297
      ToStderr("Copy of file %s to node %s failed", filename, node)
298

    
299
  return 0
300

    
301

    
302
def RunClusterCommand(opts, args):
303
  """Run a command on some nodes.
304

    
305
  @param opts: the command line options selected by the user
306
  @type args: list
307
  @param args: should contain the command to be run and its arguments
308
  @rtype: int
309
  @return: the desired exit code
310

    
311
  """
312
  cl = GetClient()
313

    
314
  command = " ".join(args)
315

    
316
  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
317

    
318
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
319
                                                    "master_node"])
320

    
321
  srun = ssh.SshRunner(cluster_name=cluster_name)
322

    
323
  # Make sure master node is at list end
324
  if master_node in nodes:
325
    nodes.remove(master_node)
326
    nodes.append(master_node)
327

    
328
  for name in nodes:
329
    result = srun.Run(name, "root", command)
330
    ToStdout("------------------------------------------------")
331
    ToStdout("node: %s", name)
332
    ToStdout("%s", result.output)
333
    ToStdout("return code = %s", result.exit_code)
334

    
335
  return 0
336

    
337

    
338
def VerifyCluster(opts, args):
339
  """Verify integrity of cluster, performing various test on nodes.
340

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

    
347
  """
348
  skip_checks = []
349
  if opts.skip_nplusone_mem:
350
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
351
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
352
  if SubmitOpCode(op):
353
    return 0
354
  else:
355
    return 1
356

    
357

    
358
def VerifyDisks(opts, args):
359
  """Verify integrity of cluster disks.
360

    
361
  @param opts: the command line options selected by the user
362
  @type args: list
363
  @param args: should be an empty list
364
  @rtype: int
365
  @return: the desired exit code
366

    
367
  """
368
  op = opcodes.OpVerifyDisks()
369
  result = SubmitOpCode(op)
370
  if not isinstance(result, (list, tuple)) or len(result) != 4:
371
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
372

    
373
  nodes, nlvm, instances, missing = result
374

    
375
  if nodes:
376
    ToStdout("Nodes unreachable or with bad data:")
377
    for name in nodes:
378
      ToStdout("\t%s", name)
379
  retcode = constants.EXIT_SUCCESS
380

    
381
  if nlvm:
382
    for node, text in nlvm.iteritems():
383
      ToStdout("Error on node %s: LVM error: %s",
384
               node, text[-400:].encode('string_escape'))
385
      retcode |= 1
386
      ToStdout("You need to fix these nodes first before fixing instances")
387

    
388
  if instances:
389
    for iname in instances:
390
      if iname in missing:
391
        continue
392
      op = opcodes.OpActivateInstanceDisks(instance_name=iname)
393
      try:
394
        ToStdout("Activating disks for instance '%s'", iname)
395
        SubmitOpCode(op)
396
      except errors.GenericError, err:
397
        nret, msg = FormatError(err)
398
        retcode |= nret
399
        ToStderr("Error activating disks for instance %s: %s", iname, msg)
400

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

    
419
  return retcode
420

    
421

    
422
@UsesRPC
423
def MasterFailover(opts, args):
424
  """Failover the master node.
425

    
426
  This command, when run on a non-master node, will cause the current
427
  master to cease being master, and the non-master to become new
428
  master.
429

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

    
436
  """
437
  return bootstrap.MasterFailover()
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.candidate_pool_size is not None):
473
    ToStderr("Please give at least one of the parameters.")
474
    return 1
475

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

    
481
  hvlist = opts.enabled_hypervisors
482
  if hvlist is not None:
483
    hvlist = hvlist.split(",")
484

    
485
  hvparams = opts.hvparams
486
  if hvparams:
487
    # a list of (name, dict) we can pass directly to dict()
488
    hvparams = dict(opts.hvparams)
489

    
490
  beparams = opts.beparams
491

    
492
  op = opcodes.OpSetClusterParams(vg_name=opts.vg_name,
493
                                  enabled_hypervisors=hvlist,
494
                                  hvparams=hvparams,
495
                                  beparams=beparams,
496
                                  candidate_pool_size=opts.candidate_pool_size)
497
  SubmitOpCode(op)
498
  return 0
499

    
500

    
501
def QueueOps(opts, args):
502
  """Queue operations.
503

    
504
  @param opts: the command line options selected by the user
505
  @type args: list
506
  @param args: should contain only one element, the subcommand
507
  @rtype: int
508
  @return: the desired exit code
509

    
510
  """
511
  command = args[0]
512
  client = GetClient()
513
  if command in ("drain", "undrain"):
514
    drain_flag = command == "drain"
515
    client.SetQueueDrainFlag(drain_flag)
516
  elif command == "info":
517
    result = client.QueryConfigValues(["drain_flag"])
518
    if result[0]:
519
      val = "set"
520
    else:
521
      val = "unset"
522
    ToStdout("The drain flag is %s" % val)
523
  return 0
524

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

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

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