4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 """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
141 cmd = [constants.SETUP_SSH]
143 # Pass --debug|--verbose to the external script if set on our invocation
144 # --debug overrides --verbose
146 cmd.append("--debug")
147 elif options.verbose:
148 cmd.append("--verbose")
149 if not options.ssh_key_check:
150 cmd.append("--no-ssh-key-check")
151 if options.force_join:
152 cmd.append("--force-join")
156 result = utils.RunCmd(cmd, interactive=True)
159 errmsg = ("Command '%s' failed with exit code %s; output %r" %
160 (result.cmd, result.exit_code, result.output))
161 raise errors.OpExecError(errmsg)
165 def AddNode(opts, args):
166 """Add a node to the cluster.
168 @param opts: the command line options selected by the user
170 @param args: should contain only one element, the new node name
172 @return: the desired exit code
176 node = netutils.GetHostname(name=args[0]).name
180 output = cl.QueryNodes(names=[node], fields=["name", "sip", "master"],
182 node_exists, sip, is_master = output[0]
183 except (errors.OpPrereqError, errors.OpExecError):
189 ToStderr("Node %s not in the cluster"
190 " - please retry without '--readd'", node)
193 ToStderr("Node %s is the master, cannot readd", node)
197 ToStderr("Node %s already in the cluster (as %s)"
198 " - please retry with '--readd'", node, node_exists)
200 sip = opts.secondary_ip
202 # read the cluster name from the master
203 output = cl.QueryConfigValues(["cluster_name"])
204 cluster_name = output[0]
206 if not readd and opts.node_setup:
207 ToStderr("-- WARNING -- \n"
208 "Performing this operation is going to replace the ssh daemon"
210 "on the target machine (%s) with the ones of the"
212 "and grant full intra-cluster ssh root access to/from it\n", node)
215 _RunSetupSSH(opts, [node])
217 bootstrap.SetupNodeDaemon(cluster_name, node, opts.ssh_key_check)
220 disk_state = utils.FlatToDict(opts.disk_state)
224 hv_state = dict(opts.hv_state)
226 op = opcodes.OpNodeAdd(node_name=args[0], secondary_ip=sip,
227 readd=opts.readd, group=opts.nodegroup,
228 vm_capable=opts.vm_capable, ndparams=opts.ndparams,
229 master_capable=opts.master_capable,
230 disk_state=disk_state,
232 SubmitOpCode(op, opts=opts)
235 def ListNodes(opts, args):
236 """List nodes and their properties.
238 @param opts: the command line options selected by the user
240 @param args: nodes to list, or empty for all
242 @return: the desired exit code
245 selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
247 fmtoverride = dict.fromkeys(["pinst_list", "sinst_list", "tags"],
250 return GenericList(constants.QR_NODE, selected_fields, args, opts.units,
251 opts.separator, not opts.no_headers,
252 format_override=fmtoverride, verbose=opts.verbose,
253 force_filter=opts.force_filter)
256 def ListNodeFields(opts, args):
259 @param opts: the command line options selected by the user
261 @param args: fields to list, or empty for all
263 @return: the desired exit code
266 return GenericListFields(constants.QR_NODE, args, opts.separator,
270 def EvacuateNode(opts, args):
271 """Relocate all secondary instance from a node.
273 @param opts: the command line options selected by the user
275 @param args: should be an empty list
277 @return: the desired exit code
280 if opts.dst_node is not None:
281 ToStderr("New secondary node given (disabling iallocator), hence evacuating"
282 " secondary instances only.")
283 opts.secondary_only = True
284 opts.primary_only = False
286 if opts.secondary_only and opts.primary_only:
287 raise errors.OpPrereqError("Only one of the --primary-only and"
288 " --secondary-only options can be passed",
290 elif opts.primary_only:
291 mode = constants.NODE_EVAC_PRI
292 elif opts.secondary_only:
293 mode = constants.NODE_EVAC_SEC
295 mode = constants.NODE_EVAC_ALL
297 # Determine affected instances
300 if not opts.secondary_only:
301 fields.append("pinst_list")
302 if not opts.primary_only:
303 fields.append("sinst_list")
307 result = cl.QueryNodes(names=args, fields=fields, use_locking=False)
308 instances = set(itertools.chain(*itertools.chain(*itertools.chain(result))))
311 # No instances to evacuate
312 ToStderr("No instances to evacuate on node(s) %s, exiting.",
313 utils.CommaJoin(args))
314 return constants.EXIT_SUCCESS
316 if not (opts.force or
317 AskUser("Relocate instance(s) %s from node(s) %s?" %
318 (utils.CommaJoin(utils.NiceSort(instances)),
319 utils.CommaJoin(args)))):
320 return constants.EXIT_CONFIRMATION
323 op = opcodes.OpNodeEvacuate(node_name=args[0], mode=mode,
324 remote_node=opts.dst_node,
325 iallocator=opts.iallocator,
326 early_release=opts.early_release)
327 result = SubmitOpCode(op, cl=cl, opts=opts)
329 # Keep track of submitted jobs
330 jex = JobExecutor(cl=cl, opts=opts)
332 for (status, job_id) in result[constants.JOB_IDS_KEY]:
333 jex.AddJobId(None, status, job_id)
335 results = jex.GetResults()
336 bad_cnt = len([row for row in results if not row[0]])
338 ToStdout("All instances evacuated successfully.")
339 rcode = constants.EXIT_SUCCESS
341 ToStdout("There were %s errors during the evacuation.", bad_cnt)
342 rcode = constants.EXIT_FAILURE
347 def FailoverNode(opts, args):
348 """Failover all primary instance on a node.
350 @param opts: the command line options selected by the user
352 @param args: should be an empty list
354 @return: the desired exit code
359 selected_fields = ["name", "pinst_list"]
361 # these fields are static data anyway, so it doesn't matter, but
362 # locking=True should be safer
363 result = cl.QueryNodes(names=args, fields=selected_fields,
365 node, pinst = result[0]
368 ToStderr("No primary instances on node %s, exiting.", node)
371 pinst = utils.NiceSort(pinst)
375 if not force and not AskUser("Fail over instance(s) %s?" %
376 (",".join("'%s'" % name for name in pinst))):
379 jex = JobExecutor(cl=cl, opts=opts)
381 op = opcodes.OpInstanceFailover(instance_name=iname,
382 ignore_consistency=opts.ignore_consistency,
383 iallocator=opts.iallocator)
384 jex.QueueJob(iname, op)
385 results = jex.GetResults()
386 bad_cnt = len([row for row in results if not row[0]])
388 ToStdout("All %d instance(s) failed over successfully.", len(results))
390 ToStdout("There were errors during the failover:\n"
391 "%d error(s) out of %d instance(s).", bad_cnt, len(results))
395 def MigrateNode(opts, args):
396 """Migrate all primary instance on a node.
401 selected_fields = ["name", "pinst_list"]
403 result = cl.QueryNodes(names=args, fields=selected_fields, use_locking=False)
404 ((node, pinst), ) = result
407 ToStdout("No primary instances on node %s, exiting." % node)
410 pinst = utils.NiceSort(pinst)
413 AskUser("Migrate instance(s) %s?" %
414 utils.CommaJoin(utils.NiceSort(pinst)))):
415 return constants.EXIT_CONFIRMATION
417 # this should be removed once --non-live is deprecated
418 if not opts.live and opts.migration_mode is not None:
419 raise errors.OpPrereqError("Only one of the --non-live and "
420 "--migration-mode options can be passed",
422 if not opts.live: # --non-live passed
423 mode = constants.HT_MIGRATION_NONLIVE
425 mode = opts.migration_mode
427 op = opcodes.OpNodeMigrate(node_name=args[0], mode=mode,
428 iallocator=opts.iallocator,
429 target_node=opts.dst_node,
430 allow_runtime_changes=opts.allow_runtime_chgs,
431 ignore_ipolicy=opts.ignore_ipolicy)
433 result = SubmitOpCode(op, cl=cl, opts=opts)
435 # Keep track of submitted jobs
436 jex = JobExecutor(cl=cl, opts=opts)
438 for (status, job_id) in result[constants.JOB_IDS_KEY]:
439 jex.AddJobId(None, status, job_id)
441 results = jex.GetResults()
442 bad_cnt = len([row for row in results if not row[0]])
444 ToStdout("All instances migrated successfully.")
445 rcode = constants.EXIT_SUCCESS
447 ToStdout("There were %s errors during the node migration.", bad_cnt)
448 rcode = constants.EXIT_FAILURE
453 def ShowNodeConfig(opts, args):
454 """Show node information.
456 @param opts: the command line options selected by the user
458 @param args: should either be an empty list, in which case
459 we show information about all nodes, or should contain
460 a list of nodes to be queried for information
462 @return: the desired exit code
466 result = cl.QueryNodes(fields=["name", "pip", "sip",
467 "pinst_list", "sinst_list",
468 "master_candidate", "drained", "offline",
469 "master_capable", "vm_capable", "powered",
470 "ndparams", "custom_ndparams"],
471 names=args, use_locking=False)
473 for (name, primary_ip, secondary_ip, pinst, sinst, is_mc, drained, offline,
474 master_capable, vm_capable, powered, ndparams,
475 ndparams_custom) in result:
476 ToStdout("Node name: %s", name)
477 ToStdout(" primary ip: %s", primary_ip)
478 ToStdout(" secondary ip: %s", secondary_ip)
479 ToStdout(" master candidate: %s", is_mc)
480 ToStdout(" drained: %s", drained)
481 ToStdout(" offline: %s", offline)
482 if powered is not None:
483 ToStdout(" powered: %s", powered)
484 ToStdout(" master_capable: %s", master_capable)
485 ToStdout(" vm_capable: %s", vm_capable)
488 ToStdout(" primary for instances:")
489 for iname in utils.NiceSort(pinst):
490 ToStdout(" - %s", iname)
492 ToStdout(" primary for no instances")
494 ToStdout(" secondary for instances:")
495 for iname in utils.NiceSort(sinst):
496 ToStdout(" - %s", iname)
498 ToStdout(" secondary for no instances")
499 ToStdout(" node parameters:")
501 FormatParameterDict(buf, ndparams_custom, ndparams, level=2)
502 ToStdout(buf.getvalue().rstrip("\n"))
507 def RemoveNode(opts, args):
508 """Remove a node from the cluster.
510 @param opts: the command line options selected by the user
512 @param args: should contain only one element, the name of
513 the node to be removed
515 @return: the desired exit code
518 op = opcodes.OpNodeRemove(node_name=args[0])
519 SubmitOpCode(op, opts=opts)
523 def PowercycleNode(opts, args):
524 """Remove a node from the cluster.
526 @param opts: the command line options selected by the user
528 @param args: should contain only one element, the name of
529 the node to be removed
531 @return: the desired exit code
535 if (not opts.confirm and
536 not AskUser("Are you sure you want to hard powercycle node %s?" % node)):
539 op = opcodes.OpNodePowercycle(node_name=node, force=opts.force)
540 result = SubmitOpCode(op, opts=opts)
546 def PowerNode(opts, args):
547 """Change/ask power state of a node.
549 @param opts: the command line options selected by the user
551 @param args: should contain only one element, the name of
552 the node to be removed
554 @return: the desired exit code
557 command = args.pop(0)
562 headers = {"node": "Node", "status": "Status"}
564 if command not in _LIST_POWER_COMMANDS:
565 ToStderr("power subcommand %s not supported." % command)
566 return constants.EXIT_FAILURE
568 oob_command = "power-%s" % command
570 if oob_command in _OOB_COMMAND_ASK:
572 ToStderr("Please provide at least one node for this command")
573 return constants.EXIT_FAILURE
574 elif not opts.force and not ConfirmOperation(args, "nodes",
575 "power %s" % command):
576 return constants.EXIT_FAILURE
580 if not opts.ignore_status and oob_command == constants.OOB_POWER_OFF:
581 # TODO: This is a little ugly as we can't catch and revert
583 opcodelist.append(opcodes.OpNodeSetParams(node_name=node, offline=True,
584 auto_promote=opts.auto_promote))
586 opcodelist.append(opcodes.OpOobCommand(node_names=args,
588 ignore_status=opts.ignore_status,
589 timeout=opts.oob_timeout,
590 power_delay=opts.power_delay))
592 cli.SetGenericOpcodeOpts(opcodelist, opts)
594 job_id = cli.SendJob(opcodelist)
596 # We just want the OOB Opcode status
597 # If it fails PollJob gives us the error message in it
598 result = cli.PollJob(job_id)[-1]
602 for node_result in result:
603 (node_tuple, data_tuple) = node_result
604 (_, node_name) = node_tuple
605 (data_status, data_node) = data_tuple
606 if data_status == constants.RS_NORMAL:
607 if oob_command == constants.OOB_POWER_STATUS:
608 if data_node[constants.OOB_POWER_STATUS_POWERED]:
612 data.append([node_name, text])
614 # We don't expect data here, so we just say, it was successfully invoked
615 data.append([node_name, "invoked"])
618 data.append([node_name, cli.FormatResultError(data_status, True)])
620 data = GenerateTable(separator=opts.separator, headers=headers,
621 fields=["node", "status"], data=data)
627 return constants.EXIT_FAILURE
629 return constants.EXIT_SUCCESS
632 def Health(opts, args):
633 """Show health of a node using OOB.
635 @param opts: the command line options selected by the user
637 @param args: should contain only one element, the name of
638 the node to be removed
640 @return: the desired exit code
643 op = opcodes.OpOobCommand(node_names=args, command=constants.OOB_HEALTH,
644 timeout=opts.oob_timeout)
645 result = SubmitOpCode(op, opts=opts)
650 headers = {"node": "Node", "status": "Status"}
654 for node_result in result:
655 (node_tuple, data_tuple) = node_result
656 (_, node_name) = node_tuple
657 (data_status, data_node) = data_tuple
658 if data_status == constants.RS_NORMAL:
659 data.append([node_name, "%s=%s" % tuple(data_node[0])])
660 for item, status in data_node[1:]:
661 data.append(["", "%s=%s" % (item, status)])
664 data.append([node_name, cli.FormatResultError(data_status, True)])
666 data = GenerateTable(separator=opts.separator, headers=headers,
667 fields=["node", "status"], data=data)
673 return constants.EXIT_FAILURE
675 return constants.EXIT_SUCCESS
678 def ListVolumes(opts, args):
679 """List logical volumes on node(s).
681 @param opts: the command line options selected by the user
683 @param args: should either be an empty list, in which case
684 we list data for all nodes, or contain a list of nodes
685 to display data only for those
687 @return: the desired exit code
690 selected_fields = ParseFields(opts.output, _LIST_VOL_DEF_FIELDS)
692 op = opcodes.OpNodeQueryvols(nodes=args, output_fields=selected_fields)
693 output = SubmitOpCode(op, opts=opts)
695 if not opts.no_headers:
696 headers = {"node": "Node", "phys": "PhysDev",
697 "vg": "VG", "name": "Name",
698 "size": "Size", "instance": "Instance"}
702 unitfields = ["size"]
706 data = GenerateTable(separator=opts.separator, headers=headers,
707 fields=selected_fields, unitfields=unitfields,
708 numfields=numfields, data=output, units=opts.units)
716 def ListStorage(opts, args):
717 """List physical volumes on node(s).
719 @param opts: the command line options selected by the user
721 @param args: should either be an empty list, in which case
722 we list data for all nodes, or contain a list of nodes
723 to display data only for those
725 @return: the desired exit code
728 # TODO: Default to ST_FILE if LVM is disabled on the cluster
729 if opts.user_storage_type is None:
730 opts.user_storage_type = constants.ST_LVM_PV
732 storage_type = ConvertStorageType(opts.user_storage_type)
734 selected_fields = ParseFields(opts.output, _LIST_STOR_DEF_FIELDS)
736 op = opcodes.OpNodeQueryStorage(nodes=args,
737 storage_type=storage_type,
738 output_fields=selected_fields)
739 output = SubmitOpCode(op, opts=opts)
741 if not opts.no_headers:
743 constants.SF_NODE: "Node",
744 constants.SF_TYPE: "Type",
745 constants.SF_NAME: "Name",
746 constants.SF_SIZE: "Size",
747 constants.SF_USED: "Used",
748 constants.SF_FREE: "Free",
749 constants.SF_ALLOCATABLE: "Allocatable",
754 unitfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
755 numfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
757 # change raw values to nicer strings
759 for idx, field in enumerate(selected_fields):
761 if field == constants.SF_ALLOCATABLE:
768 data = GenerateTable(separator=opts.separator, headers=headers,
769 fields=selected_fields, unitfields=unitfields,
770 numfields=numfields, data=output, units=opts.units)
778 def ModifyStorage(opts, args):
779 """Modify storage volume on a node.
781 @param opts: the command line options selected by the user
783 @param args: should contain 3 items: node name, storage type and volume name
785 @return: the desired exit code
788 (node_name, user_storage_type, volume_name) = args
790 storage_type = ConvertStorageType(user_storage_type)
794 if opts.allocatable is not None:
795 changes[constants.SF_ALLOCATABLE] = opts.allocatable
798 op = opcodes.OpNodeModifyStorage(node_name=node_name,
799 storage_type=storage_type,
802 SubmitOpCode(op, opts=opts)
804 ToStderr("No changes to perform, exiting.")
807 def RepairStorage(opts, args):
808 """Repairs a storage volume on a node.
810 @param opts: the command line options selected by the user
812 @param args: should contain 3 items: node name, storage type and volume name
814 @return: the desired exit code
817 (node_name, user_storage_type, volume_name) = args
819 storage_type = ConvertStorageType(user_storage_type)
821 op = opcodes.OpRepairNodeStorage(node_name=node_name,
822 storage_type=storage_type,
824 ignore_consistency=opts.ignore_consistency)
825 SubmitOpCode(op, opts=opts)
828 def SetNodeParams(opts, args):
831 @param opts: the command line options selected by the user
833 @param args: should contain only one element, the node name
835 @return: the desired exit code
838 all_changes = [opts.master_candidate, opts.drained, opts.offline,
839 opts.master_capable, opts.vm_capable, opts.secondary_ip,
841 if (all_changes.count(None) == len(all_changes) and
842 not (opts.hv_state or opts.disk_state)):
843 ToStderr("Please give at least one of the parameters.")
847 disk_state = utils.FlatToDict(opts.disk_state)
851 hv_state = dict(opts.hv_state)
853 op = opcodes.OpNodeSetParams(node_name=args[0],
854 master_candidate=opts.master_candidate,
855 offline=opts.offline,
856 drained=opts.drained,
857 master_capable=opts.master_capable,
858 vm_capable=opts.vm_capable,
859 secondary_ip=opts.secondary_ip,
861 ndparams=opts.ndparams,
862 auto_promote=opts.auto_promote,
863 powered=opts.node_powered,
865 disk_state=disk_state)
867 # even if here we process the result, we allow submit only
868 result = SubmitOrSend(op, opts)
871 ToStdout("Modified node %s", args[0])
872 for param, data in result:
873 ToStdout(" - %-5s -> %s", param, data)
879 AddNode, [ArgHost(min=1, max=1)],
880 [SECONDARY_IP_OPT, READD_OPT, NOSSH_KEYCHECK_OPT, NODE_FORCE_JOIN_OPT,
881 NONODE_SETUP_OPT, VERBOSE_OPT, NODEGROUP_OPT, PRIORITY_OPT,
882 CAPAB_MASTER_OPT, CAPAB_VM_OPT, NODE_PARAMS_OPT, HV_STATE_OPT,
884 "[-s ip] [--readd] [--no-ssh-key-check] [--force-join]"
885 " [--no-node-setup] [--verbose]"
887 "Add a node to the cluster"),
889 EvacuateNode, ARGS_ONE_NODE,
890 [FORCE_OPT, IALLOCATOR_OPT, NEW_SECONDARY_OPT, EARLY_RELEASE_OPT,
891 PRIORITY_OPT, PRIMARY_ONLY_OPT, SECONDARY_ONLY_OPT],
892 "[-f] {-I <iallocator> | -n <dst>} <node>",
893 "Relocate the secondary instances from a node"
896 FailoverNode, ARGS_ONE_NODE, [FORCE_OPT, IGNORE_CONSIST_OPT,
897 IALLOCATOR_OPT, PRIORITY_OPT],
899 "Stops the primary instances on a node and start them on their"
900 " secondary node (only for instances with drbd disk template)"),
902 MigrateNode, ARGS_ONE_NODE,
903 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, DST_NODE_OPT,
904 IALLOCATOR_OPT, PRIORITY_OPT, IGNORE_IPOLICY_OPT,
907 "Migrate all the primary instance on a node away from it"
908 " (only for instances of type drbd)"),
910 ShowNodeConfig, ARGS_MANY_NODES, [],
911 "[<node_name>...]", "Show information about the node(s)"),
913 ListNodes, ARGS_MANY_NODES,
914 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT,
917 "Lists the nodes in the cluster. The available fields can be shown using"
918 " the \"list-fields\" command (see the man page for details)."
919 " The default field list is (in order): %s." %
920 utils.CommaJoin(_LIST_DEF_FIELDS)),
922 ListNodeFields, [ArgUnknown()],
923 [NOHDR_OPT, SEP_OPT],
925 "Lists all available fields for nodes"),
927 SetNodeParams, ARGS_ONE_NODE,
928 [FORCE_OPT, SUBMIT_OPT, MC_OPT, DRAINED_OPT, OFFLINE_OPT,
929 CAPAB_MASTER_OPT, CAPAB_VM_OPT, SECONDARY_IP_OPT,
930 AUTO_PROMOTE_OPT, DRY_RUN_OPT, PRIORITY_OPT, NODE_PARAMS_OPT,
931 NODE_POWERED_OPT, HV_STATE_OPT, DISK_STATE_OPT],
932 "<node_name>", "Alters the parameters of a node"),
934 PowercycleNode, ARGS_ONE_NODE,
935 [FORCE_OPT, CONFIRM_OPT, DRY_RUN_OPT, PRIORITY_OPT],
936 "<node_name>", "Tries to forcefully powercycle a node"),
939 [ArgChoice(min=1, max=1, choices=_LIST_POWER_COMMANDS),
941 [SUBMIT_OPT, AUTO_PROMOTE_OPT, PRIORITY_OPT, IGNORE_STATUS_OPT,
942 FORCE_OPT, NOHDR_OPT, SEP_OPT, OOB_TIMEOUT_OPT, POWER_DELAY_OPT],
943 "on|off|cycle|status [nodes...]",
944 "Change power state of node by calling out-of-band helper."),
946 RemoveNode, ARGS_ONE_NODE, [DRY_RUN_OPT, PRIORITY_OPT],
947 "<node_name>", "Removes a node from the cluster"),
949 ListVolumes, [ArgNode()],
950 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, PRIORITY_OPT],
951 "[<node_name>...]", "List logical volumes on node(s)"),
953 ListStorage, ARGS_MANY_NODES,
954 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, _STORAGE_TYPE_OPT,
956 "[<node_name>...]", "List physical volumes on node(s). The available"
957 " fields are (see the man page for details): %s." %
958 (utils.CommaJoin(_LIST_STOR_HEADERS))),
961 [ArgNode(min=1, max=1),
962 ArgChoice(min=1, max=1, choices=_MODIFIABLE_STORAGE_TYPES),
963 ArgFile(min=1, max=1)],
964 [ALLOCATABLE_OPT, DRY_RUN_OPT, PRIORITY_OPT],
965 "<node_name> <storage_type> <name>", "Modify storage volume on a node"),
968 [ArgNode(min=1, max=1),
969 ArgChoice(min=1, max=1, choices=_REPAIRABLE_STORAGE_TYPES),
970 ArgFile(min=1, max=1)],
971 [IGNORE_CONSIST_OPT, DRY_RUN_OPT, PRIORITY_OPT],
972 "<node_name> <storage_type> <name>",
973 "Repairs a storage volume on a node"),
975 ListTags, ARGS_ONE_NODE, [],
976 "<node_name>", "List the tags of the given node"),
978 AddTags, [ArgNode(min=1, max=1), ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT],
979 "<node_name> tag...", "Add tags to the given node"),
981 RemoveTags, [ArgNode(min=1, max=1), ArgUnknown()],
982 [TAG_SRC_OPT, PRIORITY_OPT],
983 "<node_name> tag...", "Remove tags from the given node"),
985 Health, ARGS_MANY_NODES,
986 [NOHDR_OPT, SEP_OPT, SUBMIT_OPT, PRIORITY_OPT, OOB_TIMEOUT_OPT],
987 "[<node_name>...]", "List health of node(s) using out-of-band"),
992 return GenericMain(commands, override={"tag_type": constants.TAG_NODE},
993 env_override=_ENV_OVERRIDE)