4 # Copyright (C) 2006, 2007, 2010 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("Cluster parameters:")
325 ToStdout(" - candidate pool size: %s",
326 compat.TryToRoman(result["candidate_pool_size"],
327 convert=opts.roman_integers))
328 ToStdout(" - master netdev: %s", result["master_netdev"])
329 ToStdout(" - lvm volume group: %s", result["volume_group_name"])
330 if result["reserved_lvs"]:
331 reserved_lvs = utils.CommaJoin(result["reserved_lvs"])
333 reserved_lvs = "(none)"
334 ToStdout(" - lvm reserved volumes: %s", reserved_lvs)
335 ToStdout(" - drbd usermode helper: %s", result["drbd_usermode_helper"])
336 ToStdout(" - file storage path: %s", result["file_storage_dir"])
337 ToStdout(" - maintenance of node health: %s",
338 result["maintain_node_health"])
339 ToStdout(" - uid pool: %s",
340 uidpool.FormatUidPool(result["uid_pool"],
341 roman=opts.roman_integers))
342 ToStdout(" - default instance allocator: %s", result["default_iallocator"])
343 ToStdout(" - primary ip version: %d", result["primary_ip_version"])
344 ToStdout(" - preallocation wipe disks: %s", result["prealloc_wipe_disks"])
346 ToStdout("Default node parameters:")
347 _PrintGroupedParams(result["ndparams"], roman=opts.roman_integers)
349 ToStdout("Default instance parameters:")
350 _PrintGroupedParams(result["beparams"], roman=opts.roman_integers)
352 ToStdout("Default nic parameters:")
353 _PrintGroupedParams(result["nicparams"], roman=opts.roman_integers)
358 def ClusterCopyFile(opts, args):
359 """Copy a file from master to some nodes.
361 @param opts: the command line options selected by the user
363 @param args: should contain only one element, the path of
364 the file to be copied
366 @return: the desired exit code
370 if not os.path.exists(filename):
371 raise errors.OpPrereqError("No such filename '%s'" % filename,
376 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
378 results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
379 secondary_ips=opts.use_replication_network)
381 srun = ssh.SshRunner(cluster_name=cluster_name)
383 if not srun.CopyFileToNode(node, filename):
384 ToStderr("Copy of file %s to node %s failed", filename, node)
389 def RunClusterCommand(opts, args):
390 """Run a command on some nodes.
392 @param opts: the command line options selected by the user
394 @param args: should contain the command to be run and its arguments
396 @return: the desired exit code
401 command = " ".join(args)
403 nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
405 cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
408 srun = ssh.SshRunner(cluster_name=cluster_name)
410 # Make sure master node is at list end
411 if master_node in nodes:
412 nodes.remove(master_node)
413 nodes.append(master_node)
416 result = srun.Run(name, "root", command)
417 ToStdout("------------------------------------------------")
418 ToStdout("node: %s", name)
419 ToStdout("%s", result.output)
420 ToStdout("return code = %s", result.exit_code)
425 def VerifyCluster(opts, args):
426 """Verify integrity of cluster, performing various test on nodes.
428 @param opts: the command line options selected by the user
430 @param args: should be an empty list
432 @return: the desired exit code
436 if opts.skip_nplusone_mem:
437 skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
438 op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
439 verbose=opts.verbose,
440 error_codes=opts.error_codes,
441 debug_simulate_errors=opts.simulate_errors)
442 if SubmitOpCode(op, opts=opts):
448 def VerifyDisks(opts, args):
449 """Verify integrity of cluster disks.
451 @param opts: the command line options selected by the user
453 @param args: should be an empty list
455 @return: the desired exit code
460 op = opcodes.OpVerifyDisks()
461 result = SubmitOpCode(op, opts=opts, cl=cl)
462 if not isinstance(result, (list, tuple)) or len(result) != 3:
463 raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
465 bad_nodes, instances, missing = result
467 retcode = constants.EXIT_SUCCESS
470 for node, text in bad_nodes.items():
471 ToStdout("Error gathering data on node %s: %s",
472 node, utils.SafeEncode(text[-400:]))
474 ToStdout("You need to fix these nodes first before fixing instances")
477 for iname in instances:
480 op = opcodes.OpActivateInstanceDisks(instance_name=iname)
482 ToStdout("Activating disks for instance '%s'", iname)
483 SubmitOpCode(op, opts=opts, cl=cl)
484 except errors.GenericError, err:
485 nret, msg = FormatError(err)
487 ToStderr("Error activating disks for instance %s: %s", iname, msg)
490 (vg_name, ) = cl.QueryConfigValues(["volume_group_name"])
492 for iname, ival in missing.iteritems():
493 all_missing = compat.all(x[0] in bad_nodes for x in ival)
495 ToStdout("Instance %s cannot be verified as it lives on"
496 " broken nodes", iname)
498 ToStdout("Instance %s has missing logical volumes:", iname)
500 for node, vol in ival:
501 if node in bad_nodes:
502 ToStdout("\tbroken node %s /dev/%s/%s", node, vg_name, vol)
504 ToStdout("\t%s /dev/%s/%s", node, vg_name, vol)
506 ToStdout("You need to run replace_disks for all the above"
507 " instances, if this message persist after fixing nodes.")
513 def RepairDiskSizes(opts, args):
514 """Verify sizes of cluster disks.
516 @param opts: the command line options selected by the user
518 @param args: optional list of instances to restrict check to
520 @return: the desired exit code
523 op = opcodes.OpRepairDiskSizes(instances=args)
524 SubmitOpCode(op, opts=opts)
528 def MasterFailover(opts, args):
529 """Failover the master node.
531 This command, when run on a non-master node, will cause the current
532 master to cease being master, and the non-master to become new
535 @param opts: the command line options selected by the user
537 @param args: should be an empty list
539 @return: the desired exit code
543 usertext = ("This will perform the failover even if most other nodes"
544 " are down, or if this node is outdated. This is dangerous"
545 " as it can lead to a non-consistent cluster. Check the"
546 " gnt-cluster(8) man page before proceeding. Continue?")
547 if not AskUser(usertext):
550 return bootstrap.MasterFailover(no_voting=opts.no_voting)
553 def MasterPing(opts, args):
554 """Checks if the master is alive.
556 @param opts: the command line options selected by the user
558 @param args: should be an empty list
560 @return: the desired exit code
565 cl.QueryClusterInfo()
567 except Exception: # pylint: disable-msg=W0703
571 def SearchTags(opts, args):
572 """Searches the tags on all the cluster.
574 @param opts: the command line options selected by the user
576 @param args: should contain only one element, the tag pattern
578 @return: the desired exit code
581 op = opcodes.OpSearchTags(pattern=args[0])
582 result = SubmitOpCode(op, opts=opts)
585 result = list(result)
587 for path, tag in result:
588 ToStdout("%s %s", path, tag)
591 def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
592 new_confd_hmac_key, new_cds, cds_filename,
594 """Renews cluster certificates, keys and secrets.
596 @type new_cluster_cert: bool
597 @param new_cluster_cert: Whether to generate a new cluster certificate
598 @type new_rapi_cert: bool
599 @param new_rapi_cert: Whether to generate a new RAPI certificate
600 @type rapi_cert_filename: string
601 @param rapi_cert_filename: Path to file containing new RAPI certificate
602 @type new_confd_hmac_key: bool
603 @param new_confd_hmac_key: Whether to generate a new HMAC key
605 @param new_cds: Whether to generate a new cluster domain secret
606 @type cds_filename: string
607 @param cds_filename: Path to file containing new cluster domain secret
609 @param force: Whether to ask user for confirmation
612 if new_rapi_cert and rapi_cert_filename:
613 ToStderr("Only one of the --new-rapi-certficate and --rapi-certificate"
614 " options can be specified at the same time.")
617 if new_cds and cds_filename:
618 ToStderr("Only one of the --new-cluster-domain-secret and"
619 " --cluster-domain-secret options can be specified at"
623 if rapi_cert_filename:
624 # Read and verify new certificate
626 rapi_cert_pem = utils.ReadFile(rapi_cert_filename)
628 OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
630 except Exception, err: # pylint: disable-msg=W0703
631 ToStderr("Can't load new RAPI certificate from %s: %s" %
632 (rapi_cert_filename, str(err)))
636 OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, rapi_cert_pem)
637 except Exception, err: # pylint: disable-msg=W0703
638 ToStderr("Can't load new RAPI private key from %s: %s" %
639 (rapi_cert_filename, str(err)))
647 cds = utils.ReadFile(cds_filename)
648 except Exception, err: # pylint: disable-msg=W0703
649 ToStderr("Can't load new cluster domain secret from %s: %s" %
650 (cds_filename, str(err)))
656 usertext = ("This requires all daemons on all nodes to be restarted and"
657 " may take some time. Continue?")
658 if not AskUser(usertext):
661 def _RenewCryptoInner(ctx):
662 ctx.feedback_fn("Updating certificates and keys")
663 bootstrap.GenerateClusterCrypto(new_cluster_cert, new_rapi_cert,
666 rapi_cert_pem=rapi_cert_pem,
672 files_to_copy.append(constants.NODED_CERT_FILE)
674 if new_rapi_cert or rapi_cert_pem:
675 files_to_copy.append(constants.RAPI_CERT_FILE)
677 if new_confd_hmac_key:
678 files_to_copy.append(constants.CONFD_HMAC_KEY)
681 files_to_copy.append(constants.CLUSTER_DOMAIN_SECRET_FILE)
684 for node_name in ctx.nonmaster_nodes:
685 ctx.feedback_fn("Copying %s to %s" %
686 (", ".join(files_to_copy), node_name))
687 for file_name in files_to_copy:
688 ctx.ssh.CopyFileToNode(node_name, file_name)
690 RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
692 ToStdout("All requested certificates and keys have been replaced."
693 " Running \"gnt-cluster verify\" now is recommended.")
698 def RenewCrypto(opts, args):
699 """Renews cluster certificates, keys and secrets.
702 return _RenewCrypto(opts.new_cluster_cert,
705 opts.new_confd_hmac_key,
706 opts.new_cluster_domain_secret,
707 opts.cluster_domain_secret,
711 def SetClusterParams(opts, args):
712 """Modify the cluster.
714 @param opts: the command line options selected by the user
716 @param args: should be an empty list
718 @return: the desired exit code
721 if not (not opts.lvm_storage or opts.vg_name or
722 not opts.drbd_storage or opts.drbd_helper or
723 opts.enabled_hypervisors or opts.hvparams or
724 opts.beparams or opts.nicparams or opts.ndparams or
725 opts.candidate_pool_size is not None or
726 opts.uid_pool is not None or
727 opts.maintain_node_health is not None or
728 opts.add_uids is not None or
729 opts.remove_uids is not None or
730 opts.default_iallocator is not None or
731 opts.reserved_lvs is not None or
732 opts.master_netdev is not None or
733 opts.prealloc_wipe_disks is not None):
734 ToStderr("Please give at least one of the parameters.")
737 vg_name = opts.vg_name
738 if not opts.lvm_storage and opts.vg_name:
739 ToStderr("Options --no-lvm-storage and --vg-name conflict.")
742 if not opts.lvm_storage:
745 drbd_helper = opts.drbd_helper
746 if not opts.drbd_storage and opts.drbd_helper:
747 ToStderr("Options --no-drbd-storage and --drbd-usermode-helper conflict.")
750 if not opts.drbd_storage:
753 hvlist = opts.enabled_hypervisors
754 if hvlist is not None:
755 hvlist = hvlist.split(",")
757 # a list of (name, dict) we can pass directly to dict() (or [])
758 hvparams = dict(opts.hvparams)
759 for hv_params in hvparams.values():
760 utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
762 beparams = opts.beparams
763 utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
765 nicparams = opts.nicparams
766 utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
768 ndparams = opts.ndparams
769 if ndparams is not None:
770 utils.ForceDictType(ndparams, constants.NDS_PARAMETER_TYPES)
772 mnh = opts.maintain_node_health
774 uid_pool = opts.uid_pool
775 if uid_pool is not None:
776 uid_pool = uidpool.ParseUidPool(uid_pool)
778 add_uids = opts.add_uids
779 if add_uids is not None:
780 add_uids = uidpool.ParseUidPool(add_uids)
782 remove_uids = opts.remove_uids
783 if remove_uids is not None:
784 remove_uids = uidpool.ParseUidPool(remove_uids)
786 if opts.reserved_lvs is not None:
787 if opts.reserved_lvs == "":
788 opts.reserved_lvs = []
790 opts.reserved_lvs = utils.UnescapeAndSplit(opts.reserved_lvs, sep=",")
792 op = opcodes.OpSetClusterParams(vg_name=vg_name,
793 drbd_helper=drbd_helper,
794 enabled_hypervisors=hvlist,
800 candidate_pool_size=opts.candidate_pool_size,
801 maintain_node_health=mnh,
804 remove_uids=remove_uids,
805 default_iallocator=opts.default_iallocator,
806 prealloc_wipe_disks=opts.prealloc_wipe_disks,
807 master_netdev=opts.master_netdev,
808 reserved_lvs=opts.reserved_lvs)
809 SubmitOpCode(op, opts=opts)
813 def QueueOps(opts, args):
816 @param opts: the command line options selected by the user
818 @param args: should contain only one element, the subcommand
820 @return: the desired exit code
825 if command in ("drain", "undrain"):
826 drain_flag = command == "drain"
827 client.SetQueueDrainFlag(drain_flag)
828 elif command == "info":
829 result = client.QueryConfigValues(["drain_flag"])
834 ToStdout("The drain flag is %s" % val)
836 raise errors.OpPrereqError("Command '%s' is not valid." % command,
842 def _ShowWatcherPause(until):
843 if until is None or until < time.time():
844 ToStdout("The watcher is not paused.")
846 ToStdout("The watcher is paused until %s.", time.ctime(until))
849 def WatcherOps(opts, args):
850 """Watcher operations.
852 @param opts: the command line options selected by the user
854 @param args: should contain only one element, the subcommand
856 @return: the desired exit code
862 if command == "continue":
863 client.SetWatcherPause(None)
864 ToStdout("The watcher is no longer paused.")
866 elif command == "pause":
868 raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
870 result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
871 _ShowWatcherPause(result)
873 elif command == "info":
874 result = client.QueryConfigValues(["watcher_pause"])
875 _ShowWatcherPause(result[0])
878 raise errors.OpPrereqError("Command '%s' is not valid." % command,
886 InitCluster, [ArgHost(min=1, max=1)],
887 [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, GLOBAL_FILEDIR_OPT,
888 HVLIST_OPT, MAC_PREFIX_OPT, MASTER_NETDEV_OPT, NIC_PARAMS_OPT,
889 NOLVM_STORAGE_OPT, NOMODIFY_ETCHOSTS_OPT, NOMODIFY_SSH_SETUP_OPT,
890 SECONDARY_IP_OPT, VG_NAME_OPT, MAINTAIN_NODE_HEALTH_OPT,
891 UIDPOOL_OPT, DRBD_HELPER_OPT, NODRBD_STORAGE_OPT,
892 DEFAULT_IALLOCATOR_OPT, PRIMARY_IP_VERSION_OPT, PREALLOC_WIPE_DISKS_OPT,
894 "[opts...] <cluster_name>", "Initialises a new cluster configuration"),
896 DestroyCluster, ARGS_NONE, [YES_DOIT_OPT],
897 "", "Destroy cluster"),
899 RenameCluster, [ArgHost(min=1, max=1)],
900 [FORCE_OPT, DRY_RUN_OPT],
902 "Renames the cluster"),
904 RedistributeConfig, ARGS_NONE, [SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
905 "", "Forces a push of the configuration file and ssconf files"
906 " to the nodes in the cluster"),
908 VerifyCluster, ARGS_NONE,
909 [VERBOSE_OPT, DEBUG_SIMERR_OPT, ERROR_CODES_OPT, NONPLUS1_OPT,
910 DRY_RUN_OPT, PRIORITY_OPT],
911 "", "Does a check on the cluster configuration"),
913 VerifyDisks, ARGS_NONE, [PRIORITY_OPT],
914 "", "Does a check on the cluster disk status"),
915 'repair-disk-sizes': (
916 RepairDiskSizes, ARGS_MANY_INSTANCES, [DRY_RUN_OPT, PRIORITY_OPT],
917 "", "Updates mismatches in recorded disk sizes"),
919 MasterFailover, ARGS_NONE, [NOVOTING_OPT],
920 "", "Makes the current node the master"),
922 MasterPing, ARGS_NONE, [],
923 "", "Checks if the master is alive"),
925 ShowClusterVersion, ARGS_NONE, [],
926 "", "Shows the cluster version"),
928 ShowClusterMaster, ARGS_NONE, [],
929 "", "Shows the cluster master"),
931 ClusterCopyFile, [ArgFile(min=1, max=1)],
932 [NODE_LIST_OPT, USE_REPL_NET_OPT],
933 "[-n node...] <filename>", "Copies a file to all (or only some) nodes"),
935 RunClusterCommand, [ArgCommand(min=1)],
937 "[-n node...] <command>", "Runs a command on all (or only some) nodes"),
939 ShowClusterConfig, ARGS_NONE, [ROMAN_OPT],
940 "[--roman]", "Show cluster configuration"),
942 ListTags, ARGS_NONE, [], "", "List the tags of the cluster"),
944 AddTags, [ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT],
945 "tag...", "Add tags to the cluster"),
947 RemoveTags, [ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT],
948 "tag...", "Remove tags from the cluster"),
950 SearchTags, [ArgUnknown(min=1, max=1)], [PRIORITY_OPT], "",
951 "Searches the tags on all objects on"
952 " the cluster for a given pattern (regex)"),
955 [ArgChoice(min=1, max=1, choices=["drain", "undrain", "info"])],
956 [], "drain|undrain|info", "Change queue properties"),
959 [ArgChoice(min=1, max=1, choices=["pause", "continue", "info"]),
960 ArgSuggest(min=0, max=1, choices=["30m", "1h", "4h"])],
962 "{pause <timespec>|continue|info}", "Change watcher properties"),
964 SetClusterParams, ARGS_NONE,
965 [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, HVLIST_OPT, MASTER_NETDEV_OPT,
966 NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, VG_NAME_OPT, MAINTAIN_NODE_HEALTH_OPT,
967 UIDPOOL_OPT, ADD_UIDS_OPT, REMOVE_UIDS_OPT, DRBD_HELPER_OPT,
968 NODRBD_STORAGE_OPT, DEFAULT_IALLOCATOR_OPT, RESERVED_LVS_OPT,
969 DRY_RUN_OPT, PRIORITY_OPT, PREALLOC_WIPE_DISKS_OPT, NODE_PARAMS_OPT],
971 "Alters the parameters of the cluster"),
973 RenewCrypto, ARGS_NONE,
974 [NEW_CLUSTER_CERT_OPT, NEW_RAPI_CERT_OPT, RAPI_CERT_OPT,
975 NEW_CONFD_HMAC_KEY_OPT, FORCE_OPT,
976 NEW_CLUSTER_DOMAIN_SECRET_OPT, CLUSTER_DOMAIN_SECRET_OPT],
978 "Renews cluster certificates, keys and secrets"),
982 #: dictionary with aliases for commands
984 'masterfailover': 'master-failover',
989 return GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER},