4 # Copyright (C) 2006, 2007, 2010, 2011, 2012 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=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
34 from ganeti.cli import *
35 from ganeti import opcodes
36 from ganeti import constants
37 from ganeti import errors
38 from ganeti import utils
39 from ganeti import bootstrap
40 from ganeti import ssh
41 from ganeti import objects
42 from ganeti import uidpool
43 from ganeti import compat
44 from ganeti import netutils
45 from ganeti import pathutils
48 ON_OPT = cli_option("--on", default=False,
49 action="store_true", dest="on",
50 help="Recover from an EPO")
52 GROUPS_OPT = cli_option("--groups", default=False,
53 action="store_true", dest="groups",
54 help="Arguments are node groups instead of nodes")
56 FORCE_FAILOVER = cli_option("--yes-do-it", dest="yes_do_it",
57 help="Override interactive check for --no-voting",
58 default=False, action="store_true")
60 _EPO_PING_INTERVAL = 30 # 30 seconds between pings
61 _EPO_PING_TIMEOUT = 1 # 1 second
62 _EPO_REACHABLE_TIMEOUT = 15 * 60 # 15 minutes
66 def InitCluster(opts, args):
67 """Initialize the cluster.
69 @param opts: the command line options selected by the user
71 @param args: should contain only one element, the desired
74 @return: the desired exit code
77 if not opts.lvm_storage and opts.vg_name:
78 ToStderr("Options --no-lvm-storage and --vg-name conflict.")
81 vg_name = opts.vg_name
82 if opts.lvm_storage and not opts.vg_name:
83 vg_name = constants.DEFAULT_VG
85 if not opts.drbd_storage and opts.drbd_helper:
86 ToStderr("Options --no-drbd-storage and --drbd-usermode-helper conflict.")
89 drbd_helper = opts.drbd_helper
90 if opts.drbd_storage and not opts.drbd_helper:
91 drbd_helper = constants.DEFAULT_DRBD_HELPER
93 master_netdev = opts.master_netdev
94 if master_netdev is None:
95 master_netdev = constants.DEFAULT_BRIDGE
97 hvlist = opts.enabled_hypervisors
99 hvlist = constants.DEFAULT_ENABLED_HYPERVISOR
100 hvlist = hvlist.split(",")
102 hvparams = dict(opts.hvparams)
103 beparams = opts.beparams
104 nicparams = opts.nicparams
106 diskparams = dict(opts.diskparams)
108 # check the disk template types here, as we cannot rely on the type check done
109 # by the opcode parameter types
110 diskparams_keys = set(diskparams.keys())
111 if not (diskparams_keys <= constants.DISK_TEMPLATES):
112 unknown = utils.NiceSort(diskparams_keys - constants.DISK_TEMPLATES)
113 ToStderr("Disk templates unknown: %s" % utils.CommaJoin(unknown))
116 # prepare beparams dict
117 beparams = objects.FillDict(constants.BEC_DEFAULTS, beparams)
118 utils.ForceDictType(beparams, constants.BES_PARAMETER_COMPAT)
120 # prepare nicparams dict
121 nicparams = objects.FillDict(constants.NICC_DEFAULTS, nicparams)
122 utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
124 # prepare ndparams dict
125 if opts.ndparams is None:
126 ndparams = dict(constants.NDC_DEFAULTS)
128 ndparams = objects.FillDict(constants.NDC_DEFAULTS, opts.ndparams)
129 utils.ForceDictType(ndparams, constants.NDS_PARAMETER_TYPES)
131 # prepare hvparams dict
132 for hv in constants.HYPER_TYPES:
133 if hv not in hvparams:
135 hvparams[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], hvparams[hv])
136 utils.ForceDictType(hvparams[hv], constants.HVS_PARAMETER_TYPES)
138 # prepare diskparams dict
139 for templ in constants.DISK_TEMPLATES:
140 if templ not in diskparams:
141 diskparams[templ] = {}
142 diskparams[templ] = objects.FillDict(constants.DISK_DT_DEFAULTS[templ],
144 utils.ForceDictType(diskparams[templ], constants.DISK_DT_TYPES)
146 # prepare ipolicy dict
147 ipolicy_raw = CreateIPolicyFromOpts(
148 ispecs_mem_size=opts.ispecs_mem_size,
149 ispecs_cpu_count=opts.ispecs_cpu_count,
150 ispecs_disk_count=opts.ispecs_disk_count,
151 ispecs_disk_size=opts.ispecs_disk_size,
152 ispecs_nic_count=opts.ispecs_nic_count,
153 ipolicy_disk_templates=opts.ipolicy_disk_templates,
154 ipolicy_vcpu_ratio=opts.ipolicy_vcpu_ratio,
155 ipolicy_spindle_ratio=opts.ipolicy_spindle_ratio,
157 ipolicy = objects.FillIPolicy(constants.IPOLICY_DEFAULTS, ipolicy_raw)
159 if opts.candidate_pool_size is None:
160 opts.candidate_pool_size = constants.MASTER_POOL_SIZE_DEFAULT
162 if opts.mac_prefix is None:
163 opts.mac_prefix = constants.DEFAULT_MAC_PREFIX
165 uid_pool = opts.uid_pool
166 if uid_pool is not None:
167 uid_pool = uidpool.ParseUidPool(uid_pool)
169 if opts.prealloc_wipe_disks is None:
170 opts.prealloc_wipe_disks = False
172 external_ip_setup_script = opts.use_external_mip_script
173 if external_ip_setup_script is None:
174 external_ip_setup_script = False
177 primary_ip_version = int(opts.primary_ip_version)
178 except (ValueError, TypeError), err:
179 ToStderr("Invalid primary ip version value: %s" % str(err))
182 master_netmask = opts.master_netmask
184 if master_netmask is not None:
185 master_netmask = int(master_netmask)
186 except (ValueError, TypeError), err:
187 ToStderr("Invalid master netmask value: %s" % str(err))
191 disk_state = utils.FlatToDict(opts.disk_state)
195 hv_state = dict(opts.hv_state)
197 bootstrap.InitCluster(cluster_name=args[0],
198 secondary_ip=opts.secondary_ip,
200 mac_prefix=opts.mac_prefix,
201 master_netmask=master_netmask,
202 master_netdev=master_netdev,
203 file_storage_dir=opts.file_storage_dir,
204 shared_file_storage_dir=opts.shared_file_storage_dir,
205 enabled_hypervisors=hvlist,
210 diskparams=diskparams,
212 candidate_pool_size=opts.candidate_pool_size,
213 modify_etc_hosts=opts.modify_etc_hosts,
214 modify_ssh_setup=opts.modify_ssh_setup,
215 maintain_node_health=opts.maintain_node_health,
216 drbd_helper=drbd_helper,
218 default_iallocator=opts.default_iallocator,
219 primary_ip_version=primary_ip_version,
220 prealloc_wipe_disks=opts.prealloc_wipe_disks,
221 use_external_mip_script=external_ip_setup_script,
223 disk_state=disk_state,
225 op = opcodes.OpClusterPostInit()
226 SubmitOpCode(op, opts=opts)
231 def DestroyCluster(opts, args):
232 """Destroy the cluster.
234 @param opts: the command line options selected by the user
236 @param args: should be an empty list
238 @return: the desired exit code
241 if not opts.yes_do_it:
242 ToStderr("Destroying a cluster is irreversible. If you really want"
243 " destroy this cluster, supply the --yes-do-it option.")
246 op = opcodes.OpClusterDestroy()
247 master = SubmitOpCode(op, opts=opts)
248 # if we reached this, the opcode didn't fail; we can proceed to
249 # shutdown all the daemons
250 bootstrap.FinalizeClusterDestroy(master)
254 def RenameCluster(opts, args):
255 """Rename the cluster.
257 @param opts: the command line options selected by the user
259 @param args: should contain only one element, the new cluster name
261 @return: the desired exit code
266 (cluster_name, ) = cl.QueryConfigValues(["cluster_name"])
270 usertext = ("This will rename the cluster from '%s' to '%s'. If you are"
271 " connected over the network to the cluster name, the"
272 " operation is very dangerous as the IP address will be"
273 " removed from the node and the change may not go through."
274 " Continue?") % (cluster_name, new_name)
275 if not AskUser(usertext):
278 op = opcodes.OpClusterRename(name=new_name)
279 result = SubmitOpCode(op, opts=opts, cl=cl)
282 ToStdout("Cluster renamed from '%s' to '%s'", cluster_name, result)
287 def ActivateMasterIp(opts, args):
288 """Activates the master IP.
291 op = opcodes.OpClusterActivateMasterIp()
296 def DeactivateMasterIp(opts, args):
297 """Deactivates the master IP.
301 usertext = ("This will disable the master IP. All the open connections to"
302 " the master IP will be closed. To reach the master you will"
303 " need to use its node IP."
305 if not AskUser(usertext):
308 op = opcodes.OpClusterDeactivateMasterIp()
313 def RedistributeConfig(opts, args):
314 """Forces push of the cluster configuration.
316 @param opts: the command line options selected by the user
318 @param args: empty list
320 @return: the desired exit code
323 op = opcodes.OpClusterRedistConf()
324 SubmitOrSend(op, opts)
328 def ShowClusterVersion(opts, args):
329 """Write version of ganeti software to the standard output.
331 @param opts: the command line options selected by the user
333 @param args: should be an empty list
335 @return: the desired exit code
338 cl = GetClient(query=True)
339 result = cl.QueryClusterInfo()
340 ToStdout("Software version: %s", result["software_version"])
341 ToStdout("Internode protocol: %s", result["protocol_version"])
342 ToStdout("Configuration format: %s", result["config_version"])
343 ToStdout("OS api version: %s", result["os_api_version"])
344 ToStdout("Export interface: %s", result["export_version"])
348 def ShowClusterMaster(opts, args):
349 """Write name of master node to the standard output.
351 @param opts: the command line options selected by the user
353 @param args: should be an empty list
355 @return: the desired exit code
358 master = bootstrap.GetMaster()
363 def _PrintGroupedParams(paramsdict, level=1, roman=False):
364 """Print Grouped parameters (be, nic, disk) by group.
366 @type paramsdict: dict of dicts
367 @param paramsdict: {group: {param: value, ...}, ...}
369 @param level: Level of indention
373 for item, val in sorted(paramsdict.items()):
374 if isinstance(val, dict):
375 ToStdout("%s- %s:", indent, item)
376 _PrintGroupedParams(val, level=level + 1, roman=roman)
377 elif roman and isinstance(val, int):
378 ToStdout("%s %s: %s", indent, item, compat.TryToRoman(val))
380 ToStdout("%s %s: %s", indent, item, val)
383 def ShowClusterConfig(opts, args):
384 """Shows cluster information.
386 @param opts: the command line options selected by the user
388 @param args: should be an empty list
390 @return: the desired exit code
393 cl = GetClient(query=True)
394 result = cl.QueryClusterInfo()
396 ToStdout("Cluster name: %s", result["name"])
397 ToStdout("Cluster UUID: %s", result["uuid"])
399 ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
400 ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))
402 ToStdout("Master node: %s", result["master"])
404 ToStdout("Architecture (this node): %s (%s)",
405 result["architecture"][0], result["architecture"][1])
408 tags = utils.CommaJoin(utils.NiceSort(result["tags"]))
412 ToStdout("Tags: %s", tags)
414 ToStdout("Default hypervisor: %s", result["default_hypervisor"])
415 ToStdout("Enabled hypervisors: %s",
416 utils.CommaJoin(result["enabled_hypervisors"]))
418 ToStdout("Hypervisor parameters:")
419 _PrintGroupedParams(result["hvparams"])
421 ToStdout("OS-specific hypervisor parameters:")
422 _PrintGroupedParams(result["os_hvp"])
424 ToStdout("OS parameters:")
425 _PrintGroupedParams(result["osparams"])
427 ToStdout("Hidden OSes: %s", utils.CommaJoin(result["hidden_os"]))
428 ToStdout("Blacklisted OSes: %s", utils.CommaJoin(result["blacklisted_os"]))
430 ToStdout("Cluster parameters:")
431 ToStdout(" - candidate pool size: %s",
432 compat.TryToRoman(result["candidate_pool_size"],
433 convert=opts.roman_integers))
434 ToStdout(" - master netdev: %s", result["master_netdev"])
435 ToStdout(" - master netmask: %s", result["master_netmask"])
436 ToStdout(" - use external master IP address setup script: %s",
437 result["use_external_mip_script"])
438 ToStdout(" - lvm volume group: %s", result["volume_group_name"])
439 if result["reserved_lvs"]:
440 reserved_lvs = utils.CommaJoin(result["reserved_lvs"])
442 reserved_lvs = "(none)"
443 ToStdout(" - lvm reserved volumes: %s", reserved_lvs)
444 ToStdout(" - drbd usermode helper: %s", result["drbd_usermode_helper"])
445 ToStdout(" - file storage path: %s", result["file_storage_dir"])
446 ToStdout(" - shared file storage path: %s",
447 result["shared_file_storage_dir"])
448 ToStdout(" - maintenance of node health: %s",
449 result["maintain_node_health"])
450 ToStdout(" - uid pool: %s", uidpool.FormatUidPool(result["uid_pool"]))
451 ToStdout(" - default instance allocator: %s", result["default_iallocator"])
452 ToStdout(" - primary ip version: %d", result["primary_ip_version"])
453 ToStdout(" - preallocation wipe disks: %s", result["prealloc_wipe_disks"])
454 ToStdout(" - OS search path: %s", utils.CommaJoin(pathutils.OS_SEARCH_PATH))
456 ToStdout("Default node parameters:")
457 _PrintGroupedParams(result["ndparams"], roman=opts.roman_integers)
459 ToStdout("Default instance parameters:")
460 _PrintGroupedParams(result["beparams"], roman=opts.roman_integers)
462 ToStdout("Default nic parameters:")
463 _PrintGroupedParams(result["nicparams"], roman=opts.roman_integers)
465 ToStdout("Default disk parameters:")
466 _PrintGroupedParams(result["diskparams"], roman=opts.roman_integers)
468 ToStdout("Instance policy - limits for instances:")
469 for key in constants.IPOLICY_ISPECS:
470 ToStdout(" - %s", key)
471 _PrintGroupedParams(result["ipolicy"][key], roman=opts.roman_integers)
472 ToStdout(" - enabled disk templates: %s",
473 utils.CommaJoin(result["ipolicy"][constants.IPOLICY_DTS]))
474 for key in constants.IPOLICY_PARAMETERS:
475 ToStdout(" - %s: %s", key, result["ipolicy"][key])
480 def ClusterCopyFile(opts, args):
481 """Copy a file from master to some nodes.
483 @param opts: the command line options selected by the user
485 @param args: should contain only one element, the path of
486 the file to be copied
488 @return: the desired exit code
492 if not os.path.exists(filename):
493 raise errors.OpPrereqError("No such filename '%s'" % filename,
498 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
500 results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
501 secondary_ips=opts.use_replication_network,
502 nodegroup=opts.nodegroup)
504 srun = ssh.SshRunner(cluster_name)
506 if not srun.CopyFileToNode(node, filename):
507 ToStderr("Copy of file %s to node %s failed", filename, node)
512 def RunClusterCommand(opts, args):
513 """Run a command on some nodes.
515 @param opts: the command line options selected by the user
517 @param args: should contain the command to be run and its arguments
519 @return: the desired exit code
524 command = " ".join(args)
526 nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl, nodegroup=opts.nodegroup)
528 cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
531 srun = ssh.SshRunner(cluster_name=cluster_name)
533 # Make sure master node is at list end
534 if master_node in nodes:
535 nodes.remove(master_node)
536 nodes.append(master_node)
539 result = srun.Run(name, constants.SSH_LOGIN_USER, command)
541 if opts.failure_only and result.exit_code == constants.EXIT_SUCCESS:
542 # Do not output anything for successful commands
545 ToStdout("------------------------------------------------")
546 if opts.show_machine_names:
547 for line in result.output.splitlines():
548 ToStdout("%s: %s", name, line)
550 ToStdout("node: %s", name)
551 ToStdout("%s", result.output)
552 ToStdout("return code = %s", result.exit_code)
557 def VerifyCluster(opts, args):
558 """Verify integrity of cluster, performing various test on nodes.
560 @param opts: the command line options selected by the user
562 @param args: should be an empty list
564 @return: the desired exit code
569 if opts.skip_nplusone_mem:
570 skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
574 op = opcodes.OpClusterVerify(verbose=opts.verbose,
575 error_codes=opts.error_codes,
576 debug_simulate_errors=opts.simulate_errors,
577 skip_checks=skip_checks,
578 ignore_errors=opts.ignore_errors,
579 group_name=opts.nodegroup)
580 result = SubmitOpCode(op, cl=cl, opts=opts)
582 # Keep track of submitted jobs
583 jex = JobExecutor(cl=cl, opts=opts)
585 for (status, job_id) in result[constants.JOB_IDS_KEY]:
586 jex.AddJobId(None, status, job_id)
588 results = jex.GetResults()
590 (bad_jobs, bad_results) = \
592 # Convert iterators to lists
595 map(compat.partial(itertools.ifilterfalse, bool),
596 # Convert result to booleans in a tuple
597 zip(*((job_success, len(op_results) == 1 and op_results[0])
598 for (job_success, op_results) in results)))))
600 if bad_jobs == 0 and bad_results == 0:
601 rcode = constants.EXIT_SUCCESS
603 rcode = constants.EXIT_FAILURE
605 ToStdout("%s job(s) failed while verifying the cluster.", bad_jobs)
610 def VerifyDisks(opts, args):
611 """Verify integrity of cluster disks.
613 @param opts: the command line options selected by the user
615 @param args: should be an empty list
617 @return: the desired exit code
622 op = opcodes.OpClusterVerifyDisks()
624 result = SubmitOpCode(op, cl=cl, opts=opts)
626 # Keep track of submitted jobs
627 jex = JobExecutor(cl=cl, opts=opts)
629 for (status, job_id) in result[constants.JOB_IDS_KEY]:
630 jex.AddJobId(None, status, job_id)
632 retcode = constants.EXIT_SUCCESS
634 for (status, result) in jex.GetResults():
636 ToStdout("Job failed: %s", result)
639 ((bad_nodes, instances, missing), ) = result
641 for node, text in bad_nodes.items():
642 ToStdout("Error gathering data on node %s: %s",
643 node, utils.SafeEncode(text[-400:]))
644 retcode = constants.EXIT_FAILURE
645 ToStdout("You need to fix these nodes first before fixing instances")
647 for iname in instances:
650 op = opcodes.OpInstanceActivateDisks(instance_name=iname)
652 ToStdout("Activating disks for instance '%s'", iname)
653 SubmitOpCode(op, opts=opts, cl=cl)
654 except errors.GenericError, err:
655 nret, msg = FormatError(err)
657 ToStderr("Error activating disks for instance %s: %s", iname, msg)
660 for iname, ival in missing.iteritems():
661 all_missing = compat.all(x[0] in bad_nodes for x in ival)
663 ToStdout("Instance %s cannot be verified as it lives on"
664 " broken nodes", iname)
666 ToStdout("Instance %s has missing logical volumes:", iname)
668 for node, vol in ival:
669 if node in bad_nodes:
670 ToStdout("\tbroken node %s /dev/%s", node, vol)
672 ToStdout("\t%s /dev/%s", node, vol)
674 ToStdout("You need to replace or recreate disks for all the above"
675 " instances if this message persists after fixing broken nodes.")
676 retcode = constants.EXIT_FAILURE
678 ToStdout("No disks need to be activated.")
683 def RepairDiskSizes(opts, args):
684 """Verify sizes of cluster disks.
686 @param opts: the command line options selected by the user
688 @param args: optional list of instances to restrict check to
690 @return: the desired exit code
693 op = opcodes.OpClusterRepairDiskSizes(instances=args)
694 SubmitOpCode(op, opts=opts)
698 def MasterFailover(opts, args):
699 """Failover the master node.
701 This command, when run on a non-master node, will cause the current
702 master to cease being master, and the non-master to become new
705 @param opts: the command line options selected by the user
707 @param args: should be an empty list
709 @return: the desired exit code
712 if opts.no_voting and not opts.yes_do_it:
713 usertext = ("This will perform the failover even if most other nodes"
714 " are down, or if this node is outdated. This is dangerous"
715 " as it can lead to a non-consistent cluster. Check the"
716 " gnt-cluster(8) man page before proceeding. Continue?")
717 if not AskUser(usertext):
720 return bootstrap.MasterFailover(no_voting=opts.no_voting)
723 def MasterPing(opts, args):
724 """Checks if the master is alive.
726 @param opts: the command line options selected by the user
728 @param args: should be an empty list
730 @return: the desired exit code
735 cl.QueryClusterInfo()
737 except Exception: # pylint: disable=W0703
741 def SearchTags(opts, args):
742 """Searches the tags on all the cluster.
744 @param opts: the command line options selected by the user
746 @param args: should contain only one element, the tag pattern
748 @return: the desired exit code
751 op = opcodes.OpTagsSearch(pattern=args[0])
752 result = SubmitOpCode(op, opts=opts)
755 result = list(result)
757 for path, tag in result:
758 ToStdout("%s %s", path, tag)
761 def _ReadAndVerifyCert(cert_filename, verify_private_key=False):
762 """Reads and verifies an X509 certificate.
764 @type cert_filename: string
765 @param cert_filename: the path of the file containing the certificate to
766 verify encoded in PEM format
767 @type verify_private_key: bool
768 @param verify_private_key: whether to verify the private key in addition to
769 the public certificate
771 @return: a string containing the PEM-encoded certificate.
775 pem = utils.ReadFile(cert_filename)
777 raise errors.X509CertError(cert_filename,
778 "Unable to read certificate: %s" % str(err))
781 OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, pem)
782 except Exception, err:
783 raise errors.X509CertError(cert_filename,
784 "Unable to load certificate: %s" % str(err))
786 if verify_private_key:
788 OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, pem)
789 except Exception, err:
790 raise errors.X509CertError(cert_filename,
791 "Unable to load private key: %s" % str(err))
796 def _RenewCrypto(new_cluster_cert, new_rapi_cert, # pylint: disable=R0911
797 rapi_cert_filename, new_spice_cert, spice_cert_filename,
798 spice_cacert_filename, new_confd_hmac_key, new_cds,
799 cds_filename, force):
800 """Renews cluster certificates, keys and secrets.
802 @type new_cluster_cert: bool
803 @param new_cluster_cert: Whether to generate a new cluster certificate
804 @type new_rapi_cert: bool
805 @param new_rapi_cert: Whether to generate a new RAPI certificate
806 @type rapi_cert_filename: string
807 @param rapi_cert_filename: Path to file containing new RAPI certificate
808 @type new_spice_cert: bool
809 @param new_spice_cert: Whether to generate a new SPICE certificate
810 @type spice_cert_filename: string
811 @param spice_cert_filename: Path to file containing new SPICE certificate
812 @type spice_cacert_filename: string
813 @param spice_cacert_filename: Path to file containing the certificate of the
814 CA that signed the SPICE certificate
815 @type new_confd_hmac_key: bool
816 @param new_confd_hmac_key: Whether to generate a new HMAC key
818 @param new_cds: Whether to generate a new cluster domain secret
819 @type cds_filename: string
820 @param cds_filename: Path to file containing new cluster domain secret
822 @param force: Whether to ask user for confirmation
825 if new_rapi_cert and rapi_cert_filename:
826 ToStderr("Only one of the --new-rapi-certificate and --rapi-certificate"
827 " options can be specified at the same time.")
830 if new_cds and cds_filename:
831 ToStderr("Only one of the --new-cluster-domain-secret and"
832 " --cluster-domain-secret options can be specified at"
836 if new_spice_cert and (spice_cert_filename or spice_cacert_filename):
837 ToStderr("When using --new-spice-certificate, the --spice-certificate"
838 " and --spice-ca-certificate must not be used.")
841 if bool(spice_cacert_filename) ^ bool(spice_cert_filename):
842 ToStderr("Both --spice-certificate and --spice-ca-certificate must be"
846 rapi_cert_pem, spice_cert_pem, spice_cacert_pem = (None, None, None)
848 if rapi_cert_filename:
849 rapi_cert_pem = _ReadAndVerifyCert(rapi_cert_filename, True)
850 if spice_cert_filename:
851 spice_cert_pem = _ReadAndVerifyCert(spice_cert_filename, True)
852 spice_cacert_pem = _ReadAndVerifyCert(spice_cacert_filename)
853 except errors.X509CertError, err:
854 ToStderr("Unable to load X509 certificate from %s: %s", err[0], err[1])
859 cds = utils.ReadFile(cds_filename)
860 except Exception, err: # pylint: disable=W0703
861 ToStderr("Can't load new cluster domain secret from %s: %s" %
862 (cds_filename, str(err)))
868 usertext = ("This requires all daemons on all nodes to be restarted and"
869 " may take some time. Continue?")
870 if not AskUser(usertext):
873 def _RenewCryptoInner(ctx):
874 ctx.feedback_fn("Updating certificates and keys")
875 bootstrap.GenerateClusterCrypto(new_cluster_cert,
880 rapi_cert_pem=rapi_cert_pem,
881 spice_cert_pem=spice_cert_pem,
882 spice_cacert_pem=spice_cacert_pem,
888 files_to_copy.append(pathutils.NODED_CERT_FILE)
890 if new_rapi_cert or rapi_cert_pem:
891 files_to_copy.append(pathutils.RAPI_CERT_FILE)
893 if new_spice_cert or spice_cert_pem:
894 files_to_copy.append(pathutils.SPICE_CERT_FILE)
895 files_to_copy.append(pathutils.SPICE_CACERT_FILE)
897 if new_confd_hmac_key:
898 files_to_copy.append(pathutils.CONFD_HMAC_KEY)
901 files_to_copy.append(pathutils.CLUSTER_DOMAIN_SECRET_FILE)
904 for node_name in ctx.nonmaster_nodes:
905 ctx.feedback_fn("Copying %s to %s" %
906 (", ".join(files_to_copy), node_name))
907 for file_name in files_to_copy:
908 ctx.ssh.CopyFileToNode(node_name, file_name)
910 RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
912 ToStdout("All requested certificates and keys have been replaced."
913 " Running \"gnt-cluster verify\" now is recommended.")
918 def RenewCrypto(opts, args):
919 """Renews cluster certificates, keys and secrets.
922 return _RenewCrypto(opts.new_cluster_cert,
928 opts.new_confd_hmac_key,
929 opts.new_cluster_domain_secret,
930 opts.cluster_domain_secret,
934 def SetClusterParams(opts, args):
935 """Modify the cluster.
937 @param opts: the command line options selected by the user
939 @param args: should be an empty list
941 @return: the desired exit code
944 if not (not opts.lvm_storage or opts.vg_name or
945 not opts.drbd_storage or opts.drbd_helper or
946 opts.enabled_hypervisors or opts.hvparams or
947 opts.beparams or opts.nicparams or
948 opts.ndparams or opts.diskparams or
949 opts.candidate_pool_size is not None or
950 opts.uid_pool is not None or
951 opts.maintain_node_health is not None or
952 opts.add_uids is not None or
953 opts.remove_uids is not None or
954 opts.default_iallocator is not None or
955 opts.reserved_lvs is not None or
956 opts.master_netdev is not None or
957 opts.master_netmask is not None or
958 opts.use_external_mip_script is not None or
959 opts.prealloc_wipe_disks is not None or
962 opts.ispecs_mem_size or
963 opts.ispecs_cpu_count or
964 opts.ispecs_disk_count or
965 opts.ispecs_disk_size or
966 opts.ispecs_nic_count or
967 opts.ipolicy_disk_templates is not None or
968 opts.ipolicy_vcpu_ratio is not None or
969 opts.ipolicy_spindle_ratio is not None):
970 ToStderr("Please give at least one of the parameters.")
973 vg_name = opts.vg_name
974 if not opts.lvm_storage and opts.vg_name:
975 ToStderr("Options --no-lvm-storage and --vg-name conflict.")
978 if not opts.lvm_storage:
981 drbd_helper = opts.drbd_helper
982 if not opts.drbd_storage and opts.drbd_helper:
983 ToStderr("Options --no-drbd-storage and --drbd-usermode-helper conflict.")
986 if not opts.drbd_storage:
989 hvlist = opts.enabled_hypervisors
990 if hvlist is not None:
991 hvlist = hvlist.split(",")
993 # a list of (name, dict) we can pass directly to dict() (or [])
994 hvparams = dict(opts.hvparams)
995 for hv_params in hvparams.values():
996 utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
998 diskparams = dict(opts.diskparams)
1000 for dt_params in diskparams.values():
1001 utils.ForceDictType(dt_params, constants.DISK_DT_TYPES)
1003 beparams = opts.beparams
1004 utils.ForceDictType(beparams, constants.BES_PARAMETER_COMPAT)
1006 nicparams = opts.nicparams
1007 utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
1009 ndparams = opts.ndparams
1010 if ndparams is not None:
1011 utils.ForceDictType(ndparams, constants.NDS_PARAMETER_TYPES)
1013 ipolicy = CreateIPolicyFromOpts(
1014 ispecs_mem_size=opts.ispecs_mem_size,
1015 ispecs_cpu_count=opts.ispecs_cpu_count,
1016 ispecs_disk_count=opts.ispecs_disk_count,
1017 ispecs_disk_size=opts.ispecs_disk_size,
1018 ispecs_nic_count=opts.ispecs_nic_count,
1019 ipolicy_disk_templates=opts.ipolicy_disk_templates,
1020 ipolicy_vcpu_ratio=opts.ipolicy_vcpu_ratio,
1021 ipolicy_spindle_ratio=opts.ipolicy_spindle_ratio,
1024 mnh = opts.maintain_node_health
1026 uid_pool = opts.uid_pool
1027 if uid_pool is not None:
1028 uid_pool = uidpool.ParseUidPool(uid_pool)
1030 add_uids = opts.add_uids
1031 if add_uids is not None:
1032 add_uids = uidpool.ParseUidPool(add_uids)
1034 remove_uids = opts.remove_uids
1035 if remove_uids is not None:
1036 remove_uids = uidpool.ParseUidPool(remove_uids)
1038 if opts.reserved_lvs is not None:
1039 if opts.reserved_lvs == "":
1040 opts.reserved_lvs = []
1042 opts.reserved_lvs = utils.UnescapeAndSplit(opts.reserved_lvs, sep=",")
1044 if opts.master_netmask is not None:
1046 opts.master_netmask = int(opts.master_netmask)
1048 ToStderr("The --master-netmask option expects an int parameter.")
1051 ext_ip_script = opts.use_external_mip_script
1054 disk_state = utils.FlatToDict(opts.disk_state)
1058 hv_state = dict(opts.hv_state)
1060 op = opcodes.OpClusterSetParams(vg_name=vg_name,
1061 drbd_helper=drbd_helper,
1062 enabled_hypervisors=hvlist,
1066 nicparams=nicparams,
1068 diskparams=diskparams,
1070 candidate_pool_size=opts.candidate_pool_size,
1071 maintain_node_health=mnh,
1074 remove_uids=remove_uids,
1075 default_iallocator=opts.default_iallocator,
1076 prealloc_wipe_disks=opts.prealloc_wipe_disks,
1077 master_netdev=opts.master_netdev,
1078 master_netmask=opts.master_netmask,
1079 reserved_lvs=opts.reserved_lvs,
1080 use_external_mip_script=ext_ip_script,
1082 disk_state=disk_state,
1084 SubmitOrSend(op, opts)
1088 def QueueOps(opts, args):
1089 """Queue operations.
1091 @param opts: the command line options selected by the user
1093 @param args: should contain only one element, the subcommand
1095 @return: the desired exit code
1099 client = GetClient()
1100 if command in ("drain", "undrain"):
1101 drain_flag = command == "drain"
1102 client.SetQueueDrainFlag(drain_flag)
1103 elif command == "info":
1104 result = client.QueryConfigValues(["drain_flag"])
1109 ToStdout("The drain flag is %s" % val)
1111 raise errors.OpPrereqError("Command '%s' is not valid." % command,
1117 def _ShowWatcherPause(until):
1118 if until is None or until < time.time():
1119 ToStdout("The watcher is not paused.")
1121 ToStdout("The watcher is paused until %s.", time.ctime(until))
1124 def WatcherOps(opts, args):
1125 """Watcher operations.
1127 @param opts: the command line options selected by the user
1129 @param args: should contain only one element, the subcommand
1131 @return: the desired exit code
1135 client = GetClient()
1137 if command == "continue":
1138 client.SetWatcherPause(None)
1139 ToStdout("The watcher is no longer paused.")
1141 elif command == "pause":
1143 raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
1145 result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
1146 _ShowWatcherPause(result)
1148 elif command == "info":
1149 result = client.QueryConfigValues(["watcher_pause"])
1150 _ShowWatcherPause(result[0])
1153 raise errors.OpPrereqError("Command '%s' is not valid." % command,
1159 def _OobPower(opts, node_list, power):
1160 """Puts the node in the list to desired power state.
1162 @param opts: The command line options selected by the user
1163 @param node_list: The list of nodes to operate on
1164 @param power: True if they should be powered on, False otherwise
1165 @return: The success of the operation (none failed)
1169 command = constants.OOB_POWER_ON
1171 command = constants.OOB_POWER_OFF
1173 op = opcodes.OpOobCommand(node_names=node_list,
1176 timeout=opts.oob_timeout,
1177 power_delay=opts.power_delay)
1178 result = SubmitOpCode(op, opts=opts)
1180 for node_result in result:
1181 (node_tuple, data_tuple) = node_result
1182 (_, node_name) = node_tuple
1183 (data_status, _) = data_tuple
1184 if data_status != constants.RS_NORMAL:
1185 assert data_status != constants.RS_UNAVAIL
1187 ToStderr("There was a problem changing power for %s, please investigate",
1196 def _InstanceStart(opts, inst_list, start, no_remember=False):
1197 """Puts the instances in the list to desired state.
1199 @param opts: The command line options selected by the user
1200 @param inst_list: The list of instances to operate on
1201 @param start: True if they should be started, False for shutdown
1202 @param no_remember: If the instance state should be remembered
1203 @return: The success of the operation (none failed)
1207 opcls = opcodes.OpInstanceStartup
1208 text_submit, text_success, text_failed = ("startup", "started", "starting")
1210 opcls = compat.partial(opcodes.OpInstanceShutdown,
1211 timeout=opts.shutdown_timeout,
1212 no_remember=no_remember)
1213 text_submit, text_success, text_failed = ("shutdown", "stopped", "stopping")
1215 jex = JobExecutor(opts=opts)
1217 for inst in inst_list:
1218 ToStdout("Submit %s of instance %s", text_submit, inst)
1219 op = opcls(instance_name=inst)
1220 jex.QueueJob(inst, op)
1222 results = jex.GetResults()
1223 bad_cnt = len([1 for (success, _) in results if not success])
1226 ToStdout("All instances have been %s successfully", text_success)
1228 ToStderr("There were errors while %s instances:\n"
1229 "%d error(s) out of %d instance(s)", text_failed, bad_cnt,
1236 class _RunWhenNodesReachableHelper:
1237 """Helper class to make shared internal state sharing easier.
1239 @ivar success: Indicates if all action_cb calls were successful
1242 def __init__(self, node_list, action_cb, node2ip, port, feedback_fn,
1243 _ping_fn=netutils.TcpPing, _sleep_fn=time.sleep):
1246 @param node_list: The list of nodes to be reachable
1247 @param action_cb: Callback called when a new host is reachable
1249 @param node2ip: Node to ip mapping
1250 @param port: The port to use for the TCP ping
1251 @param feedback_fn: The function used for feedback
1252 @param _ping_fn: Function to check reachabilty (for unittest use only)
1253 @param _sleep_fn: Function to sleep (for unittest use only)
1256 self.down = set(node_list)
1258 self.node2ip = node2ip
1260 self.action_cb = action_cb
1262 self.feedback_fn = feedback_fn
1263 self._ping_fn = _ping_fn
1264 self._sleep_fn = _sleep_fn
1267 """When called we run action_cb.
1269 @raises utils.RetryAgain: When there are still down nodes
1272 if not self.action_cb(self.up):
1273 self.success = False
1276 raise utils.RetryAgain()
1280 def Wait(self, secs):
1281 """Checks if a host is up or waits remaining seconds.
1283 @param secs: The secs remaining
1287 for node in self.down:
1288 if self._ping_fn(self.node2ip[node], self.port, timeout=_EPO_PING_TIMEOUT,
1289 live_port_needed=True):
1290 self.feedback_fn("Node %s became available" % node)
1292 self.down -= self.up
1293 # If we have a node available there is the possibility to run the
1294 # action callback successfully, therefore we don't wait and return
1297 self._sleep_fn(max(0.0, start + secs - time.time()))
1300 def _RunWhenNodesReachable(node_list, action_cb, interval):
1301 """Run action_cb when nodes become reachable.
1303 @param node_list: The list of nodes to be reachable
1304 @param action_cb: Callback called when a new host is reachable
1305 @param interval: The earliest time to retry
1308 client = GetClient()
1309 cluster_info = client.QueryClusterInfo()
1310 if cluster_info["primary_ip_version"] == constants.IP4_VERSION:
1311 family = netutils.IPAddress.family
1313 family = netutils.IP6Address.family
1315 node2ip = dict((node, netutils.GetHostname(node, family=family).ip)
1316 for node in node_list)
1318 port = netutils.GetDaemonPort(constants.NODED)
1319 helper = _RunWhenNodesReachableHelper(node_list, action_cb, node2ip, port,
1323 return utils.Retry(helper, interval, _EPO_REACHABLE_TIMEOUT,
1324 wait_fn=helper.Wait)
1325 except utils.RetryTimeout:
1326 ToStderr("Time exceeded while waiting for nodes to become reachable"
1327 " again:\n - %s", " - ".join(helper.down))
1331 def _MaybeInstanceStartup(opts, inst_map, nodes_online,
1332 _instance_start_fn=_InstanceStart):
1333 """Start the instances conditional based on node_states.
1335 @param opts: The command line options selected by the user
1336 @param inst_map: A dict of inst -> nodes mapping
1337 @param nodes_online: A list of nodes online
1338 @param _instance_start_fn: Callback to start instances (unittest use only)
1339 @return: Success of the operation on all instances
1342 start_inst_list = []
1343 for (inst, nodes) in inst_map.items():
1344 if not (nodes - nodes_online):
1345 # All nodes the instance lives on are back online
1346 start_inst_list.append(inst)
1348 for inst in start_inst_list:
1352 return _instance_start_fn(opts, start_inst_list, True)
1357 def _EpoOn(opts, full_node_list, node_list, inst_map):
1358 """Does the actual power on.
1360 @param opts: The command line options selected by the user
1361 @param full_node_list: All nodes to operate on (includes nodes not supporting
1363 @param node_list: The list of nodes to operate on (all need to support OOB)
1364 @param inst_map: A dict of inst -> nodes mapping
1365 @return: The desired exit status
1368 if node_list and not _OobPower(opts, node_list, False):
1369 ToStderr("Not all nodes seem to get back up, investigate and start"
1370 " manually if needed")
1372 # Wait for the nodes to be back up
1373 action_cb = compat.partial(_MaybeInstanceStartup, opts, dict(inst_map))
1375 ToStdout("Waiting until all nodes are available again")
1376 if not _RunWhenNodesReachable(full_node_list, action_cb, _EPO_PING_INTERVAL):
1377 ToStderr("Please investigate and start stopped instances manually")
1378 return constants.EXIT_FAILURE
1380 return constants.EXIT_SUCCESS
1383 def _EpoOff(opts, node_list, inst_map):
1384 """Does the actual power off.
1386 @param opts: The command line options selected by the user
1387 @param node_list: The list of nodes to operate on (all need to support OOB)
1388 @param inst_map: A dict of inst -> nodes mapping
1389 @return: The desired exit status
1392 if not _InstanceStart(opts, inst_map.keys(), False, no_remember=True):
1393 ToStderr("Please investigate and stop instances manually before continuing")
1394 return constants.EXIT_FAILURE
1397 return constants.EXIT_SUCCESS
1399 if _OobPower(opts, node_list, False):
1400 return constants.EXIT_SUCCESS
1402 return constants.EXIT_FAILURE
1405 def Epo(opts, args, cl=None, _on_fn=_EpoOn, _off_fn=_EpoOff,
1406 _confirm_fn=ConfirmOperation,
1407 _stdout_fn=ToStdout, _stderr_fn=ToStderr):
1410 @param opts: the command line options selected by the user
1412 @param args: should contain only one element, the subcommand
1414 @return: the desired exit code
1417 if opts.groups and opts.show_all:
1418 _stderr_fn("Only one of --groups or --all are allowed")
1419 return constants.EXIT_FAILURE
1420 elif args and opts.show_all:
1421 _stderr_fn("Arguments in combination with --all are not allowed")
1422 return constants.EXIT_FAILURE
1429 itertools.chain(*cl.QueryGroups(args, ["node_list"], False))
1431 node_query_list = args
1433 result = cl.QueryNodes(node_query_list, ["name", "master", "pinst_list",
1434 "sinst_list", "powered", "offline"],
1437 all_nodes = map(compat.fst, result)
1440 for (node, master, pinsts, sinsts, powered, offline) in result:
1442 for inst in (pinsts + sinsts):
1443 if inst in inst_map:
1445 inst_map[inst].add(node)
1447 inst_map[inst] = set()
1449 inst_map[inst] = set([node])
1451 if master and opts.on:
1452 # We ignore the master for turning on the machines, in fact we are
1453 # already operating on the master at this point :)
1455 elif master and not opts.show_all:
1456 _stderr_fn("%s is the master node, please do a master-failover to another"
1457 " node not affected by the EPO or use --all if you intend to"
1458 " shutdown the whole cluster", node)
1459 return constants.EXIT_FAILURE
1460 elif powered is None:
1461 _stdout_fn("Node %s does not support out-of-band handling, it can not be"
1462 " handled in a fully automated manner", node)
1463 elif powered == opts.on:
1464 _stdout_fn("Node %s is already in desired power state, skipping", node)
1465 elif not offline or (offline and powered):
1466 node_list.append(node)
1468 if not (opts.force or _confirm_fn(all_nodes, "nodes", "epo")):
1469 return constants.EXIT_FAILURE
1472 return _on_fn(opts, all_nodes, node_list, inst_map)
1474 return _off_fn(opts, node_list, inst_map)
1479 InitCluster, [ArgHost(min=1, max=1)],
1480 [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, GLOBAL_FILEDIR_OPT,
1481 HVLIST_OPT, MAC_PREFIX_OPT, MASTER_NETDEV_OPT, MASTER_NETMASK_OPT,
1482 NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, NOMODIFY_ETCHOSTS_OPT,
1483 NOMODIFY_SSH_SETUP_OPT, SECONDARY_IP_OPT, VG_NAME_OPT,
1484 MAINTAIN_NODE_HEALTH_OPT, UIDPOOL_OPT, DRBD_HELPER_OPT, NODRBD_STORAGE_OPT,
1485 DEFAULT_IALLOCATOR_OPT, PRIMARY_IP_VERSION_OPT, PREALLOC_WIPE_DISKS_OPT,
1486 NODE_PARAMS_OPT, GLOBAL_SHARED_FILEDIR_OPT, USE_EXTERNAL_MIP_SCRIPT,
1487 DISK_PARAMS_OPT, HV_STATE_OPT, DISK_STATE_OPT] + INSTANCE_POLICY_OPTS,
1488 "[opts...] <cluster_name>", "Initialises a new cluster configuration"),
1490 DestroyCluster, ARGS_NONE, [YES_DOIT_OPT],
1491 "", "Destroy cluster"),
1493 RenameCluster, [ArgHost(min=1, max=1)],
1494 [FORCE_OPT, DRY_RUN_OPT],
1496 "Renames the cluster"),
1498 RedistributeConfig, ARGS_NONE, [SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1499 "", "Forces a push of the configuration file and ssconf files"
1500 " to the nodes in the cluster"),
1502 VerifyCluster, ARGS_NONE,
1503 [VERBOSE_OPT, DEBUG_SIMERR_OPT, ERROR_CODES_OPT, NONPLUS1_OPT,
1504 DRY_RUN_OPT, PRIORITY_OPT, NODEGROUP_OPT, IGNORE_ERRORS_OPT],
1505 "", "Does a check on the cluster configuration"),
1507 VerifyDisks, ARGS_NONE, [PRIORITY_OPT],
1508 "", "Does a check on the cluster disk status"),
1509 "repair-disk-sizes": (
1510 RepairDiskSizes, ARGS_MANY_INSTANCES, [DRY_RUN_OPT, PRIORITY_OPT],
1511 "[instance...]", "Updates mismatches in recorded disk sizes"),
1512 "master-failover": (
1513 MasterFailover, ARGS_NONE, [NOVOTING_OPT, FORCE_FAILOVER],
1514 "", "Makes the current node the master"),
1516 MasterPing, ARGS_NONE, [],
1517 "", "Checks if the master is alive"),
1519 ShowClusterVersion, ARGS_NONE, [],
1520 "", "Shows the cluster version"),
1522 ShowClusterMaster, ARGS_NONE, [],
1523 "", "Shows the cluster master"),
1525 ClusterCopyFile, [ArgFile(min=1, max=1)],
1526 [NODE_LIST_OPT, USE_REPL_NET_OPT, NODEGROUP_OPT],
1527 "[-n node...] <filename>", "Copies a file to all (or only some) nodes"),
1529 RunClusterCommand, [ArgCommand(min=1)],
1530 [NODE_LIST_OPT, NODEGROUP_OPT, SHOW_MACHINE_OPT, FAILURE_ONLY_OPT],
1531 "[-n node...] <command>", "Runs a command on all (or only some) nodes"),
1533 ShowClusterConfig, ARGS_NONE, [ROMAN_OPT],
1534 "[--roman]", "Show cluster configuration"),
1536 ListTags, ARGS_NONE, [], "", "List the tags of the cluster"),
1538 AddTags, [ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
1539 "tag...", "Add tags to the cluster"),
1541 RemoveTags, [ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
1542 "tag...", "Remove tags from the cluster"),
1544 SearchTags, [ArgUnknown(min=1, max=1)], [PRIORITY_OPT], "",
1545 "Searches the tags on all objects on"
1546 " the cluster for a given pattern (regex)"),
1549 [ArgChoice(min=1, max=1, choices=["drain", "undrain", "info"])],
1550 [], "drain|undrain|info", "Change queue properties"),
1553 [ArgChoice(min=1, max=1, choices=["pause", "continue", "info"]),
1554 ArgSuggest(min=0, max=1, choices=["30m", "1h", "4h"])],
1556 "{pause <timespec>|continue|info}", "Change watcher properties"),
1558 SetClusterParams, ARGS_NONE,
1559 [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, HVLIST_OPT, MASTER_NETDEV_OPT,
1560 MASTER_NETMASK_OPT, NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, VG_NAME_OPT,
1561 MAINTAIN_NODE_HEALTH_OPT, UIDPOOL_OPT, ADD_UIDS_OPT, REMOVE_UIDS_OPT,
1562 DRBD_HELPER_OPT, NODRBD_STORAGE_OPT, DEFAULT_IALLOCATOR_OPT,
1563 RESERVED_LVS_OPT, DRY_RUN_OPT, PRIORITY_OPT, PREALLOC_WIPE_DISKS_OPT,
1564 NODE_PARAMS_OPT, USE_EXTERNAL_MIP_SCRIPT, DISK_PARAMS_OPT, HV_STATE_OPT,
1565 DISK_STATE_OPT, SUBMIT_OPT] +
1566 INSTANCE_POLICY_OPTS,
1568 "Alters the parameters of the cluster"),
1570 RenewCrypto, ARGS_NONE,
1571 [NEW_CLUSTER_CERT_OPT, NEW_RAPI_CERT_OPT, RAPI_CERT_OPT,
1572 NEW_CONFD_HMAC_KEY_OPT, FORCE_OPT,
1573 NEW_CLUSTER_DOMAIN_SECRET_OPT, CLUSTER_DOMAIN_SECRET_OPT,
1574 NEW_SPICE_CERT_OPT, SPICE_CERT_OPT, SPICE_CACERT_OPT],
1576 "Renews cluster certificates, keys and secrets"),
1578 Epo, [ArgUnknown()],
1579 [FORCE_OPT, ON_OPT, GROUPS_OPT, ALL_OPT, OOB_TIMEOUT_OPT,
1580 SHUTDOWN_TIMEOUT_OPT, POWER_DELAY_OPT],
1582 "Performs an emergency power-off on given args"),
1583 "activate-master-ip": (
1584 ActivateMasterIp, ARGS_NONE, [], "", "Activates the master IP"),
1585 "deactivate-master-ip": (
1586 DeactivateMasterIp, ARGS_NONE, [CONFIRM_OPT], "",
1587 "Deactivates the master IP"),
1591 #: dictionary with aliases for commands
1593 "masterfailover": "master-failover",
1599 return GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER},