4 # Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
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.
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.
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
21 """Cluster related commands"""
23 # pylint: disable-msg=W0401,W0613,W0614,C0103
24 # W0401: Wildcard import ganeti.cli
25 # W0613: Unused argument, since all functions follow the same API
26 # W0614: Unused import %s from wildcard import (since we need cli)
27 # C0103: Invalid name gnt-cluster
33 from ganeti.cli import *
34 from ganeti import opcodes
35 from ganeti import constants
36 from ganeti import errors
37 from ganeti import utils
38 from ganeti import bootstrap
39 from ganeti import ssh
40 from ganeti import objects
41 from ganeti import uidpool
42 from ganeti import compat
46 def InitCluster(opts, args):
47 """Initialize the cluster.
49 @param opts: the command line options selected by the user
51 @param args: should contain only one element, the desired
54 @return: the desired exit code
57 if not opts.lvm_storage and opts.vg_name:
58 ToStderr("Options --no-lvm-storage and --vg-name conflict.")
61 vg_name = opts.vg_name
62 if opts.lvm_storage and not opts.vg_name:
63 vg_name = constants.DEFAULT_VG
65 if not opts.drbd_storage and opts.drbd_helper:
66 ToStderr("Options --no-drbd-storage and --drbd-usermode-helper conflict.")
69 drbd_helper = opts.drbd_helper
70 if opts.drbd_storage and not opts.drbd_helper:
71 drbd_helper = constants.DEFAULT_DRBD_HELPER
73 master_netdev = opts.master_netdev
74 if master_netdev is None:
75 master_netdev = constants.DEFAULT_BRIDGE
77 hvlist = opts.enabled_hypervisors
79 hvlist = constants.DEFAULT_ENABLED_HYPERVISOR
80 hvlist = hvlist.split(",")
82 hvparams = dict(opts.hvparams)
83 beparams = opts.beparams
84 nicparams = opts.nicparams
86 # prepare beparams dict
87 beparams = objects.FillDict(constants.BEC_DEFAULTS, beparams)
88 utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
90 # prepare nicparams dict
91 nicparams = objects.FillDict(constants.NICC_DEFAULTS, nicparams)
92 utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
94 # prepare ndparams dict
95 if opts.ndparams is None:
96 ndparams = dict(constants.NDC_DEFAULTS)
98 ndparams = objects.FillDict(constants.NDC_DEFAULTS, opts.ndparams)
99 utils.ForceDictType(ndparams, constants.NDS_PARAMETER_TYPES)
101 # prepare hvparams dict
102 for hv in constants.HYPER_TYPES:
103 if hv not in hvparams:
105 hvparams[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], hvparams[hv])
106 utils.ForceDictType(hvparams[hv], constants.HVS_PARAMETER_TYPES)
108 if opts.candidate_pool_size is None:
109 opts.candidate_pool_size = constants.MASTER_POOL_SIZE_DEFAULT
111 if opts.mac_prefix is None:
112 opts.mac_prefix = constants.DEFAULT_MAC_PREFIX
114 uid_pool = opts.uid_pool
115 if uid_pool is not None:
116 uid_pool = uidpool.ParseUidPool(uid_pool)
118 if opts.prealloc_wipe_disks is None:
119 opts.prealloc_wipe_disks = False
122 primary_ip_version = int(opts.primary_ip_version)
123 except (ValueError, TypeError), err:
124 ToStderr("Invalid primary ip version value: %s" % str(err))
127 bootstrap.InitCluster(cluster_name=args[0],
128 secondary_ip=opts.secondary_ip,
130 mac_prefix=opts.mac_prefix,
131 master_netdev=master_netdev,
132 file_storage_dir=opts.file_storage_dir,
133 enabled_hypervisors=hvlist,
138 candidate_pool_size=opts.candidate_pool_size,
139 modify_etc_hosts=opts.modify_etc_hosts,
140 modify_ssh_setup=opts.modify_ssh_setup,
141 maintain_node_health=opts.maintain_node_health,
142 drbd_helper=drbd_helper,
144 default_iallocator=opts.default_iallocator,
145 primary_ip_version=primary_ip_version,
146 prealloc_wipe_disks=opts.prealloc_wipe_disks,
148 op = opcodes.OpClusterPostInit()
149 SubmitOpCode(op, opts=opts)
154 def DestroyCluster(opts, args):
155 """Destroy the cluster.
157 @param opts: the command line options selected by the user
159 @param args: should be an empty list
161 @return: the desired exit code
164 if not opts.yes_do_it:
165 ToStderr("Destroying a cluster is irreversible. If you really want"
166 " destroy this cluster, supply the --yes-do-it option.")
169 op = opcodes.OpClusterDestroy()
170 master = SubmitOpCode(op, opts=opts)
171 # if we reached this, the opcode didn't fail; we can proceed to
172 # shutdown all the daemons
173 bootstrap.FinalizeClusterDestroy(master)
177 def RenameCluster(opts, args):
178 """Rename the cluster.
180 @param opts: the command line options selected by the user
182 @param args: should contain only one element, the new cluster name
184 @return: the desired exit code
189 (cluster_name, ) = cl.QueryConfigValues(["cluster_name"])
193 usertext = ("This will rename the cluster from '%s' to '%s'. If you are"
194 " connected over the network to the cluster name, the"
195 " operation is very dangerous as the IP address will be"
196 " removed from the node and the change may not go through."
197 " Continue?") % (cluster_name, new_name)
198 if not AskUser(usertext):
201 op = opcodes.OpClusterRename(name=new_name)
202 result = SubmitOpCode(op, opts=opts, cl=cl)
205 ToStdout("Cluster renamed from '%s' to '%s'", cluster_name, result)
210 def RedistributeConfig(opts, args):
211 """Forces push of the cluster configuration.
213 @param opts: the command line options selected by the user
215 @param args: empty list
217 @return: the desired exit code
220 op = opcodes.OpClusterRedistConf()
221 SubmitOrSend(op, opts)
225 def ShowClusterVersion(opts, args):
226 """Write version of ganeti software to the standard output.
228 @param opts: the command line options selected by the user
230 @param args: should be an empty list
232 @return: the desired exit code
236 result = cl.QueryClusterInfo()
237 ToStdout("Software version: %s", result["software_version"])
238 ToStdout("Internode protocol: %s", result["protocol_version"])
239 ToStdout("Configuration format: %s", result["config_version"])
240 ToStdout("OS api version: %s", result["os_api_version"])
241 ToStdout("Export interface: %s", result["export_version"])
245 def ShowClusterMaster(opts, args):
246 """Write name of master node to the standard output.
248 @param opts: the command line options selected by the user
250 @param args: should be an empty list
252 @return: the desired exit code
255 master = bootstrap.GetMaster()
260 def _PrintGroupedParams(paramsdict, level=1, roman=False):
261 """Print Grouped parameters (be, nic, disk) by group.
263 @type paramsdict: dict of dicts
264 @param paramsdict: {group: {param: value, ...}, ...}
266 @param level: Level of indention
270 for item, val in sorted(paramsdict.items()):
271 if isinstance(val, dict):
272 ToStdout("%s- %s:", indent, item)
273 _PrintGroupedParams(val, level=level + 1, roman=roman)
274 elif roman and isinstance(val, int):
275 ToStdout("%s %s: %s", indent, item, compat.TryToRoman(val))
277 ToStdout("%s %s: %s", indent, item, val)
280 def ShowClusterConfig(opts, args):
281 """Shows cluster information.
283 @param opts: the command line options selected by the user
285 @param args: should be an empty list
287 @return: the desired exit code
291 result = cl.QueryClusterInfo()
293 ToStdout("Cluster name: %s", result["name"])
294 ToStdout("Cluster UUID: %s", result["uuid"])
296 ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
297 ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))
299 ToStdout("Master node: %s", result["master"])
301 ToStdout("Architecture (this node): %s (%s)",
302 result["architecture"][0], result["architecture"][1])
305 tags = utils.CommaJoin(utils.NiceSort(result["tags"]))
309 ToStdout("Tags: %s", tags)
311 ToStdout("Default hypervisor: %s", result["default_hypervisor"])
312 ToStdout("Enabled hypervisors: %s",
313 utils.CommaJoin(result["enabled_hypervisors"]))
315 ToStdout("Hypervisor parameters:")
316 _PrintGroupedParams(result["hvparams"])
318 ToStdout("OS-specific hypervisor parameters:")
319 _PrintGroupedParams(result["os_hvp"])
321 ToStdout("OS parameters:")
322 _PrintGroupedParams(result["osparams"])
324 ToStdout("Hidden OSes: %s", utils.CommaJoin(result["hidden_os"]))
325 ToStdout("Blacklisted OSes: %s", utils.CommaJoin(result["blacklisted_os"]))
327 ToStdout("Cluster parameters:")
328 ToStdout(" - candidate pool size: %s",
329 compat.TryToRoman(result["candidate_pool_size"],
330 convert=opts.roman_integers))
331 ToStdout(" - master netdev: %s", result["master_netdev"])
332 ToStdout(" - lvm volume group: %s", result["volume_group_name"])
333 if result["reserved_lvs"]:
334 reserved_lvs = utils.CommaJoin(result["reserved_lvs"])
336 reserved_lvs = "(none)"
337 ToStdout(" - lvm reserved volumes: %s", reserved_lvs)
338 ToStdout(" - drbd usermode helper: %s", result["drbd_usermode_helper"])
339 ToStdout(" - file storage path: %s", result["file_storage_dir"])
340 ToStdout(" - maintenance of node health: %s",
341 result["maintain_node_health"])
342 ToStdout(" - uid pool: %s",
343 uidpool.FormatUidPool(result["uid_pool"],
344 roman=opts.roman_integers))
345 ToStdout(" - default instance allocator: %s", result["default_iallocator"])
346 ToStdout(" - primary ip version: %d", result["primary_ip_version"])
347 ToStdout(" - preallocation wipe disks: %s", result["prealloc_wipe_disks"])
348 ToStdout(" - OS search path: %s", utils.CommaJoin(constants.OS_SEARCH_PATH))
350 ToStdout("Default node parameters:")
351 _PrintGroupedParams(result["ndparams"], roman=opts.roman_integers)
353 ToStdout("Default instance parameters:")
354 _PrintGroupedParams(result["beparams"], roman=opts.roman_integers)
356 ToStdout("Default nic parameters:")
357 _PrintGroupedParams(result["nicparams"], roman=opts.roman_integers)
362 def ClusterCopyFile(opts, args):
363 """Copy a file from master to some nodes.
365 @param opts: the command line options selected by the user
367 @param args: should contain only one element, the path of
368 the file to be copied
370 @return: the desired exit code
374 if not os.path.exists(filename):
375 raise errors.OpPrereqError("No such filename '%s'" % filename,
380 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
382 results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
383 secondary_ips=opts.use_replication_network)
385 srun = ssh.SshRunner(cluster_name=cluster_name)
387 if not srun.CopyFileToNode(node, filename):
388 ToStderr("Copy of file %s to node %s failed", filename, node)
393 def RunClusterCommand(opts, args):
394 """Run a command on some nodes.
396 @param opts: the command line options selected by the user
398 @param args: should contain the command to be run and its arguments
400 @return: the desired exit code
405 command = " ".join(args)
407 nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
409 cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
412 srun = ssh.SshRunner(cluster_name=cluster_name)
414 # Make sure master node is at list end
415 if master_node in nodes:
416 nodes.remove(master_node)
417 nodes.append(master_node)
420 result = srun.Run(name, "root", command)
421 ToStdout("------------------------------------------------")
422 ToStdout("node: %s", name)
423 ToStdout("%s", result.output)
424 ToStdout("return code = %s", result.exit_code)
429 def VerifyCluster(opts, args):
430 """Verify integrity of cluster, performing various test on nodes.
432 @param opts: the command line options selected by the user
434 @param args: should be an empty list
436 @return: the desired exit code
440 if opts.skip_nplusone_mem:
441 skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
442 op = opcodes.OpClusterVerify(skip_checks=skip_checks,
443 verbose=opts.verbose,
444 error_codes=opts.error_codes,
445 debug_simulate_errors=opts.simulate_errors)
446 if SubmitOpCode(op, opts=opts):
452 def VerifyDisks(opts, args):
453 """Verify integrity of cluster disks.
455 @param opts: the command line options selected by the user
457 @param args: should be an empty list
459 @return: the desired exit code
464 op = opcodes.OpClusterVerifyDisks()
465 result = SubmitOpCode(op, opts=opts, cl=cl)
466 if not isinstance(result, (list, tuple)) or len(result) != 3:
467 raise errors.ProgrammerError("Unknown result type for OpClusterVerifyDisks")
469 bad_nodes, instances, missing = result
471 retcode = constants.EXIT_SUCCESS
474 for node, text in bad_nodes.items():
475 ToStdout("Error gathering data on node %s: %s",
476 node, utils.SafeEncode(text[-400:]))
478 ToStdout("You need to fix these nodes first before fixing instances")
481 for iname in instances:
484 op = opcodes.OpInstanceActivateDisks(instance_name=iname)
486 ToStdout("Activating disks for instance '%s'", iname)
487 SubmitOpCode(op, opts=opts, cl=cl)
488 except errors.GenericError, err:
489 nret, msg = FormatError(err)
491 ToStderr("Error activating disks for instance %s: %s", iname, msg)
494 for iname, ival in missing.iteritems():
495 all_missing = compat.all(x[0] in bad_nodes for x in ival)
497 ToStdout("Instance %s cannot be verified as it lives on"
498 " broken nodes", iname)
500 ToStdout("Instance %s has missing logical volumes:", iname)
502 for node, vol in ival:
503 if node in bad_nodes:
504 ToStdout("\tbroken node %s /dev/%s", node, vol)
506 ToStdout("\t%s /dev/%s", node, vol)
508 ToStdout("You need to run replace or recreate disks for all the above"
509 " instances, if this message persist after fixing nodes.")
515 def RepairDiskSizes(opts, args):
516 """Verify sizes of cluster disks.
518 @param opts: the command line options selected by the user
520 @param args: optional list of instances to restrict check to
522 @return: the desired exit code
525 op = opcodes.OpClusterRepairDiskSizes(instances=args)
526 SubmitOpCode(op, opts=opts)
530 def MasterFailover(opts, args):
531 """Failover the master node.
533 This command, when run on a non-master node, will cause the current
534 master to cease being master, and the non-master to become new
537 @param opts: the command line options selected by the user
539 @param args: should be an empty list
541 @return: the desired exit code
545 usertext = ("This will perform the failover even if most other nodes"
546 " are down, or if this node is outdated. This is dangerous"
547 " as it can lead to a non-consistent cluster. Check the"
548 " gnt-cluster(8) man page before proceeding. Continue?")
549 if not AskUser(usertext):
552 return bootstrap.MasterFailover(no_voting=opts.no_voting)
555 def MasterPing(opts, args):
556 """Checks if the master is alive.
558 @param opts: the command line options selected by the user
560 @param args: should be an empty list
562 @return: the desired exit code
567 cl.QueryClusterInfo()
569 except Exception: # pylint: disable-msg=W0703
573 def SearchTags(opts, args):
574 """Searches the tags on all the cluster.
576 @param opts: the command line options selected by the user
578 @param args: should contain only one element, the tag pattern
580 @return: the desired exit code
583 op = opcodes.OpTagsSearch(pattern=args[0])
584 result = SubmitOpCode(op, opts=opts)
587 result = list(result)
589 for path, tag in result:
590 ToStdout("%s %s", path, tag)
593 def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
594 new_confd_hmac_key, new_cds, cds_filename,
596 """Renews cluster certificates, keys and secrets.
598 @type new_cluster_cert: bool
599 @param new_cluster_cert: Whether to generate a new cluster certificate
600 @type new_rapi_cert: bool
601 @param new_rapi_cert: Whether to generate a new RAPI certificate
602 @type rapi_cert_filename: string
603 @param rapi_cert_filename: Path to file containing new RAPI certificate
604 @type new_confd_hmac_key: bool
605 @param new_confd_hmac_key: Whether to generate a new HMAC key
607 @param new_cds: Whether to generate a new cluster domain secret
608 @type cds_filename: string
609 @param cds_filename: Path to file containing new cluster domain secret
611 @param force: Whether to ask user for confirmation
614 if new_rapi_cert and rapi_cert_filename:
615 ToStderr("Only one of the --new-rapi-certficate and --rapi-certificate"
616 " options can be specified at the same time.")
619 if new_cds and cds_filename:
620 ToStderr("Only one of the --new-cluster-domain-secret and"
621 " --cluster-domain-secret options can be specified at"
625 if rapi_cert_filename:
626 # Read and verify new certificate
628 rapi_cert_pem = utils.ReadFile(rapi_cert_filename)
630 OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
632 except Exception, err: # pylint: disable-msg=W0703
633 ToStderr("Can't load new RAPI certificate from %s: %s" %
634 (rapi_cert_filename, str(err)))
638 OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, rapi_cert_pem)
639 except Exception, err: # pylint: disable-msg=W0703
640 ToStderr("Can't load new RAPI private key from %s: %s" %
641 (rapi_cert_filename, str(err)))
649 cds = utils.ReadFile(cds_filename)
650 except Exception, err: # pylint: disable-msg=W0703
651 ToStderr("Can't load new cluster domain secret from %s: %s" %
652 (cds_filename, str(err)))
658 usertext = ("This requires all daemons on all nodes to be restarted and"
659 " may take some time. Continue?")
660 if not AskUser(usertext):
663 def _RenewCryptoInner(ctx):
664 ctx.feedback_fn("Updating certificates and keys")
665 bootstrap.GenerateClusterCrypto(new_cluster_cert, new_rapi_cert,
668 rapi_cert_pem=rapi_cert_pem,
674 files_to_copy.append(constants.NODED_CERT_FILE)
676 if new_rapi_cert or rapi_cert_pem:
677 files_to_copy.append(constants.RAPI_CERT_FILE)
679 if new_confd_hmac_key:
680 files_to_copy.append(constants.CONFD_HMAC_KEY)
683 files_to_copy.append(constants.CLUSTER_DOMAIN_SECRET_FILE)
686 for node_name in ctx.nonmaster_nodes:
687 ctx.feedback_fn("Copying %s to %s" %
688 (", ".join(files_to_copy), node_name))
689 for file_name in files_to_copy:
690 ctx.ssh.CopyFileToNode(node_name, file_name)
692 RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
694 ToStdout("All requested certificates and keys have been replaced."
695 " Running \"gnt-cluster verify\" now is recommended.")
700 def RenewCrypto(opts, args):
701 """Renews cluster certificates, keys and secrets.
704 return _RenewCrypto(opts.new_cluster_cert,
707 opts.new_confd_hmac_key,
708 opts.new_cluster_domain_secret,
709 opts.cluster_domain_secret,
713 def SetClusterParams(opts, args):
714 """Modify the cluster.
716 @param opts: the command line options selected by the user
718 @param args: should be an empty list
720 @return: the desired exit code
723 if not (not opts.lvm_storage or opts.vg_name or
724 not opts.drbd_storage or opts.drbd_helper or
725 opts.enabled_hypervisors or opts.hvparams or
726 opts.beparams or opts.nicparams or opts.ndparams or
727 opts.candidate_pool_size is not None or
728 opts.uid_pool is not None or
729 opts.maintain_node_health is not None or
730 opts.add_uids is not None or
731 opts.remove_uids is not None or
732 opts.default_iallocator is not None or
733 opts.reserved_lvs is not None or
734 opts.master_netdev is not None or
735 opts.prealloc_wipe_disks is not None):
736 ToStderr("Please give at least one of the parameters.")
739 vg_name = opts.vg_name
740 if not opts.lvm_storage and opts.vg_name:
741 ToStderr("Options --no-lvm-storage and --vg-name conflict.")
744 if not opts.lvm_storage:
747 drbd_helper = opts.drbd_helper
748 if not opts.drbd_storage and opts.drbd_helper:
749 ToStderr("Options --no-drbd-storage and --drbd-usermode-helper conflict.")
752 if not opts.drbd_storage:
755 hvlist = opts.enabled_hypervisors
756 if hvlist is not None:
757 hvlist = hvlist.split(",")
759 # a list of (name, dict) we can pass directly to dict() (or [])
760 hvparams = dict(opts.hvparams)
761 for hv_params in hvparams.values():
762 utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
764 beparams = opts.beparams
765 utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
767 nicparams = opts.nicparams
768 utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
770 ndparams = opts.ndparams
771 if ndparams is not None:
772 utils.ForceDictType(ndparams, constants.NDS_PARAMETER_TYPES)
774 mnh = opts.maintain_node_health
776 uid_pool = opts.uid_pool
777 if uid_pool is not None:
778 uid_pool = uidpool.ParseUidPool(uid_pool)
780 add_uids = opts.add_uids
781 if add_uids is not None:
782 add_uids = uidpool.ParseUidPool(add_uids)
784 remove_uids = opts.remove_uids
785 if remove_uids is not None:
786 remove_uids = uidpool.ParseUidPool(remove_uids)
788 if opts.reserved_lvs is not None:
789 if opts.reserved_lvs == "":
790 opts.reserved_lvs = []
792 opts.reserved_lvs = utils.UnescapeAndSplit(opts.reserved_lvs, sep=",")
794 op = opcodes.OpClusterSetParams(vg_name=vg_name,
795 drbd_helper=drbd_helper,
796 enabled_hypervisors=hvlist,
802 candidate_pool_size=opts.candidate_pool_size,
803 maintain_node_health=mnh,
806 remove_uids=remove_uids,
807 default_iallocator=opts.default_iallocator,
808 prealloc_wipe_disks=opts.prealloc_wipe_disks,
809 master_netdev=opts.master_netdev,
810 reserved_lvs=opts.reserved_lvs)
811 SubmitOpCode(op, opts=opts)
815 def QueueOps(opts, args):
818 @param opts: the command line options selected by the user
820 @param args: should contain only one element, the subcommand
822 @return: the desired exit code
827 if command in ("drain", "undrain"):
828 drain_flag = command == "drain"
829 client.SetQueueDrainFlag(drain_flag)
830 elif command == "info":
831 result = client.QueryConfigValues(["drain_flag"])
836 ToStdout("The drain flag is %s" % val)
838 raise errors.OpPrereqError("Command '%s' is not valid." % command,
844 def _ShowWatcherPause(until):
845 if until is None or until < time.time():
846 ToStdout("The watcher is not paused.")
848 ToStdout("The watcher is paused until %s.", time.ctime(until))
851 def WatcherOps(opts, args):
852 """Watcher operations.
854 @param opts: the command line options selected by the user
856 @param args: should contain only one element, the subcommand
858 @return: the desired exit code
864 if command == "continue":
865 client.SetWatcherPause(None)
866 ToStdout("The watcher is no longer paused.")
868 elif command == "pause":
870 raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
872 result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
873 _ShowWatcherPause(result)
875 elif command == "info":
876 result = client.QueryConfigValues(["watcher_pause"])
877 _ShowWatcherPause(result[0])
880 raise errors.OpPrereqError("Command '%s' is not valid." % command,
888 InitCluster, [ArgHost(min=1, max=1)],
889 [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, GLOBAL_FILEDIR_OPT,
890 HVLIST_OPT, MAC_PREFIX_OPT, MASTER_NETDEV_OPT, NIC_PARAMS_OPT,
891 NOLVM_STORAGE_OPT, NOMODIFY_ETCHOSTS_OPT, NOMODIFY_SSH_SETUP_OPT,
892 SECONDARY_IP_OPT, VG_NAME_OPT, MAINTAIN_NODE_HEALTH_OPT,
893 UIDPOOL_OPT, DRBD_HELPER_OPT, NODRBD_STORAGE_OPT,
894 DEFAULT_IALLOCATOR_OPT, PRIMARY_IP_VERSION_OPT, PREALLOC_WIPE_DISKS_OPT,
896 "[opts...] <cluster_name>", "Initialises a new cluster configuration"),
898 DestroyCluster, ARGS_NONE, [YES_DOIT_OPT],
899 "", "Destroy cluster"),
901 RenameCluster, [ArgHost(min=1, max=1)],
902 [FORCE_OPT, DRY_RUN_OPT],
904 "Renames the cluster"),
906 RedistributeConfig, ARGS_NONE, [SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
907 "", "Forces a push of the configuration file and ssconf files"
908 " to the nodes in the cluster"),
910 VerifyCluster, ARGS_NONE,
911 [VERBOSE_OPT, DEBUG_SIMERR_OPT, ERROR_CODES_OPT, NONPLUS1_OPT,
912 DRY_RUN_OPT, PRIORITY_OPT],
913 "", "Does a check on the cluster configuration"),
915 VerifyDisks, ARGS_NONE, [PRIORITY_OPT],
916 "", "Does a check on the cluster disk status"),
917 'repair-disk-sizes': (
918 RepairDiskSizes, ARGS_MANY_INSTANCES, [DRY_RUN_OPT, PRIORITY_OPT],
919 "", "Updates mismatches in recorded disk sizes"),
921 MasterFailover, ARGS_NONE, [NOVOTING_OPT],
922 "", "Makes the current node the master"),
924 MasterPing, ARGS_NONE, [],
925 "", "Checks if the master is alive"),
927 ShowClusterVersion, ARGS_NONE, [],
928 "", "Shows the cluster version"),
930 ShowClusterMaster, ARGS_NONE, [],
931 "", "Shows the cluster master"),
933 ClusterCopyFile, [ArgFile(min=1, max=1)],
934 [NODE_LIST_OPT, USE_REPL_NET_OPT],
935 "[-n node...] <filename>", "Copies a file to all (or only some) nodes"),
937 RunClusterCommand, [ArgCommand(min=1)],
939 "[-n node...] <command>", "Runs a command on all (or only some) nodes"),
941 ShowClusterConfig, ARGS_NONE, [ROMAN_OPT],
942 "[--roman]", "Show cluster configuration"),
944 ListTags, ARGS_NONE, [], "", "List the tags of the cluster"),
946 AddTags, [ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT],
947 "tag...", "Add tags to the cluster"),
949 RemoveTags, [ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT],
950 "tag...", "Remove tags from the cluster"),
952 SearchTags, [ArgUnknown(min=1, max=1)], [PRIORITY_OPT], "",
953 "Searches the tags on all objects on"
954 " the cluster for a given pattern (regex)"),
957 [ArgChoice(min=1, max=1, choices=["drain", "undrain", "info"])],
958 [], "drain|undrain|info", "Change queue properties"),
961 [ArgChoice(min=1, max=1, choices=["pause", "continue", "info"]),
962 ArgSuggest(min=0, max=1, choices=["30m", "1h", "4h"])],
964 "{pause <timespec>|continue|info}", "Change watcher properties"),
966 SetClusterParams, ARGS_NONE,
967 [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, HVLIST_OPT, MASTER_NETDEV_OPT,
968 NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, VG_NAME_OPT, MAINTAIN_NODE_HEALTH_OPT,
969 UIDPOOL_OPT, ADD_UIDS_OPT, REMOVE_UIDS_OPT, DRBD_HELPER_OPT,
970 NODRBD_STORAGE_OPT, DEFAULT_IALLOCATOR_OPT, RESERVED_LVS_OPT,
971 DRY_RUN_OPT, PRIORITY_OPT, PREALLOC_WIPE_DISKS_OPT, NODE_PARAMS_OPT],
973 "Alters the parameters of the cluster"),
975 RenewCrypto, ARGS_NONE,
976 [NEW_CLUSTER_CERT_OPT, NEW_RAPI_CERT_OPT, RAPI_CERT_OPT,
977 NEW_CONFD_HMAC_KEY_OPT, FORCE_OPT,
978 NEW_CLUSTER_DOMAIN_SECRET_OPT, CLUSTER_DOMAIN_SECRET_OPT],
980 "Renews cluster certificates, keys and secrets"),
984 #: dictionary with aliases for commands
986 'masterfailover': 'master-failover',
991 return GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER},