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