Unify the “--backend-parameters” option
[ganeti-local] / scripts / gnt-cluster
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 import os.path
28 import time
29
30 from ganeti.cli import *
31 from ganeti import opcodes
32 from ganeti import constants
33 from ganeti import errors
34 from ganeti import utils
35 from ganeti import bootstrap
36 from ganeti import ssh
37 from ganeti import objects
38
39
40 @UsesRPC
41 def InitCluster(opts, args):
42   """Initialize the cluster.
43
44   @param opts: the command line options selected by the user
45   @type args: list
46   @param args: should contain only one element, the desired
47       cluster name
48   @rtype: int
49   @return: the desired exit code
50
51   """
52   if not opts.lvm_storage and opts.vg_name:
53     ToStderr("Options --no-lvm-storage and --vg-name conflict.")
54     return 1
55
56   vg_name = opts.vg_name
57   if opts.lvm_storage and not opts.vg_name:
58     vg_name = constants.DEFAULT_VG
59
60   hvlist = opts.enabled_hypervisors
61   hvlist = hvlist.split(",")
62
63   hvparams = dict(opts.hvparams)
64   beparams = opts.beparams
65   nicparams = opts.nicparams
66
67   # prepare beparams dict
68   beparams = objects.FillDict(constants.BEC_DEFAULTS, beparams)
69   utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
70
71   # prepare nicparams dict
72   nicparams = objects.FillDict(constants.NICC_DEFAULTS, nicparams)
73   utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
74
75   # prepare hvparams dict
76   for hv in constants.HYPER_TYPES:
77     if hv not in hvparams:
78       hvparams[hv] = {}
79     hvparams[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], hvparams[hv])
80     utils.ForceDictType(hvparams[hv], constants.HVS_PARAMETER_TYPES)
81
82   bootstrap.InitCluster(cluster_name=args[0],
83                         secondary_ip=opts.secondary_ip,
84                         vg_name=vg_name,
85                         mac_prefix=opts.mac_prefix,
86                         master_netdev=opts.master_netdev,
87                         file_storage_dir=opts.file_storage_dir,
88                         enabled_hypervisors=hvlist,
89                         hvparams=hvparams,
90                         beparams=beparams,
91                         nicparams=nicparams,
92                         candidate_pool_size=opts.candidate_pool_size,
93                         modify_etc_hosts=opts.modify_etc_hosts,
94                         )
95   op = opcodes.OpPostInitCluster()
96   SubmitOpCode(op)
97   return 0
98
99
100 @UsesRPC
101 def DestroyCluster(opts, args):
102   """Destroy the cluster.
103
104   @param opts: the command line options selected by the user
105   @type args: list
106   @param args: should be an empty list
107   @rtype: int
108   @return: the desired exit code
109
110   """
111   if not opts.yes_do_it:
112     ToStderr("Destroying a cluster is irreversible. If you really want"
113              " destroy this cluster, supply the --yes-do-it option.")
114     return 1
115
116   op = opcodes.OpDestroyCluster()
117   master = SubmitOpCode(op)
118   # if we reached this, the opcode didn't fail; we can proceed to
119   # shutdown all the daemons
120   bootstrap.FinalizeClusterDestroy(master)
121   return 0
122
123
124 def RenameCluster(opts, args):
125   """Rename the cluster.
126
127   @param opts: the command line options selected by the user
128   @type args: list
129   @param args: should contain only one element, the new cluster name
130   @rtype: int
131   @return: the desired exit code
132
133   """
134   name = args[0]
135   if not opts.force:
136     usertext = ("This will rename the cluster to '%s'. If you are connected"
137                 " over the network to the cluster name, the operation is very"
138                 " dangerous as the IP address will be removed from the node"
139                 " and the change may not go through. Continue?") % name
140     if not AskUser(usertext):
141       return 1
142
143   op = opcodes.OpRenameCluster(name=name)
144   SubmitOpCode(op)
145   return 0
146
147
148 def RedistributeConfig(opts, args):
149   """Forces push of the cluster configuration.
150
151   @param opts: the command line options selected by the user
152   @type args: list
153   @param args: empty list
154   @rtype: int
155   @return: the desired exit code
156
157   """
158   op = opcodes.OpRedistributeConfig()
159   SubmitOrSend(op, opts)
160   return 0
161
162
163 def ShowClusterVersion(opts, args):
164   """Write version of ganeti software to the standard output.
165
166   @param opts: the command line options selected by the user
167   @type args: list
168   @param args: should be an empty list
169   @rtype: int
170   @return: the desired exit code
171
172   """
173   cl = GetClient()
174   result = cl.QueryClusterInfo()
175   ToStdout("Software version: %s", result["software_version"])
176   ToStdout("Internode protocol: %s", result["protocol_version"])
177   ToStdout("Configuration format: %s", result["config_version"])
178   ToStdout("OS api version: %s", result["os_api_version"])
179   ToStdout("Export interface: %s", result["export_version"])
180   return 0
181
182
183 def ShowClusterMaster(opts, args):
184   """Write name of master node to the standard output.
185
186   @param opts: the command line options selected by the user
187   @type args: list
188   @param args: should be an empty list
189   @rtype: int
190   @return: the desired exit code
191
192   """
193   master = bootstrap.GetMaster()
194   ToStdout(master)
195   return 0
196
197 def _PrintGroupedParams(paramsdict):
198   """Print Grouped parameters (be, nic, disk) by group.
199
200   @type paramsdict: dict of dicts
201   @param paramsdict: {group: {param: value, ...}, ...}
202
203   """
204   for gr_name, gr_dict in paramsdict.items():
205     ToStdout("  - %s:", gr_name)
206     for item, val in gr_dict.iteritems():
207       ToStdout("      %s: %s", item, val)
208
209 def ShowClusterConfig(opts, args):
210   """Shows cluster information.
211
212   @param opts: the command line options selected by the user
213   @type args: list
214   @param args: should be an empty list
215   @rtype: int
216   @return: the desired exit code
217
218   """
219   cl = GetClient()
220   result = cl.QueryClusterInfo()
221
222   ToStdout("Cluster name: %s", result["name"])
223
224   ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
225   ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))
226
227   ToStdout("Master node: %s", result["master"])
228
229   ToStdout("Architecture (this node): %s (%s)",
230            result["architecture"][0], result["architecture"][1])
231
232   if result["tags"]:
233     tags = ", ".join(utils.NiceSort(result["tags"]))
234   else:
235     tags = "(none)"
236
237   ToStdout("Tags: %s", tags)
238
239   ToStdout("Default hypervisor: %s", result["default_hypervisor"])
240   ToStdout("Enabled hypervisors: %s", ", ".join(result["enabled_hypervisors"]))
241
242   ToStdout("Hypervisor parameters:")
243   _PrintGroupedParams(result["hvparams"])
244
245   ToStdout("Cluster parameters:")
246   ToStdout("  - candidate pool size: %s", result["candidate_pool_size"])
247   ToStdout("  - master netdev: %s", result["master_netdev"])
248   ToStdout("  - lvm volume group: %s", result["volume_group_name"])
249   ToStdout("  - file storage path: %s", result["file_storage_dir"])
250
251   ToStdout("Default instance parameters:")
252   _PrintGroupedParams(result["beparams"])
253
254   ToStdout("Default nic parameters:")
255   _PrintGroupedParams(result["nicparams"])
256
257   return 0
258
259
260 def ClusterCopyFile(opts, args):
261   """Copy a file from master to some nodes.
262
263   @param opts: the command line options selected by the user
264   @type args: list
265   @param args: should contain only one element, the path of
266       the file to be copied
267   @rtype: int
268   @return: the desired exit code
269
270   """
271   filename = args[0]
272   if not os.path.exists(filename):
273     raise errors.OpPrereqError("No such filename '%s'" % filename)
274
275   cl = GetClient()
276
277   myname = utils.HostInfo().name
278
279   cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
280
281   results = GetOnlineNodes(nodes=opts.nodes, cl=cl)
282   results = [name for name in results if name != myname]
283
284   srun = ssh.SshRunner(cluster_name=cluster_name)
285   for node in results:
286     if not srun.CopyFileToNode(node, filename):
287       ToStderr("Copy of file %s to node %s failed", filename, node)
288
289   return 0
290
291
292 def RunClusterCommand(opts, args):
293   """Run a command on some nodes.
294
295   @param opts: the command line options selected by the user
296   @type args: list
297   @param args: should contain the command to be run and its arguments
298   @rtype: int
299   @return: the desired exit code
300
301   """
302   cl = GetClient()
303
304   command = " ".join(args)
305
306   nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
307
308   cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
309                                                     "master_node"])
310
311   srun = ssh.SshRunner(cluster_name=cluster_name)
312
313   # Make sure master node is at list end
314   if master_node in nodes:
315     nodes.remove(master_node)
316     nodes.append(master_node)
317
318   for name in nodes:
319     result = srun.Run(name, "root", command)
320     ToStdout("------------------------------------------------")
321     ToStdout("node: %s", name)
322     ToStdout("%s", result.output)
323     ToStdout("return code = %s", result.exit_code)
324
325   return 0
326
327
328 def VerifyCluster(opts, args):
329   """Verify integrity of cluster, performing various test on nodes.
330
331   @param opts: the command line options selected by the user
332   @type args: list
333   @param args: should be an empty list
334   @rtype: int
335   @return: the desired exit code
336
337   """
338   skip_checks = []
339   if opts.skip_nplusone_mem:
340     skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
341   op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
342                                verbose=opts.verbose,
343                                error_codes=opts.error_codes,
344                                debug_simulate_errors=opts.simulate_errors)
345   if SubmitOpCode(op):
346     return 0
347   else:
348     return 1
349
350
351 def VerifyDisks(opts, args):
352   """Verify integrity of cluster disks.
353
354   @param opts: the command line options selected by the user
355   @type args: list
356   @param args: should be an empty list
357   @rtype: int
358   @return: the desired exit code
359
360   """
361   op = opcodes.OpVerifyDisks()
362   result = SubmitOpCode(op)
363   if not isinstance(result, (list, tuple)) or len(result) != 3:
364     raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
365
366   bad_nodes, instances, missing = result
367
368   retcode = constants.EXIT_SUCCESS
369
370   if bad_nodes:
371     for node, text in bad_nodes.items():
372       ToStdout("Error gathering data on node %s: %s",
373                node, utils.SafeEncode(text[-400:]))
374       retcode |= 1
375       ToStdout("You need to fix these nodes first before fixing instances")
376
377   if instances:
378     for iname in instances:
379       if iname in missing:
380         continue
381       op = opcodes.OpActivateInstanceDisks(instance_name=iname)
382       try:
383         ToStdout("Activating disks for instance '%s'", iname)
384         SubmitOpCode(op)
385       except errors.GenericError, err:
386         nret, msg = FormatError(err)
387         retcode |= nret
388         ToStderr("Error activating disks for instance %s: %s", iname, msg)
389
390   if missing:
391     for iname, ival in missing.iteritems():
392       all_missing = utils.all(ival, lambda x: x[0] in bad_nodes)
393       if all_missing:
394         ToStdout("Instance %s cannot be verified as it lives on"
395                  " broken nodes", iname)
396       else:
397         ToStdout("Instance %s has missing logical volumes:", iname)
398         ival.sort()
399         for node, vol in ival:
400           if node in bad_nodes:
401             ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
402           else:
403             ToStdout("\t%s /dev/xenvg/%s", node, vol)
404     ToStdout("You need to run replace_disks for all the above"
405            " instances, if this message persist after fixing nodes.")
406     retcode |= 1
407
408   return retcode
409
410
411 def RepairDiskSizes(opts, args):
412   """Verify sizes of cluster disks.
413
414   @param opts: the command line options selected by the user
415   @type args: list
416   @param args: optional list of instances to restrict check to
417   @rtype: int
418   @return: the desired exit code
419
420   """
421   op = opcodes.OpRepairDiskSizes(instances=args)
422   SubmitOpCode(op)
423
424
425 @UsesRPC
426 def MasterFailover(opts, args):
427   """Failover the master node.
428
429   This command, when run on a non-master node, will cause the current
430   master to cease being master, and the non-master to become new
431   master.
432
433   @param opts: the command line options selected by the user
434   @type args: list
435   @param args: should be an empty list
436   @rtype: int
437   @return: the desired exit code
438
439   """
440   if opts.no_voting:
441     usertext = ("This will perform the failover even if most other nodes"
442                 " are down, or if this node is outdated. This is dangerous"
443                 " as it can lead to a non-consistent cluster. Check the"
444                 " gnt-cluster(8) man page before proceeding. Continue?")
445     if not AskUser(usertext):
446       return 1
447
448   return bootstrap.MasterFailover(no_voting=opts.no_voting)
449
450
451 def SearchTags(opts, args):
452   """Searches the tags on all the cluster.
453
454   @param opts: the command line options selected by the user
455   @type args: list
456   @param args: should contain only one element, the tag pattern
457   @rtype: int
458   @return: the desired exit code
459
460   """
461   op = opcodes.OpSearchTags(pattern=args[0])
462   result = SubmitOpCode(op)
463   if not result:
464     return 1
465   result = list(result)
466   result.sort()
467   for path, tag in result:
468     ToStdout("%s %s", path, tag)
469
470
471 def SetClusterParams(opts, args):
472   """Modify the cluster.
473
474   @param opts: the command line options selected by the user
475   @type args: list
476   @param args: should be an empty list
477   @rtype: int
478   @return: the desired exit code
479
480   """
481   if not (not opts.lvm_storage or opts.vg_name or
482           opts.enabled_hypervisors or opts.hvparams or
483           opts.beparams or opts.nicparams or
484           opts.candidate_pool_size is not None):
485     ToStderr("Please give at least one of the parameters.")
486     return 1
487
488   vg_name = opts.vg_name
489   if not opts.lvm_storage and opts.vg_name:
490     ToStdout("Options --no-lvm-storage and --vg-name conflict.")
491     return 1
492   elif not opts.lvm_storage:
493     vg_name = ''
494
495   hvlist = opts.enabled_hypervisors
496   if hvlist is not None:
497     hvlist = hvlist.split(",")
498
499   # a list of (name, dict) we can pass directly to dict() (or [])
500   hvparams = dict(opts.hvparams)
501   for hv, hv_params in hvparams.iteritems():
502     utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
503
504   beparams = opts.beparams
505   utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
506
507   nicparams = opts.nicparams
508   utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
509
510   op = opcodes.OpSetClusterParams(vg_name=vg_name,
511                                   enabled_hypervisors=hvlist,
512                                   hvparams=hvparams,
513                                   beparams=beparams,
514                                   nicparams=nicparams,
515                                   candidate_pool_size=opts.candidate_pool_size)
516   SubmitOpCode(op)
517   return 0
518
519
520 def QueueOps(opts, args):
521   """Queue operations.
522
523   @param opts: the command line options selected by the user
524   @type args: list
525   @param args: should contain only one element, the subcommand
526   @rtype: int
527   @return: the desired exit code
528
529   """
530   command = args[0]
531   client = GetClient()
532   if command in ("drain", "undrain"):
533     drain_flag = command == "drain"
534     client.SetQueueDrainFlag(drain_flag)
535   elif command == "info":
536     result = client.QueryConfigValues(["drain_flag"])
537     if result[0]:
538       val = "set"
539     else:
540       val = "unset"
541     ToStdout("The drain flag is %s" % val)
542   else:
543     raise errors.OpPrereqError("Command '%s' is not valid." % command)
544
545   return 0
546
547
548 def _ShowWatcherPause(until):
549   if until is None or until < time.time():
550     ToStdout("The watcher is not paused.")
551   else:
552     ToStdout("The watcher is paused until %s.", time.ctime(until))
553
554
555 def WatcherOps(opts, args):
556   """Watcher operations.
557
558   @param opts: the command line options selected by the user
559   @type args: list
560   @param args: should contain only one element, the subcommand
561   @rtype: int
562   @return: the desired exit code
563
564   """
565   command = args[0]
566   client = GetClient()
567
568   if command == "continue":
569     client.SetWatcherPause(None)
570     ToStdout("The watcher is no longer paused.")
571
572   elif command == "pause":
573     if len(args) < 2:
574       raise errors.OpPrereqError("Missing pause duration")
575
576     result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
577     _ShowWatcherPause(result)
578
579   elif command == "info":
580     result = client.QueryConfigValues(["watcher_pause"])
581     _ShowWatcherPause(result)
582
583   else:
584     raise errors.OpPrereqError("Command '%s' is not valid." % command)
585
586   return 0
587
588
589 # this is an option common to more than one command, so we declare
590 # it here and reuse it
591 node_option = cli_option("-n", "--node", action="append", dest="nodes",
592                          help="Node to copy to (if not given, all nodes),"
593                               " can be given multiple times",
594                          metavar="<node>", default=[])
595
596 commands = {
597   'init': (InitCluster, [ArgHost(min=1, max=1)],
598            [DEBUG_OPT,
599             cli_option("-s", "--secondary-ip", dest="secondary_ip",
600                        help="Specify the secondary ip for this node;"
601                        " if given, the entire cluster must have secondary"
602                        " addresses",
603                        metavar="ADDRESS", default=None),
604             cli_option("-m", "--mac-prefix", dest="mac_prefix",
605                        help="Specify the mac prefix for the instance IP"
606                        " addresses, in the format XX:XX:XX",
607                        metavar="PREFIX",
608                        default=constants.DEFAULT_MAC_PREFIX,),
609             cli_option("-g", "--vg-name", dest="vg_name",
610                        help="Specify the volume group name "
611                        " (cluster-wide) for disk allocation [xenvg]",
612                        metavar="VG",
613                        default=None,),
614             cli_option("--master-netdev", dest="master_netdev",
615                        help="Specify the node interface (cluster-wide)"
616                          " on which the master IP address will be added "
617                          " [%s]" % constants.DEFAULT_BRIDGE,
618                        metavar="NETDEV",
619                        default=constants.DEFAULT_BRIDGE,),
620             cli_option("--file-storage-dir", dest="file_storage_dir",
621                        help="Specify the default directory (cluster-wide)"
622                             " for storing the file-based disks [%s]" %
623                             constants.DEFAULT_FILE_STORAGE_DIR,
624                        metavar="DIR",
625                        default=constants.DEFAULT_FILE_STORAGE_DIR,),
626             cli_option("--no-lvm-storage", dest="lvm_storage",
627                        help="No support for lvm based instances"
628                             " (cluster-wide)",
629                        action="store_false", default=True,),
630             cli_option("--no-etc-hosts", dest="modify_etc_hosts",
631                        help="Don't modify /etc/hosts"
632                             " (cluster-wide)",
633                        action="store_false", default=True,),
634             cli_option("--enabled-hypervisors", dest="enabled_hypervisors",
635                        help="Comma-separated list of hypervisors",
636                        type="string",
637                        default=constants.DEFAULT_ENABLED_HYPERVISOR),
638             cli_option("-H", "--hypervisor-parameters", dest="hvparams",
639                        help="Hypervisor and hypervisor options, in the format"
640                             " hypervisor:option=value,option=value,...",
641                        default=[],
642                        action="append",
643                        type="identkeyval"),
644             BACKEND_OPT,
645             cli_option("-N", "--nic-parameters", dest="nicparams",
646                        type="keyval", default={},
647                        help="NIC parameters"),
648             cli_option("-C", "--candidate-pool-size",
649                        default=constants.MASTER_POOL_SIZE_DEFAULT,
650                        help="Set the candidate pool size",
651                        dest="candidate_pool_size", type="int"),
652             ],
653            "[opts...] <cluster_name>",
654            "Initialises a new cluster configuration"),
655   'destroy': (DestroyCluster, ARGS_NONE,
656               [DEBUG_OPT,
657                cli_option("--yes-do-it", dest="yes_do_it",
658                           help="Destroy cluster",
659                           action="store_true"),
660               ],
661               "", "Destroy cluster"),
662   'rename': (RenameCluster, [ArgHost(min=1, max=1)],
663              [DEBUG_OPT, FORCE_OPT],
664              "<new_name>",
665              "Renames the cluster"),
666   'redist-conf': (RedistributeConfig, ARGS_NONE, [DEBUG_OPT, SUBMIT_OPT],
667                   "",
668                   "Forces a push of the configuration file and ssconf files"
669                   " to the nodes in the cluster"),
670   'verify': (VerifyCluster, ARGS_NONE,
671              [DEBUG_OPT, VERBOSE_OPT, DEBUG_SIMERR_OPT,
672               cli_option("--error-codes", dest="error_codes",
673                          help="Enable parseable error messages",
674                          action="store_true", default=False),
675               cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
676                          help="Skip N+1 memory redundancy tests",
677                          action="store_true", default=False),
678               ],
679              "", "Does a check on the cluster configuration"),
680   'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
681                    "", "Does a check on the cluster disk status"),
682   'repair-disk-sizes': (RepairDiskSizes, ARGS_MANY_INSTANCES, [DEBUG_OPT],
683                    "", "Updates mismatches in recorded disk sizes"),
684   'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT,
685                      cli_option("--no-voting", dest="no_voting",
686                                 help="Skip node agreement check (dangerous)",
687                                 action="store_true",
688                                 default=False,),
689                      ],
690                      "", "Makes the current node the master"),
691   'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
692               "", "Shows the cluster version"),
693   'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
694                 "", "Shows the cluster master"),
695   'copyfile': (ClusterCopyFile, [ArgFile(min=1, max=1)],
696                [DEBUG_OPT, node_option],
697                "[-n node...] <filename>",
698                "Copies a file to all (or only some) nodes"),
699   'command': (RunClusterCommand, [ArgCommand(min=1)], [DEBUG_OPT, node_option],
700               "[-n node...] <command>",
701               "Runs a command on all (or only some) nodes"),
702   'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
703            "", "Show cluster configuration"),
704   'list-tags': (ListTags, ARGS_NONE,
705                 [DEBUG_OPT], "", "List the tags of the cluster"),
706   'add-tags': (AddTags, [ArgUnknown()], [DEBUG_OPT, TAG_SRC_OPT],
707                "tag...", "Add tags to the cluster"),
708   'remove-tags': (RemoveTags, [ArgUnknown()], [DEBUG_OPT, TAG_SRC_OPT],
709                   "tag...", "Remove tags from the cluster"),
710   'search-tags': (SearchTags, [ArgUnknown(min=1, max=1)],
711                   [DEBUG_OPT], "", "Searches the tags on all objects on"
712                   " the cluster for a given pattern (regex)"),
713   'queue': (QueueOps,
714             [ArgChoice(min=1, max=1, choices=["drain", "undrain", "info"])],
715             [DEBUG_OPT],
716             "drain|undrain|info", "Change queue properties"),
717   'watcher': (WatcherOps,
718               [ArgChoice(min=1, max=1,
719                          choices=["pause", "continue", "info"]),
720                ArgSuggest(min=0, max=1, choices=["30m", "1h", "4h"])],
721               [DEBUG_OPT],
722               "{pause <timespec>|continue|info}", "Change watcher properties"),
723   'modify': (SetClusterParams, ARGS_NONE,
724              [DEBUG_OPT,
725               cli_option("-g", "--vg-name", dest="vg_name",
726                          help="Specify the volume group name "
727                          " (cluster-wide) for disk allocation "
728                          "and enable lvm based storage",
729                          metavar="VG",),
730               cli_option("--no-lvm-storage", dest="lvm_storage",
731                          help="Disable support for lvm based instances"
732                               " (cluster-wide)",
733                          action="store_false", default=True,),
734               cli_option("--enabled-hypervisors", dest="enabled_hypervisors",
735                          help="Comma-separated list of hypervisors",
736                          type="string", default=None),
737               cli_option("-H", "--hypervisor-parameters", dest="hvparams",
738                          help="Hypervisor and hypervisor options, in the"
739                          " format"
740                          " hypervisor:option=value,option=value,...",
741                          default=[],
742                          action="append",
743                          type="identkeyval"),
744               BACKEND_OPT,
745               cli_option("-N", "--nic-parameters", dest="nicparams",
746                          type="keyval", default={},
747                          help="NIC parameters"),
748               cli_option("-C", "--candidate-pool-size", default=None,
749                          help="Set the candidate pool size",
750                          dest="candidate_pool_size", type="int"),
751               ],
752              "[opts...]",
753              "Alters the parameters of the cluster"),
754   }
755
756 if __name__ == '__main__':
757   sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))