4 # Copyright (C) 2006, 2007, 2008, 2009, 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 """Node 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-node
31 from ganeti.cli import *
32 from ganeti import cli
33 from ganeti import bootstrap
34 from ganeti import opcodes
35 from ganeti import utils
36 from ganeti import constants
37 from ganeti import errors
38 from ganeti import netutils
39 from cStringIO import StringIO
42 #: default list of field for L{ListNodes}
44 "name", "dtotal", "dfree",
45 "mtotal", "mnode", "mfree",
46 "pinst_cnt", "sinst_cnt",
50 #: Default field list for L{ListVolumes}
51 _LIST_VOL_DEF_FIELDS = ["node", "phys", "vg", "name", "size", "instance"]
54 #: default list of field for L{ListStorage}
55 _LIST_STOR_DEF_FIELDS = [
62 constants.SF_ALLOCATABLE,
66 #: default list of power commands
67 _LIST_POWER_COMMANDS = ["on", "off", "cycle", "status"]
70 #: headers (and full field list) for L{ListStorage}
71 _LIST_STOR_HEADERS = {
72 constants.SF_NODE: "Node",
73 constants.SF_TYPE: "Type",
74 constants.SF_NAME: "Name",
75 constants.SF_SIZE: "Size",
76 constants.SF_USED: "Used",
77 constants.SF_FREE: "Free",
78 constants.SF_ALLOCATABLE: "Allocatable",
82 #: User-facing storage unit types
83 _USER_STORAGE_TYPE = {
84 constants.ST_FILE: "file",
85 constants.ST_LVM_PV: "lvm-pv",
86 constants.ST_LVM_VG: "lvm-vg",
90 cli_option("-t", "--storage-type",
91 dest="user_storage_type",
92 choices=_USER_STORAGE_TYPE.keys(),
94 metavar="STORAGE_TYPE",
95 help=("Storage type (%s)" %
96 utils.CommaJoin(_USER_STORAGE_TYPE.keys())))
98 _REPAIRABLE_STORAGE_TYPES = \
99 [st for st, so in constants.VALID_STORAGE_OPERATIONS.iteritems()
100 if constants.SO_FIX_CONSISTENCY in so]
102 _MODIFIABLE_STORAGE_TYPES = constants.MODIFIABLE_STORAGE_FIELDS.keys()
105 _OOB_COMMAND_ASK = frozenset([constants.OOB_POWER_OFF,
106 constants.OOB_POWER_CYCLE])
109 _ENV_OVERRIDE = frozenset(["list"])
112 NONODE_SETUP_OPT = cli_option("--no-node-setup", default=True,
113 action="store_false", dest="node_setup",
114 help=("Do not make initial SSH setup on remote"
115 " node (needs to be done manually)"))
117 IGNORE_STATUS_OPT = cli_option("--ignore-status", default=False,
118 action="store_true", dest="ignore_status",
119 help=("Ignore the Node(s) offline status"
120 " (potentially DANGEROUS)"))
123 def ConvertStorageType(user_storage_type):
124 """Converts a user storage type to its internal name.
128 return _USER_STORAGE_TYPE[user_storage_type]
130 raise errors.OpPrereqError("Unknown storage type: %s" % user_storage_type,
134 def _RunSetupSSH(options, nodes):
135 """Wrapper around utils.RunCmd to call setup-ssh
137 @param options: The command line options
138 @param nodes: The nodes to setup
142 assert nodes, "Empty node list"
144 cmd = [constants.SETUP_SSH]
146 # Pass --debug|--verbose to the external script if set on our invocation
147 # --debug overrides --verbose
149 cmd.append("--debug")
150 elif options.verbose:
151 cmd.append("--verbose")
152 if not options.ssh_key_check:
153 cmd.append("--no-ssh-key-check")
154 if options.force_join:
155 cmd.append("--force-join")
159 result = utils.RunCmd(cmd, interactive=True)
162 errmsg = ("Command '%s' failed with exit code %s; output %r" %
163 (result.cmd, result.exit_code, result.output))
164 raise errors.OpExecError(errmsg)
168 def AddNode(opts, args):
169 """Add a node to the cluster.
171 @param opts: the command line options selected by the user
173 @param args: should contain only one element, the new node name
175 @return: the desired exit code
179 node = netutils.GetHostname(name=args[0]).name
183 output = cl.QueryNodes(names=[node], fields=["name", "sip", "master"],
185 node_exists, sip, is_master = output[0]
186 except (errors.OpPrereqError, errors.OpExecError):
192 ToStderr("Node %s not in the cluster"
193 " - please retry without '--readd'", node)
196 ToStderr("Node %s is the master, cannot readd", node)
200 ToStderr("Node %s already in the cluster (as %s)"
201 " - please retry with '--readd'", node, node_exists)
203 sip = opts.secondary_ip
205 # read the cluster name from the master
206 output = cl.QueryConfigValues(["cluster_name"])
207 cluster_name = output[0]
209 if not readd and opts.node_setup:
210 ToStderr("-- WARNING -- \n"
211 "Performing this operation is going to replace the ssh daemon"
213 "on the target machine (%s) with the ones of the"
215 "and grant full intra-cluster ssh root access to/from it\n", node)
218 _RunSetupSSH(opts, [node])
220 bootstrap.SetupNodeDaemon(cluster_name, node, opts.ssh_key_check)
223 disk_state = utils.FlatToDict(opts.disk_state)
227 hv_state = dict(opts.hv_state)
229 op = opcodes.OpNodeAdd(node_name=args[0], secondary_ip=sip,
230 readd=opts.readd, group=opts.nodegroup,
231 vm_capable=opts.vm_capable, ndparams=opts.ndparams,
232 master_capable=opts.master_capable,
233 disk_state=disk_state,
235 SubmitOpCode(op, opts=opts)
238 def ListNodes(opts, args):
239 """List nodes and their properties.
241 @param opts: the command line options selected by the user
243 @param args: nodes to list, or empty for all
245 @return: the desired exit code
248 selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
250 fmtoverride = dict.fromkeys(["pinst_list", "sinst_list", "tags"],
253 return GenericList(constants.QR_NODE, selected_fields, args, opts.units,
254 opts.separator, not opts.no_headers,
255 format_override=fmtoverride, verbose=opts.verbose,
256 force_filter=opts.force_filter)
259 def ListNodeFields(opts, args):
262 @param opts: the command line options selected by the user
264 @param args: fields to list, or empty for all
266 @return: the desired exit code
269 return GenericListFields(constants.QR_NODE, args, opts.separator,
273 def EvacuateNode(opts, args):
274 """Relocate all secondary instance from a node.
276 @param opts: the command line options selected by the user
278 @param args: should be an empty list
280 @return: the desired exit code
283 if opts.dst_node is not None:
284 ToStderr("New secondary node given (disabling iallocator), hence evacuating"
285 " secondary instances only.")
286 opts.secondary_only = True
287 opts.primary_only = False
289 if opts.secondary_only and opts.primary_only:
290 raise errors.OpPrereqError("Only one of the --primary-only and"
291 " --secondary-only options can be passed",
293 elif opts.primary_only:
294 mode = constants.NODE_EVAC_PRI
295 elif opts.secondary_only:
296 mode = constants.NODE_EVAC_SEC
298 mode = constants.NODE_EVAC_ALL
300 # Determine affected instances
303 if not opts.secondary_only:
304 fields.append("pinst_list")
305 if not opts.primary_only:
306 fields.append("sinst_list")
310 result = cl.QueryNodes(names=args, fields=fields, use_locking=False)
311 instances = set(itertools.chain(*itertools.chain(*itertools.chain(result))))
314 # No instances to evacuate
315 ToStderr("No instances to evacuate on node(s) %s, exiting.",
316 utils.CommaJoin(args))
317 return constants.EXIT_SUCCESS
319 if not (opts.force or
320 AskUser("Relocate instance(s) %s from node(s) %s?" %
321 (utils.CommaJoin(utils.NiceSort(instances)),
322 utils.CommaJoin(args)))):
323 return constants.EXIT_CONFIRMATION
326 op = opcodes.OpNodeEvacuate(node_name=args[0], mode=mode,
327 remote_node=opts.dst_node,
328 iallocator=opts.iallocator,
329 early_release=opts.early_release)
330 result = SubmitOrSend(op, opts, cl=cl)
332 # Keep track of submitted jobs
333 jex = JobExecutor(cl=cl, opts=opts)
335 for (status, job_id) in result[constants.JOB_IDS_KEY]:
336 jex.AddJobId(None, status, job_id)
338 results = jex.GetResults()
339 bad_cnt = len([row for row in results if not row[0]])
341 ToStdout("All instances evacuated successfully.")
342 rcode = constants.EXIT_SUCCESS
344 ToStdout("There were %s errors during the evacuation.", bad_cnt)
345 rcode = constants.EXIT_FAILURE
350 def FailoverNode(opts, args):
351 """Failover all primary instance on a node.
353 @param opts: the command line options selected by the user
355 @param args: should be an empty list
357 @return: the desired exit code
362 selected_fields = ["name", "pinst_list"]
364 # these fields are static data anyway, so it doesn't matter, but
365 # locking=True should be safer
366 result = cl.QueryNodes(names=args, fields=selected_fields,
368 node, pinst = result[0]
371 ToStderr("No primary instances on node %s, exiting.", node)
374 pinst = utils.NiceSort(pinst)
378 if not force and not AskUser("Fail over instance(s) %s?" %
379 (",".join("'%s'" % name for name in pinst))):
382 jex = JobExecutor(cl=cl, opts=opts)
384 op = opcodes.OpInstanceFailover(instance_name=iname,
385 ignore_consistency=opts.ignore_consistency,
386 iallocator=opts.iallocator)
387 jex.QueueJob(iname, op)
388 results = jex.GetResults()
389 bad_cnt = len([row for row in results if not row[0]])
391 ToStdout("All %d instance(s) failed over successfully.", len(results))
393 ToStdout("There were errors during the failover:\n"
394 "%d error(s) out of %d instance(s).", bad_cnt, len(results))
398 def MigrateNode(opts, args):
399 """Migrate all primary instance on a node.
404 selected_fields = ["name", "pinst_list"]
406 result = cl.QueryNodes(names=args, fields=selected_fields, use_locking=False)
407 ((node, pinst), ) = result
410 ToStdout("No primary instances on node %s, exiting." % node)
413 pinst = utils.NiceSort(pinst)
416 AskUser("Migrate instance(s) %s?" %
417 utils.CommaJoin(utils.NiceSort(pinst)))):
418 return constants.EXIT_CONFIRMATION
420 # this should be removed once --non-live is deprecated
421 if not opts.live and opts.migration_mode is not None:
422 raise errors.OpPrereqError("Only one of the --non-live and "
423 "--migration-mode options can be passed",
425 if not opts.live: # --non-live passed
426 mode = constants.HT_MIGRATION_NONLIVE
428 mode = opts.migration_mode
430 op = opcodes.OpNodeMigrate(node_name=args[0], mode=mode,
431 iallocator=opts.iallocator,
432 target_node=opts.dst_node,
433 allow_runtime_changes=opts.allow_runtime_chgs,
434 ignore_ipolicy=opts.ignore_ipolicy)
436 result = SubmitOrSend(op, opts, cl=cl)
438 # Keep track of submitted jobs
439 jex = JobExecutor(cl=cl, opts=opts)
441 for (status, job_id) in result[constants.JOB_IDS_KEY]:
442 jex.AddJobId(None, status, job_id)
444 results = jex.GetResults()
445 bad_cnt = len([row for row in results if not row[0]])
447 ToStdout("All instances migrated successfully.")
448 rcode = constants.EXIT_SUCCESS
450 ToStdout("There were %s errors during the node migration.", bad_cnt)
451 rcode = constants.EXIT_FAILURE
456 def ShowNodeConfig(opts, args):
457 """Show node information.
459 @param opts: the command line options selected by the user
461 @param args: should either be an empty list, in which case
462 we show information about all nodes, or should contain
463 a list of nodes to be queried for information
465 @return: the desired exit code
469 result = cl.QueryNodes(fields=["name", "pip", "sip",
470 "pinst_list", "sinst_list",
471 "master_candidate", "drained", "offline",
472 "master_capable", "vm_capable", "powered",
473 "ndparams", "custom_ndparams"],
474 names=args, use_locking=False)
476 for (name, primary_ip, secondary_ip, pinst, sinst, is_mc, drained, offline,
477 master_capable, vm_capable, powered, ndparams,
478 ndparams_custom) in result:
479 ToStdout("Node name: %s", name)
480 ToStdout(" primary ip: %s", primary_ip)
481 ToStdout(" secondary ip: %s", secondary_ip)
482 ToStdout(" master candidate: %s", is_mc)
483 ToStdout(" drained: %s", drained)
484 ToStdout(" offline: %s", offline)
485 if powered is not None:
486 ToStdout(" powered: %s", powered)
487 ToStdout(" master_capable: %s", master_capable)
488 ToStdout(" vm_capable: %s", vm_capable)
491 ToStdout(" primary for instances:")
492 for iname in utils.NiceSort(pinst):
493 ToStdout(" - %s", iname)
495 ToStdout(" primary for no instances")
497 ToStdout(" secondary for instances:")
498 for iname in utils.NiceSort(sinst):
499 ToStdout(" - %s", iname)
501 ToStdout(" secondary for no instances")
502 ToStdout(" node parameters:")
504 FormatParameterDict(buf, ndparams_custom, ndparams, level=2)
505 ToStdout(buf.getvalue().rstrip("\n"))
510 def RemoveNode(opts, args):
511 """Remove a node from the cluster.
513 @param opts: the command line options selected by the user
515 @param args: should contain only one element, the name of
516 the node to be removed
518 @return: the desired exit code
521 op = opcodes.OpNodeRemove(node_name=args[0])
522 SubmitOpCode(op, opts=opts)
526 def PowercycleNode(opts, args):
527 """Remove a node from the cluster.
529 @param opts: the command line options selected by the user
531 @param args: should contain only one element, the name of
532 the node to be removed
534 @return: the desired exit code
538 if (not opts.confirm and
539 not AskUser("Are you sure you want to hard powercycle node %s?" % node)):
542 op = opcodes.OpNodePowercycle(node_name=node, force=opts.force)
543 result = SubmitOrSend(op, opts)
549 def PowerNode(opts, args):
550 """Change/ask power state of a node.
552 @param opts: the command line options selected by the user
554 @param args: should contain only one element, the name of
555 the node to be removed
557 @return: the desired exit code
560 command = args.pop(0)
565 headers = {"node": "Node", "status": "Status"}
567 if command not in _LIST_POWER_COMMANDS:
568 ToStderr("power subcommand %s not supported." % command)
569 return constants.EXIT_FAILURE
571 oob_command = "power-%s" % command
573 if oob_command in _OOB_COMMAND_ASK:
575 ToStderr("Please provide at least one node for this command")
576 return constants.EXIT_FAILURE
577 elif not opts.force and not ConfirmOperation(args, "nodes",
578 "power %s" % command):
579 return constants.EXIT_FAILURE
583 if not opts.ignore_status and oob_command == constants.OOB_POWER_OFF:
584 # TODO: This is a little ugly as we can't catch and revert
586 opcodelist.append(opcodes.OpNodeSetParams(node_name=node, offline=True,
587 auto_promote=opts.auto_promote))
589 opcodelist.append(opcodes.OpOobCommand(node_names=args,
591 ignore_status=opts.ignore_status,
592 timeout=opts.oob_timeout,
593 power_delay=opts.power_delay))
595 cli.SetGenericOpcodeOpts(opcodelist, opts)
597 job_id = cli.SendJob(opcodelist)
599 # We just want the OOB Opcode status
600 # If it fails PollJob gives us the error message in it
601 result = cli.PollJob(job_id)[-1]
605 for node_result in result:
606 (node_tuple, data_tuple) = node_result
607 (_, node_name) = node_tuple
608 (data_status, data_node) = data_tuple
609 if data_status == constants.RS_NORMAL:
610 if oob_command == constants.OOB_POWER_STATUS:
611 if data_node[constants.OOB_POWER_STATUS_POWERED]:
615 data.append([node_name, text])
617 # We don't expect data here, so we just say, it was successfully invoked
618 data.append([node_name, "invoked"])
621 data.append([node_name, cli.FormatResultError(data_status, True)])
623 data = GenerateTable(separator=opts.separator, headers=headers,
624 fields=["node", "status"], data=data)
630 return constants.EXIT_FAILURE
632 return constants.EXIT_SUCCESS
635 def Health(opts, args):
636 """Show health of a node using OOB.
638 @param opts: the command line options selected by the user
640 @param args: should contain only one element, the name of
641 the node to be removed
643 @return: the desired exit code
646 op = opcodes.OpOobCommand(node_names=args, command=constants.OOB_HEALTH,
647 timeout=opts.oob_timeout)
648 result = SubmitOpCode(op, opts=opts)
653 headers = {"node": "Node", "status": "Status"}
657 for node_result in result:
658 (node_tuple, data_tuple) = node_result
659 (_, node_name) = node_tuple
660 (data_status, data_node) = data_tuple
661 if data_status == constants.RS_NORMAL:
662 data.append([node_name, "%s=%s" % tuple(data_node[0])])
663 for item, status in data_node[1:]:
664 data.append(["", "%s=%s" % (item, status)])
667 data.append([node_name, cli.FormatResultError(data_status, True)])
669 data = GenerateTable(separator=opts.separator, headers=headers,
670 fields=["node", "status"], data=data)
676 return constants.EXIT_FAILURE
678 return constants.EXIT_SUCCESS
681 def ListVolumes(opts, args):
682 """List logical volumes on node(s).
684 @param opts: the command line options selected by the user
686 @param args: should either be an empty list, in which case
687 we list data for all nodes, or contain a list of nodes
688 to display data only for those
690 @return: the desired exit code
693 selected_fields = ParseFields(opts.output, _LIST_VOL_DEF_FIELDS)
695 op = opcodes.OpNodeQueryvols(nodes=args, output_fields=selected_fields)
696 output = SubmitOpCode(op, opts=opts)
698 if not opts.no_headers:
699 headers = {"node": "Node", "phys": "PhysDev",
700 "vg": "VG", "name": "Name",
701 "size": "Size", "instance": "Instance"}
705 unitfields = ["size"]
709 data = GenerateTable(separator=opts.separator, headers=headers,
710 fields=selected_fields, unitfields=unitfields,
711 numfields=numfields, data=output, units=opts.units)
719 def ListStorage(opts, args):
720 """List physical volumes on node(s).
722 @param opts: the command line options selected by the user
724 @param args: should either be an empty list, in which case
725 we list data for all nodes, or contain a list of nodes
726 to display data only for those
728 @return: the desired exit code
731 # TODO: Default to ST_FILE if LVM is disabled on the cluster
732 if opts.user_storage_type is None:
733 opts.user_storage_type = constants.ST_LVM_PV
735 storage_type = ConvertStorageType(opts.user_storage_type)
737 selected_fields = ParseFields(opts.output, _LIST_STOR_DEF_FIELDS)
739 op = opcodes.OpNodeQueryStorage(nodes=args,
740 storage_type=storage_type,
741 output_fields=selected_fields)
742 output = SubmitOpCode(op, opts=opts)
744 if not opts.no_headers:
746 constants.SF_NODE: "Node",
747 constants.SF_TYPE: "Type",
748 constants.SF_NAME: "Name",
749 constants.SF_SIZE: "Size",
750 constants.SF_USED: "Used",
751 constants.SF_FREE: "Free",
752 constants.SF_ALLOCATABLE: "Allocatable",
757 unitfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
758 numfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
760 # change raw values to nicer strings
762 for idx, field in enumerate(selected_fields):
764 if field == constants.SF_ALLOCATABLE:
771 data = GenerateTable(separator=opts.separator, headers=headers,
772 fields=selected_fields, unitfields=unitfields,
773 numfields=numfields, data=output, units=opts.units)
781 def ModifyStorage(opts, args):
782 """Modify storage volume on a node.
784 @param opts: the command line options selected by the user
786 @param args: should contain 3 items: node name, storage type and volume name
788 @return: the desired exit code
791 (node_name, user_storage_type, volume_name) = args
793 storage_type = ConvertStorageType(user_storage_type)
797 if opts.allocatable is not None:
798 changes[constants.SF_ALLOCATABLE] = opts.allocatable
801 op = opcodes.OpNodeModifyStorage(node_name=node_name,
802 storage_type=storage_type,
805 SubmitOrSend(op, opts)
807 ToStderr("No changes to perform, exiting.")
810 def RepairStorage(opts, args):
811 """Repairs a storage volume on a node.
813 @param opts: the command line options selected by the user
815 @param args: should contain 3 items: node name, storage type and volume name
817 @return: the desired exit code
820 (node_name, user_storage_type, volume_name) = args
822 storage_type = ConvertStorageType(user_storage_type)
824 op = opcodes.OpRepairNodeStorage(node_name=node_name,
825 storage_type=storage_type,
827 ignore_consistency=opts.ignore_consistency)
828 SubmitOrSend(op, opts)
831 def SetNodeParams(opts, args):
834 @param opts: the command line options selected by the user
836 @param args: should contain only one element, the node name
838 @return: the desired exit code
841 all_changes = [opts.master_candidate, opts.drained, opts.offline,
842 opts.master_capable, opts.vm_capable, opts.secondary_ip,
844 if (all_changes.count(None) == len(all_changes) and
845 not (opts.hv_state or opts.disk_state)):
846 ToStderr("Please give at least one of the parameters.")
850 disk_state = utils.FlatToDict(opts.disk_state)
854 hv_state = dict(opts.hv_state)
856 op = opcodes.OpNodeSetParams(node_name=args[0],
857 master_candidate=opts.master_candidate,
858 offline=opts.offline,
859 drained=opts.drained,
860 master_capable=opts.master_capable,
861 vm_capable=opts.vm_capable,
862 secondary_ip=opts.secondary_ip,
864 ndparams=opts.ndparams,
865 auto_promote=opts.auto_promote,
866 powered=opts.node_powered,
868 disk_state=disk_state)
870 # even if here we process the result, we allow submit only
871 result = SubmitOrSend(op, opts)
874 ToStdout("Modified node %s", args[0])
875 for param, data in result:
876 ToStdout(" - %-5s -> %s", param, data)
882 AddNode, [ArgHost(min=1, max=1)],
883 [SECONDARY_IP_OPT, READD_OPT, NOSSH_KEYCHECK_OPT, NODE_FORCE_JOIN_OPT,
884 NONODE_SETUP_OPT, VERBOSE_OPT, NODEGROUP_OPT, PRIORITY_OPT,
885 CAPAB_MASTER_OPT, CAPAB_VM_OPT, NODE_PARAMS_OPT, HV_STATE_OPT,
887 "[-s ip] [--readd] [--no-ssh-key-check] [--force-join]"
888 " [--no-node-setup] [--verbose]"
890 "Add a node to the cluster"),
892 EvacuateNode, ARGS_ONE_NODE,
893 [FORCE_OPT, IALLOCATOR_OPT, NEW_SECONDARY_OPT, EARLY_RELEASE_OPT,
894 PRIORITY_OPT, PRIMARY_ONLY_OPT, SECONDARY_ONLY_OPT, SUBMIT_OPT],
895 "[-f] {-I <iallocator> | -n <dst>} [-p | -s] [options...] <node>",
896 "Relocate the primary and/or secondary instances from a node"),
898 FailoverNode, ARGS_ONE_NODE, [FORCE_OPT, IGNORE_CONSIST_OPT,
899 IALLOCATOR_OPT, PRIORITY_OPT],
901 "Stops the primary instances on a node and start them on their"
902 " secondary node (only for instances with drbd disk template)"),
904 MigrateNode, ARGS_ONE_NODE,
905 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, DST_NODE_OPT,
906 IALLOCATOR_OPT, PRIORITY_OPT, IGNORE_IPOLICY_OPT,
907 NORUNTIME_CHGS_OPT, SUBMIT_OPT, PRIORITY_OPT],
909 "Migrate all the primary instance on a node away from it"
910 " (only for instances of type drbd)"),
912 ShowNodeConfig, ARGS_MANY_NODES, [],
913 "[<node_name>...]", "Show information about the node(s)"),
915 ListNodes, ARGS_MANY_NODES,
916 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT,
919 "Lists the nodes in the cluster. The available fields can be shown using"
920 " the \"list-fields\" command (see the man page for details)."
921 " The default field list is (in order): %s." %
922 utils.CommaJoin(_LIST_DEF_FIELDS)),
924 ListNodeFields, [ArgUnknown()],
925 [NOHDR_OPT, SEP_OPT],
927 "Lists all available fields for nodes"),
929 SetNodeParams, ARGS_ONE_NODE,
930 [FORCE_OPT, SUBMIT_OPT, MC_OPT, DRAINED_OPT, OFFLINE_OPT,
931 CAPAB_MASTER_OPT, CAPAB_VM_OPT, SECONDARY_IP_OPT,
932 AUTO_PROMOTE_OPT, DRY_RUN_OPT, PRIORITY_OPT, NODE_PARAMS_OPT,
933 NODE_POWERED_OPT, HV_STATE_OPT, DISK_STATE_OPT],
934 "<node_name>", "Alters the parameters of a node"),
936 PowercycleNode, ARGS_ONE_NODE,
937 [FORCE_OPT, CONFIRM_OPT, DRY_RUN_OPT, PRIORITY_OPT, SUBMIT_OPT],
938 "<node_name>", "Tries to forcefully powercycle a node"),
941 [ArgChoice(min=1, max=1, choices=_LIST_POWER_COMMANDS),
943 [SUBMIT_OPT, AUTO_PROMOTE_OPT, PRIORITY_OPT, IGNORE_STATUS_OPT,
944 FORCE_OPT, NOHDR_OPT, SEP_OPT, OOB_TIMEOUT_OPT, POWER_DELAY_OPT],
945 "on|off|cycle|status [nodes...]",
946 "Change power state of node by calling out-of-band helper."),
948 RemoveNode, ARGS_ONE_NODE, [DRY_RUN_OPT, PRIORITY_OPT],
949 "<node_name>", "Removes a node from the cluster"),
951 ListVolumes, [ArgNode()],
952 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, PRIORITY_OPT],
953 "[<node_name>...]", "List logical volumes on node(s)"),
955 ListStorage, ARGS_MANY_NODES,
956 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, _STORAGE_TYPE_OPT,
958 "[<node_name>...]", "List physical volumes on node(s). The available"
959 " fields are (see the man page for details): %s." %
960 (utils.CommaJoin(_LIST_STOR_HEADERS))),
963 [ArgNode(min=1, max=1),
964 ArgChoice(min=1, max=1, choices=_MODIFIABLE_STORAGE_TYPES),
965 ArgFile(min=1, max=1)],
966 [ALLOCATABLE_OPT, DRY_RUN_OPT, PRIORITY_OPT, SUBMIT_OPT],
967 "<node_name> <storage_type> <name>", "Modify storage volume on a node"),
970 [ArgNode(min=1, max=1),
971 ArgChoice(min=1, max=1, choices=_REPAIRABLE_STORAGE_TYPES),
972 ArgFile(min=1, max=1)],
973 [IGNORE_CONSIST_OPT, DRY_RUN_OPT, PRIORITY_OPT, SUBMIT_OPT],
974 "<node_name> <storage_type> <name>",
975 "Repairs a storage volume on a node"),
977 ListTags, ARGS_ONE_NODE, [],
978 "<node_name>", "List the tags of the given node"),
980 AddTags, [ArgNode(min=1, max=1), ArgUnknown()],
981 [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
982 "<node_name> tag...", "Add tags to the given node"),
984 RemoveTags, [ArgNode(min=1, max=1), ArgUnknown()],
985 [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
986 "<node_name> tag...", "Remove tags from the given node"),
988 Health, ARGS_MANY_NODES,
989 [NOHDR_OPT, SEP_OPT, PRIORITY_OPT, OOB_TIMEOUT_OPT],
990 "[<node_name>...]", "List health of node(s) using out-of-band"),
993 #: dictionary with aliases for commands
1000 return GenericMain(commands, aliases=aliases,
1001 override={"tag_type": constants.TAG_NODE},
1002 env_override=_ENV_OVERRIDE)