Introduce parseable error codes in LUVerifyCluster
[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   if SubmitOpCode(op):
345     return 0
346   else:
347     return 1
348
349
350 def VerifyDisks(opts, args):
351   """Verify integrity of cluster disks.
352
353   @param opts: the command line options selected by the user
354   @type args: list
355   @param args: should be an empty list
356   @rtype: int
357   @return: the desired exit code
358
359   """
360   op = opcodes.OpVerifyDisks()
361   result = SubmitOpCode(op)
362   if not isinstance(result, (list, tuple)) or len(result) != 3:
363     raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
364
365   bad_nodes, instances, missing = result
366
367   retcode = constants.EXIT_SUCCESS
368
369   if bad_nodes:
370     for node, text in bad_nodes.items():
371       ToStdout("Error gathering data on node %s: %s",
372                node, utils.SafeEncode(text[-400:]))
373       retcode |= 1
374       ToStdout("You need to fix these nodes first before fixing instances")
375
376   if instances:
377     for iname in instances:
378       if iname in missing:
379         continue
380       op = opcodes.OpActivateInstanceDisks(instance_name=iname)
381       try:
382         ToStdout("Activating disks for instance '%s'", iname)
383         SubmitOpCode(op)
384       except errors.GenericError, err:
385         nret, msg = FormatError(err)
386         retcode |= nret
387         ToStderr("Error activating disks for instance %s: %s", iname, msg)
388
389   if missing:
390     for iname, ival in missing.iteritems():
391       all_missing = utils.all(ival, lambda x: x[0] in bad_nodes)
392       if all_missing:
393         ToStdout("Instance %s cannot be verified as it lives on"
394                  " broken nodes", iname)
395       else:
396         ToStdout("Instance %s has missing logical volumes:", iname)
397         ival.sort()
398         for node, vol in ival:
399           if node in bad_nodes:
400             ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
401           else:
402             ToStdout("\t%s /dev/xenvg/%s", node, vol)
403     ToStdout("You need to run replace_disks for all the above"
404            " instances, if this message persist after fixing nodes.")
405     retcode |= 1
406
407   return retcode
408
409
410 def RepairDiskSizes(opts, args):
411   """Verify sizes of cluster disks.
412
413   @param opts: the command line options selected by the user
414   @type args: list
415   @param args: optional list of instances to restrict check to
416   @rtype: int
417   @return: the desired exit code
418
419   """
420   op = opcodes.OpRepairDiskSizes(instances=args)
421   SubmitOpCode(op)
422
423
424 @UsesRPC
425 def MasterFailover(opts, args):
426   """Failover the master node.
427
428   This command, when run on a non-master node, will cause the current
429   master to cease being master, and the non-master to become new
430   master.
431
432   @param opts: the command line options selected by the user
433   @type args: list
434   @param args: should be an empty list
435   @rtype: int
436   @return: the desired exit code
437
438   """
439   if opts.no_voting:
440     usertext = ("This will perform the failover even if most other nodes"
441                 " are down, or if this node is outdated. This is dangerous"
442                 " as it can lead to a non-consistent cluster. Check the"
443                 " gnt-cluster(8) man page before proceeding. Continue?")
444     if not AskUser(usertext):
445       return 1
446
447   return bootstrap.MasterFailover(no_voting=opts.no_voting)
448
449
450 def SearchTags(opts, args):
451   """Searches the tags on all the cluster.
452
453   @param opts: the command line options selected by the user
454   @type args: list
455   @param args: should contain only one element, the tag pattern
456   @rtype: int
457   @return: the desired exit code
458
459   """
460   op = opcodes.OpSearchTags(pattern=args[0])
461   result = SubmitOpCode(op)
462   if not result:
463     return 1
464   result = list(result)
465   result.sort()
466   for path, tag in result:
467     ToStdout("%s %s", path, tag)
468
469
470 def SetClusterParams(opts, args):
471   """Modify the cluster.
472
473   @param opts: the command line options selected by the user
474   @type args: list
475   @param args: should be an empty list
476   @rtype: int
477   @return: the desired exit code
478
479   """
480   if not (not opts.lvm_storage or opts.vg_name or
481           opts.enabled_hypervisors or opts.hvparams or
482           opts.beparams or opts.nicparams or
483           opts.candidate_pool_size is not None):
484     ToStderr("Please give at least one of the parameters.")
485     return 1
486
487   vg_name = opts.vg_name
488   if not opts.lvm_storage and opts.vg_name:
489     ToStdout("Options --no-lvm-storage and --vg-name conflict.")
490     return 1
491   elif not opts.lvm_storage:
492     vg_name = ''
493
494   hvlist = opts.enabled_hypervisors
495   if hvlist is not None:
496     hvlist = hvlist.split(",")
497
498   # a list of (name, dict) we can pass directly to dict() (or [])
499   hvparams = dict(opts.hvparams)
500   for hv, hv_params in hvparams.iteritems():
501     utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
502
503   beparams = opts.beparams
504   utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
505
506   nicparams = opts.nicparams
507   utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
508
509   op = opcodes.OpSetClusterParams(vg_name=vg_name,
510                                   enabled_hypervisors=hvlist,
511                                   hvparams=hvparams,
512                                   beparams=beparams,
513                                   nicparams=nicparams,
514                                   candidate_pool_size=opts.candidate_pool_size)
515   SubmitOpCode(op)
516   return 0
517
518
519 def QueueOps(opts, args):
520   """Queue operations.
521
522   @param opts: the command line options selected by the user
523   @type args: list
524   @param args: should contain only one element, the subcommand
525   @rtype: int
526   @return: the desired exit code
527
528   """
529   command = args[0]
530   client = GetClient()
531   if command in ("drain", "undrain"):
532     drain_flag = command == "drain"
533     client.SetQueueDrainFlag(drain_flag)
534   elif command == "info":
535     result = client.QueryConfigValues(["drain_flag"])
536     if result[0]:
537       val = "set"
538     else:
539       val = "unset"
540     ToStdout("The drain flag is %s" % val)
541   else:
542     raise errors.OpPrereqError("Command '%s' is not valid." % command)
543
544   return 0
545
546
547 def _ShowWatcherPause(until):
548   if until is None or until < time.time():
549     ToStdout("The watcher is not paused.")
550   else:
551     ToStdout("The watcher is paused until %s.", time.ctime(until))
552
553
554 def WatcherOps(opts, args):
555   """Watcher operations.
556
557   @param opts: the command line options selected by the user
558   @type args: list
559   @param args: should contain only one element, the subcommand
560   @rtype: int
561   @return: the desired exit code
562
563   """
564   command = args[0]
565   client = GetClient()
566
567   if command == "continue":
568     client.SetWatcherPause(None)
569     ToStdout("The watcher is no longer paused.")
570
571   elif command == "pause":
572     if len(args) < 2:
573       raise errors.OpPrereqError("Missing pause duration")
574
575     result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
576     _ShowWatcherPause(result)
577
578   elif command == "info":
579     result = client.QueryConfigValues(["watcher_pause"])
580     _ShowWatcherPause(result)
581
582   else:
583     raise errors.OpPrereqError("Command '%s' is not valid." % command)
584
585   return 0
586
587
588 # this is an option common to more than one command, so we declare
589 # it here and reuse it
590 node_option = cli_option("-n", "--node", action="append", dest="nodes",
591                          help="Node to copy to (if not given, all nodes),"
592                               " can be given multiple times",
593                          metavar="<node>", default=[])
594
595 commands = {
596   'init': (InitCluster, [ArgHost(min=1, max=1)],
597            [DEBUG_OPT,
598             cli_option("-s", "--secondary-ip", dest="secondary_ip",
599                        help="Specify the secondary ip for this node;"
600                        " if given, the entire cluster must have secondary"
601                        " addresses",
602                        metavar="ADDRESS", default=None),
603             cli_option("-m", "--mac-prefix", dest="mac_prefix",
604                        help="Specify the mac prefix for the instance IP"
605                        " addresses, in the format XX:XX:XX",
606                        metavar="PREFIX",
607                        default=constants.DEFAULT_MAC_PREFIX,),
608             cli_option("-g", "--vg-name", dest="vg_name",
609                        help="Specify the volume group name "
610                        " (cluster-wide) for disk allocation [xenvg]",
611                        metavar="VG",
612                        default=None,),
613             cli_option("--master-netdev", dest="master_netdev",
614                        help="Specify the node interface (cluster-wide)"
615                          " on which the master IP address will be added "
616                          " [%s]" % constants.DEFAULT_BRIDGE,
617                        metavar="NETDEV",
618                        default=constants.DEFAULT_BRIDGE,),
619             cli_option("--file-storage-dir", dest="file_storage_dir",
620                        help="Specify the default directory (cluster-wide)"
621                             " for storing the file-based disks [%s]" %
622                             constants.DEFAULT_FILE_STORAGE_DIR,
623                        metavar="DIR",
624                        default=constants.DEFAULT_FILE_STORAGE_DIR,),
625             cli_option("--no-lvm-storage", dest="lvm_storage",
626                        help="No support for lvm based instances"
627                             " (cluster-wide)",
628                        action="store_false", default=True,),
629             cli_option("--no-etc-hosts", dest="modify_etc_hosts",
630                        help="Don't modify /etc/hosts"
631                             " (cluster-wide)",
632                        action="store_false", default=True,),
633             cli_option("--enabled-hypervisors", dest="enabled_hypervisors",
634                        help="Comma-separated list of hypervisors",
635                        type="string",
636                        default=constants.DEFAULT_ENABLED_HYPERVISOR),
637             cli_option("-H", "--hypervisor-parameters", dest="hvparams",
638                        help="Hypervisor and hypervisor options, in the format"
639                             " hypervisor:option=value,option=value,...",
640                        default=[],
641                        action="append",
642                        type="identkeyval"),
643             cli_option("-B", "--backend-parameters", dest="beparams",
644                        type="keyval", default={},
645                        help="Backend parameters"),
646             cli_option("-N", "--nic-parameters", dest="nicparams",
647                        type="keyval", default={},
648                        help="NIC parameters"),
649             cli_option("-C", "--candidate-pool-size",
650                        default=constants.MASTER_POOL_SIZE_DEFAULT,
651                        help="Set the candidate pool size",
652                        dest="candidate_pool_size", type="int"),
653             ],
654            "[opts...] <cluster_name>",
655            "Initialises a new cluster configuration"),
656   'destroy': (DestroyCluster, ARGS_NONE,
657               [DEBUG_OPT,
658                cli_option("--yes-do-it", dest="yes_do_it",
659                           help="Destroy cluster",
660                           action="store_true"),
661               ],
662               "", "Destroy cluster"),
663   'rename': (RenameCluster, [ArgHost(min=1, max=1)],
664              [DEBUG_OPT, FORCE_OPT],
665              "<new_name>",
666              "Renames the cluster"),
667   'redist-conf': (RedistributeConfig, ARGS_NONE, [DEBUG_OPT, SUBMIT_OPT],
668                   "",
669                   "Forces a push of the configuration file and ssconf files"
670                   " to the nodes in the cluster"),
671   'verify': (VerifyCluster, ARGS_NONE,
672              [DEBUG_OPT, VERBOSE_OPT,
673               cli_option("--error-codes", dest="error_codes",
674                          help="Enable parseable error messages",
675                          action="store_true", default=False),
676               cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
677                          help="Skip N+1 memory redundancy tests",
678                          action="store_true", default=False),
679               ],
680              "", "Does a check on the cluster configuration"),
681   'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
682                    "", "Does a check on the cluster disk status"),
683   'repair-disk-sizes': (RepairDiskSizes, ARGS_MANY_INSTANCES, [DEBUG_OPT],
684                    "", "Updates mismatches in recorded disk sizes"),
685   'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT,
686                      cli_option("--no-voting", dest="no_voting",
687                                 help="Skip node agreement check (dangerous)",
688                                 action="store_true",
689                                 default=False,),
690                      ],
691                      "", "Makes the current node the master"),
692   'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
693               "", "Shows the cluster version"),
694   'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
695                 "", "Shows the cluster master"),
696   'copyfile': (ClusterCopyFile, [ArgFile(min=1, max=1)],
697                [DEBUG_OPT, node_option],
698                "[-n node...] <filename>",
699                "Copies a file to all (or only some) nodes"),
700   'command': (RunClusterCommand, [ArgCommand(min=1)], [DEBUG_OPT, node_option],
701               "[-n node...] <command>",
702               "Runs a command on all (or only some) nodes"),
703   'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
704            "", "Show cluster configuration"),
705   'list-tags': (ListTags, ARGS_NONE,
706                 [DEBUG_OPT], "", "List the tags of the cluster"),
707   'add-tags': (AddTags, [ArgUnknown()], [DEBUG_OPT, TAG_SRC_OPT],
708                "tag...", "Add tags to the cluster"),
709   'remove-tags': (RemoveTags, [ArgUnknown()], [DEBUG_OPT, TAG_SRC_OPT],
710                   "tag...", "Remove tags from the cluster"),
711   'search-tags': (SearchTags, [ArgUnknown(min=1, max=1)],
712                   [DEBUG_OPT], "", "Searches the tags on all objects on"
713                   " the cluster for a given pattern (regex)"),
714   'queue': (QueueOps,
715             [ArgChoice(min=1, max=1, choices=["drain", "undrain", "info"])],
716             [DEBUG_OPT],
717             "drain|undrain|info", "Change queue properties"),
718   'watcher': (WatcherOps,
719               [ArgChoice(min=1, max=1,
720                          choices=["pause", "continue", "info"]),
721                ArgSuggest(min=0, max=1, choices=["30m", "1h", "4h"])],
722               [DEBUG_OPT],
723               "{pause <timespec>|continue|info}", "Change watcher properties"),
724   'modify': (SetClusterParams, ARGS_NONE,
725              [DEBUG_OPT,
726               cli_option("-g", "--vg-name", dest="vg_name",
727                          help="Specify the volume group name "
728                          " (cluster-wide) for disk allocation "
729                          "and enable lvm based storage",
730                          metavar="VG",),
731               cli_option("--no-lvm-storage", dest="lvm_storage",
732                          help="Disable support for lvm based instances"
733                               " (cluster-wide)",
734                          action="store_false", default=True,),
735               cli_option("--enabled-hypervisors", dest="enabled_hypervisors",
736                          help="Comma-separated list of hypervisors",
737                          type="string", default=None),
738               cli_option("-H", "--hypervisor-parameters", dest="hvparams",
739                          help="Hypervisor and hypervisor options, in the"
740                          " format"
741                          " hypervisor:option=value,option=value,...",
742                          default=[],
743                          action="append",
744                          type="identkeyval"),
745               cli_option("-B", "--backend-parameters", dest="beparams",
746                          type="keyval", default={},
747                          help="Backend parameters"),
748               cli_option("-N", "--nic-parameters", dest="nicparams",
749                          type="keyval", default={},
750                          help="NIC parameters"),
751               cli_option("-C", "--candidate-pool-size", default=None,
752                          help="Set the candidate pool size",
753                          dest="candidate_pool_size", type="int"),
754               ],
755              "[opts...]",
756              "Alters the parameters of the cluster"),
757   }
758
759 if __name__ == '__main__':
760   sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))