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 ignore_ipolicy=opts.ignore_ipolicy)
432 result = SubmitOpCode(op, cl=cl, opts=opts)
434 # Keep track of submitted jobs
435 jex = JobExecutor(cl=cl, opts=opts)
437 for (status, job_id) in result[constants.JOB_IDS_KEY]:
438 jex.AddJobId(None, status, job_id)
440 results = jex.GetResults()
441 bad_cnt = len([row for row in results if not row[0]])
443 ToStdout("All instances migrated successfully.")
444 rcode = constants.EXIT_SUCCESS
446 ToStdout("There were %s errors during the node migration.", bad_cnt)
447 rcode = constants.EXIT_FAILURE
452 def ShowNodeConfig(opts, args):
453 """Show node information.
455 @param opts: the command line options selected by the user
457 @param args: should either be an empty list, in which case
458 we show information about all nodes, or should contain
459 a list of nodes to be queried for information
461 @return: the desired exit code
465 result = cl.QueryNodes(fields=["name", "pip", "sip",
466 "pinst_list", "sinst_list",
467 "master_candidate", "drained", "offline",
468 "master_capable", "vm_capable", "powered",
469 "ndparams", "custom_ndparams"],
470 names=args, use_locking=False)
472 for (name, primary_ip, secondary_ip, pinst, sinst, is_mc, drained, offline,
473 master_capable, vm_capable, powered, ndparams,
474 ndparams_custom) in result:
475 ToStdout("Node name: %s", name)
476 ToStdout(" primary ip: %s", primary_ip)
477 ToStdout(" secondary ip: %s", secondary_ip)
478 ToStdout(" master candidate: %s", is_mc)
479 ToStdout(" drained: %s", drained)
480 ToStdout(" offline: %s", offline)
481 if powered is not None:
482 ToStdout(" powered: %s", powered)
483 ToStdout(" master_capable: %s", master_capable)
484 ToStdout(" vm_capable: %s", vm_capable)
487 ToStdout(" primary for instances:")
488 for iname in utils.NiceSort(pinst):
489 ToStdout(" - %s", iname)
491 ToStdout(" primary for no instances")
493 ToStdout(" secondary for instances:")
494 for iname in utils.NiceSort(sinst):
495 ToStdout(" - %s", iname)
497 ToStdout(" secondary for no instances")
498 ToStdout(" node parameters:")
500 FormatParameterDict(buf, ndparams_custom, ndparams, level=2)
501 ToStdout(buf.getvalue().rstrip("\n"))
506 def RemoveNode(opts, args):
507 """Remove a node from the cluster.
509 @param opts: the command line options selected by the user
511 @param args: should contain only one element, the name of
512 the node to be removed
514 @return: the desired exit code
517 op = opcodes.OpNodeRemove(node_name=args[0])
518 SubmitOpCode(op, opts=opts)
522 def PowercycleNode(opts, args):
523 """Remove a node from the cluster.
525 @param opts: the command line options selected by the user
527 @param args: should contain only one element, the name of
528 the node to be removed
530 @return: the desired exit code
534 if (not opts.confirm and
535 not AskUser("Are you sure you want to hard powercycle node %s?" % node)):
538 op = opcodes.OpNodePowercycle(node_name=node, force=opts.force)
539 result = SubmitOpCode(op, opts=opts)
545 def PowerNode(opts, args):
546 """Change/ask power state of a node.
548 @param opts: the command line options selected by the user
550 @param args: should contain only one element, the name of
551 the node to be removed
553 @return: the desired exit code
556 command = args.pop(0)
561 headers = {"node": "Node", "status": "Status"}
563 if command not in _LIST_POWER_COMMANDS:
564 ToStderr("power subcommand %s not supported." % command)
565 return constants.EXIT_FAILURE
567 oob_command = "power-%s" % command
569 if oob_command in _OOB_COMMAND_ASK:
571 ToStderr("Please provide at least one node for this command")
572 return constants.EXIT_FAILURE
573 elif not opts.force and not ConfirmOperation(args, "nodes",
574 "power %s" % command):
575 return constants.EXIT_FAILURE
579 if not opts.ignore_status and oob_command == constants.OOB_POWER_OFF:
580 # TODO: This is a little ugly as we can't catch and revert
582 opcodelist.append(opcodes.OpNodeSetParams(node_name=node, offline=True,
583 auto_promote=opts.auto_promote))
585 opcodelist.append(opcodes.OpOobCommand(node_names=args,
587 ignore_status=opts.ignore_status,
588 timeout=opts.oob_timeout,
589 power_delay=opts.power_delay))
591 cli.SetGenericOpcodeOpts(opcodelist, opts)
593 job_id = cli.SendJob(opcodelist)
595 # We just want the OOB Opcode status
596 # If it fails PollJob gives us the error message in it
597 result = cli.PollJob(job_id)[-1]
601 for node_result in result:
602 (node_tuple, data_tuple) = node_result
603 (_, node_name) = node_tuple
604 (data_status, data_node) = data_tuple
605 if data_status == constants.RS_NORMAL:
606 if oob_command == constants.OOB_POWER_STATUS:
607 if data_node[constants.OOB_POWER_STATUS_POWERED]:
611 data.append([node_name, text])
613 # We don't expect data here, so we just say, it was successfully invoked
614 data.append([node_name, "invoked"])
617 data.append([node_name, cli.FormatResultError(data_status, True)])
619 data = GenerateTable(separator=opts.separator, headers=headers,
620 fields=["node", "status"], data=data)
626 return constants.EXIT_FAILURE
628 return constants.EXIT_SUCCESS
631 def Health(opts, args):
632 """Show health of a node using OOB.
634 @param opts: the command line options selected by the user
636 @param args: should contain only one element, the name of
637 the node to be removed
639 @return: the desired exit code
642 op = opcodes.OpOobCommand(node_names=args, command=constants.OOB_HEALTH,
643 timeout=opts.oob_timeout)
644 result = SubmitOpCode(op, opts=opts)
649 headers = {"node": "Node", "status": "Status"}
653 for node_result in result:
654 (node_tuple, data_tuple) = node_result
655 (_, node_name) = node_tuple
656 (data_status, data_node) = data_tuple
657 if data_status == constants.RS_NORMAL:
658 data.append([node_name, "%s=%s" % tuple(data_node[0])])
659 for item, status in data_node[1:]:
660 data.append(["", "%s=%s" % (item, status)])
663 data.append([node_name, cli.FormatResultError(data_status, True)])
665 data = GenerateTable(separator=opts.separator, headers=headers,
666 fields=["node", "status"], data=data)
672 return constants.EXIT_FAILURE
674 return constants.EXIT_SUCCESS
677 def ListVolumes(opts, args):
678 """List logical volumes on node(s).
680 @param opts: the command line options selected by the user
682 @param args: should either be an empty list, in which case
683 we list data for all nodes, or contain a list of nodes
684 to display data only for those
686 @return: the desired exit code
689 selected_fields = ParseFields(opts.output, _LIST_VOL_DEF_FIELDS)
691 op = opcodes.OpNodeQueryvols(nodes=args, output_fields=selected_fields)
692 output = SubmitOpCode(op, opts=opts)
694 if not opts.no_headers:
695 headers = {"node": "Node", "phys": "PhysDev",
696 "vg": "VG", "name": "Name",
697 "size": "Size", "instance": "Instance"}
701 unitfields = ["size"]
705 data = GenerateTable(separator=opts.separator, headers=headers,
706 fields=selected_fields, unitfields=unitfields,
707 numfields=numfields, data=output, units=opts.units)
715 def ListStorage(opts, args):
716 """List physical volumes on node(s).
718 @param opts: the command line options selected by the user
720 @param args: should either be an empty list, in which case
721 we list data for all nodes, or contain a list of nodes
722 to display data only for those
724 @return: the desired exit code
727 # TODO: Default to ST_FILE if LVM is disabled on the cluster
728 if opts.user_storage_type is None:
729 opts.user_storage_type = constants.ST_LVM_PV
731 storage_type = ConvertStorageType(opts.user_storage_type)
733 selected_fields = ParseFields(opts.output, _LIST_STOR_DEF_FIELDS)
735 op = opcodes.OpNodeQueryStorage(nodes=args,
736 storage_type=storage_type,
737 output_fields=selected_fields)
738 output = SubmitOpCode(op, opts=opts)
740 if not opts.no_headers:
742 constants.SF_NODE: "Node",
743 constants.SF_TYPE: "Type",
744 constants.SF_NAME: "Name",
745 constants.SF_SIZE: "Size",
746 constants.SF_USED: "Used",
747 constants.SF_FREE: "Free",
748 constants.SF_ALLOCATABLE: "Allocatable",
753 unitfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
754 numfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
756 # change raw values to nicer strings
758 for idx, field in enumerate(selected_fields):
760 if field == constants.SF_ALLOCATABLE:
767 data = GenerateTable(separator=opts.separator, headers=headers,
768 fields=selected_fields, unitfields=unitfields,
769 numfields=numfields, data=output, units=opts.units)
777 def ModifyStorage(opts, args):
778 """Modify storage volume on a node.
780 @param opts: the command line options selected by the user
782 @param args: should contain 3 items: node name, storage type and volume name
784 @return: the desired exit code
787 (node_name, user_storage_type, volume_name) = args
789 storage_type = ConvertStorageType(user_storage_type)
793 if opts.allocatable is not None:
794 changes[constants.SF_ALLOCATABLE] = opts.allocatable
797 op = opcodes.OpNodeModifyStorage(node_name=node_name,
798 storage_type=storage_type,
801 SubmitOpCode(op, opts=opts)
803 ToStderr("No changes to perform, exiting.")
806 def RepairStorage(opts, args):
807 """Repairs a storage volume on a node.
809 @param opts: the command line options selected by the user
811 @param args: should contain 3 items: node name, storage type and volume name
813 @return: the desired exit code
816 (node_name, user_storage_type, volume_name) = args
818 storage_type = ConvertStorageType(user_storage_type)
820 op = opcodes.OpRepairNodeStorage(node_name=node_name,
821 storage_type=storage_type,
823 ignore_consistency=opts.ignore_consistency)
824 SubmitOpCode(op, opts=opts)
827 def SetNodeParams(opts, args):
830 @param opts: the command line options selected by the user
832 @param args: should contain only one element, the node name
834 @return: the desired exit code
837 all_changes = [opts.master_candidate, opts.drained, opts.offline,
838 opts.master_capable, opts.vm_capable, opts.secondary_ip,
840 if (all_changes.count(None) == len(all_changes) and
841 not (opts.hv_state or opts.disk_state)):
842 ToStderr("Please give at least one of the parameters.")
846 disk_state = utils.FlatToDict(opts.disk_state)
850 hv_state = dict(opts.hv_state)
852 op = opcodes.OpNodeSetParams(node_name=args[0],
853 master_candidate=opts.master_candidate,
854 offline=opts.offline,
855 drained=opts.drained,
856 master_capable=opts.master_capable,
857 vm_capable=opts.vm_capable,
858 secondary_ip=opts.secondary_ip,
860 ndparams=opts.ndparams,
861 auto_promote=opts.auto_promote,
862 powered=opts.node_powered,
864 disk_state=disk_state)
866 # even if here we process the result, we allow submit only
867 result = SubmitOrSend(op, opts)
870 ToStdout("Modified node %s", args[0])
871 for param, data in result:
872 ToStdout(" - %-5s -> %s", param, data)
878 AddNode, [ArgHost(min=1, max=1)],
879 [SECONDARY_IP_OPT, READD_OPT, NOSSH_KEYCHECK_OPT, NODE_FORCE_JOIN_OPT,
880 NONODE_SETUP_OPT, VERBOSE_OPT, NODEGROUP_OPT, PRIORITY_OPT,
881 CAPAB_MASTER_OPT, CAPAB_VM_OPT, NODE_PARAMS_OPT, HV_STATE_OPT,
883 "[-s ip] [--readd] [--no-ssh-key-check] [--force-join]"
884 " [--no-node-setup] [--verbose]"
886 "Add a node to the cluster"),
888 EvacuateNode, ARGS_ONE_NODE,
889 [FORCE_OPT, IALLOCATOR_OPT, NEW_SECONDARY_OPT, EARLY_RELEASE_OPT,
890 PRIORITY_OPT, PRIMARY_ONLY_OPT, SECONDARY_ONLY_OPT],
891 "[-f] {-I <iallocator> | -n <dst>} <node>",
892 "Relocate the secondary instances from a node"
895 FailoverNode, ARGS_ONE_NODE, [FORCE_OPT, IGNORE_CONSIST_OPT,
896 IALLOCATOR_OPT, PRIORITY_OPT],
898 "Stops the primary instances on a node and start them on their"
899 " secondary node (only for instances with drbd disk template)"),
901 MigrateNode, ARGS_ONE_NODE,
902 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, DST_NODE_OPT,
903 IALLOCATOR_OPT, PRIORITY_OPT, IGNORE_IPOLICY_OPT],
905 "Migrate all the primary instance on a node away from it"
906 " (only for instances of type drbd)"),
908 ShowNodeConfig, ARGS_MANY_NODES, [],
909 "[<node_name>...]", "Show information about the node(s)"),
911 ListNodes, ARGS_MANY_NODES,
912 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT,
915 "Lists the nodes in the cluster. The available fields can be shown using"
916 " the \"list-fields\" command (see the man page for details)."
917 " The default field list is (in order): %s." %
918 utils.CommaJoin(_LIST_DEF_FIELDS)),
920 ListNodeFields, [ArgUnknown()],
921 [NOHDR_OPT, SEP_OPT],
923 "Lists all available fields for nodes"),
925 SetNodeParams, ARGS_ONE_NODE,
926 [FORCE_OPT, SUBMIT_OPT, MC_OPT, DRAINED_OPT, OFFLINE_OPT,
927 CAPAB_MASTER_OPT, CAPAB_VM_OPT, SECONDARY_IP_OPT,
928 AUTO_PROMOTE_OPT, DRY_RUN_OPT, PRIORITY_OPT, NODE_PARAMS_OPT,
929 NODE_POWERED_OPT, HV_STATE_OPT, DISK_STATE_OPT],
930 "<node_name>", "Alters the parameters of a node"),
932 PowercycleNode, ARGS_ONE_NODE,
933 [FORCE_OPT, CONFIRM_OPT, DRY_RUN_OPT, PRIORITY_OPT],
934 "<node_name>", "Tries to forcefully powercycle a node"),
937 [ArgChoice(min=1, max=1, choices=_LIST_POWER_COMMANDS),
939 [SUBMIT_OPT, AUTO_PROMOTE_OPT, PRIORITY_OPT, IGNORE_STATUS_OPT,
940 FORCE_OPT, NOHDR_OPT, SEP_OPT, OOB_TIMEOUT_OPT, POWER_DELAY_OPT],
941 "on|off|cycle|status [nodes...]",
942 "Change power state of node by calling out-of-band helper."),
944 RemoveNode, ARGS_ONE_NODE, [DRY_RUN_OPT, PRIORITY_OPT],
945 "<node_name>", "Removes a node from the cluster"),
947 ListVolumes, [ArgNode()],
948 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, PRIORITY_OPT],
949 "[<node_name>...]", "List logical volumes on node(s)"),
951 ListStorage, ARGS_MANY_NODES,
952 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, _STORAGE_TYPE_OPT,
954 "[<node_name>...]", "List physical volumes on node(s). The available"
955 " fields are (see the man page for details): %s." %
956 (utils.CommaJoin(_LIST_STOR_HEADERS))),
959 [ArgNode(min=1, max=1),
960 ArgChoice(min=1, max=1, choices=_MODIFIABLE_STORAGE_TYPES),
961 ArgFile(min=1, max=1)],
962 [ALLOCATABLE_OPT, DRY_RUN_OPT, PRIORITY_OPT],
963 "<node_name> <storage_type> <name>", "Modify storage volume on a node"),
966 [ArgNode(min=1, max=1),
967 ArgChoice(min=1, max=1, choices=_REPAIRABLE_STORAGE_TYPES),
968 ArgFile(min=1, max=1)],
969 [IGNORE_CONSIST_OPT, DRY_RUN_OPT, PRIORITY_OPT],
970 "<node_name> <storage_type> <name>",
971 "Repairs a storage volume on a node"),
973 ListTags, ARGS_ONE_NODE, [],
974 "<node_name>", "List the tags of the given node"),
976 AddTags, [ArgNode(min=1, max=1), ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT],
977 "<node_name> tag...", "Add tags to the given node"),
979 RemoveTags, [ArgNode(min=1, max=1), ArgUnknown()],
980 [TAG_SRC_OPT, PRIORITY_OPT],
981 "<node_name> tag...", "Remove tags from the given node"),
983 Health, ARGS_MANY_NODES,
984 [NOHDR_OPT, SEP_OPT, SUBMIT_OPT, PRIORITY_OPT, OOB_TIMEOUT_OPT],
985 "[<node_name>...]", "List health of node(s) using out-of-band"),
990 return GenericMain(commands, override={"tag_type": constants.TAG_NODE},
991 env_override=_ENV_OVERRIDE)