Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ 07813a9e

History | View | Annotate | Download (22.6 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
  ToStdout("%s", GetClient().QueryConfigValues(["master_node"])[0])
211
  return 0
212

    
213

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

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

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

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

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

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

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

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

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

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

    
252
  return 0
253

    
254

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

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

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

    
270
  cl = GetClient()
271

    
272
  myname = utils.HostInfo().name
273

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

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

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

    
284
  return 0
285

    
286

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

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

    
296
  """
297
  cl = GetClient()
298

    
299
  command = " ".join(args)
300

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

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

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

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

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

    
320
  return 0
321

    
322

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

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

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

    
342

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

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

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

    
358
  nodes, nlvm, instances, missing = result
359

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

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

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

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

    
404
  return retcode
405

    
406

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

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

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

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

    
424

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

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

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

    
444

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

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

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

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

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

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

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

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

    
486

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

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

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

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

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

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