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 NONODE_SETUP_OPT = cli_option("--no-node-setup", default=True,
110 action="store_false", dest="node_setup",
111 help=("Do not make initial SSH setup on remote"
112 " node (needs to be done manually)"))
114 IGNORE_STATUS_OPT = cli_option("--ignore-status", default=False,
115 action="store_true", dest="ignore_status",
116 help=("Ignore the Node(s) offline status"
117 " (potentially DANGEROUS)"))
120 def ConvertStorageType(user_storage_type):
121 """Converts a user storage type to its internal name.
125 return _USER_STORAGE_TYPE[user_storage_type]
127 raise errors.OpPrereqError("Unknown storage type: %s" % user_storage_type,
131 def _RunSetupSSH(options, nodes):
132 """Wrapper around utils.RunCmd to call setup-ssh
134 @param options: The command line options
135 @param nodes: The nodes to setup
138 cmd = [constants.SETUP_SSH]
140 # Pass --debug|--verbose to the external script if set on our invocation
141 # --debug overrides --verbose
143 cmd.append("--debug")
144 elif options.verbose:
145 cmd.append("--verbose")
146 if not options.ssh_key_check:
147 cmd.append("--no-ssh-key-check")
148 if options.force_join:
149 cmd.append("--force-join")
153 result = utils.RunCmd(cmd, interactive=True)
156 errmsg = ("Command '%s' failed with exit code %s; output %r" %
157 (result.cmd, result.exit_code, result.output))
158 raise errors.OpExecError(errmsg)
162 def AddNode(opts, args):
163 """Add a node to the cluster.
165 @param opts: the command line options selected by the user
167 @param args: should contain only one element, the new node name
169 @return: the desired exit code
173 node = netutils.GetHostname(name=args[0]).name
177 output = cl.QueryNodes(names=[node], fields=["name", "sip", "master"],
179 node_exists, sip, is_master = output[0]
180 except (errors.OpPrereqError, errors.OpExecError):
186 ToStderr("Node %s not in the cluster"
187 " - please retry without '--readd'", node)
190 ToStderr("Node %s is the master, cannot readd", node)
194 ToStderr("Node %s already in the cluster (as %s)"
195 " - please retry with '--readd'", node, node_exists)
197 sip = opts.secondary_ip
199 # read the cluster name from the master
200 output = cl.QueryConfigValues(["cluster_name"])
201 cluster_name = output[0]
203 if not readd and opts.node_setup:
204 ToStderr("-- WARNING -- \n"
205 "Performing this operation is going to replace the ssh daemon"
207 "on the target machine (%s) with the ones of the"
209 "and grant full intra-cluster ssh root access to/from it\n", node)
212 _RunSetupSSH(opts, [node])
214 bootstrap.SetupNodeDaemon(cluster_name, node, opts.ssh_key_check)
216 op = opcodes.OpNodeAdd(node_name=args[0], secondary_ip=sip,
217 readd=opts.readd, group=opts.nodegroup,
218 vm_capable=opts.vm_capable, ndparams=opts.ndparams,
219 master_capable=opts.master_capable)
220 SubmitOpCode(op, opts=opts)
223 def ListNodes(opts, args):
224 """List nodes and their properties.
226 @param opts: the command line options selected by the user
228 @param args: nodes to list, or empty for all
230 @return: the desired exit code
233 selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
235 fmtoverride = dict.fromkeys(["pinst_list", "sinst_list", "tags"],
238 return GenericList(constants.QR_NODE, selected_fields, args, opts.units,
239 opts.separator, not opts.no_headers,
240 format_override=fmtoverride, verbose=opts.verbose,
241 force_filter=opts.force_filter)
244 def ListNodeFields(opts, args):
247 @param opts: the command line options selected by the user
249 @param args: fields to list, or empty for all
251 @return: the desired exit code
254 return GenericListFields(constants.QR_NODE, args, opts.separator,
258 def EvacuateNode(opts, args):
259 """Relocate all secondary instance from a node.
261 @param opts: the command line options selected by the user
263 @param args: should be an empty list
265 @return: the desired exit code
268 if opts.dst_node is not None:
269 ToStderr("New secondary node given (disabling iallocator), hence evacuating"
270 " secondary instances only.")
271 opts.secondary_only = True
272 opts.primary_only = False
274 if opts.secondary_only and opts.primary_only:
275 raise errors.OpPrereqError("Only one of the --primary-only and"
276 " --secondary-only options can be passed",
278 elif opts.primary_only:
279 mode = constants.IALLOCATOR_NEVAC_PRI
280 elif opts.secondary_only:
281 mode = constants.IALLOCATOR_NEVAC_SEC
283 mode = constants.IALLOCATOR_NEVAC_ALL
285 # Determine affected instances
288 if not opts.secondary_only:
289 fields.append("pinst_list")
290 if not opts.primary_only:
291 fields.append("sinst_list")
295 result = cl.QueryNodes(names=args, fields=fields, use_locking=False)
296 instances = set(itertools.chain(*itertools.chain(*itertools.chain(result))))
299 # No instances to evacuate
300 ToStderr("No instances to evacuate on node(s) %s, exiting.",
301 utils.CommaJoin(args))
302 return constants.EXIT_SUCCESS
304 if not (opts.force or
305 AskUser("Relocate instance(s) %s from node(s) %s?" %
306 (utils.CommaJoin(utils.NiceSort(instances)),
307 utils.CommaJoin(args)))):
308 return constants.EXIT_CONFIRMATION
311 op = opcodes.OpNodeEvacuate(node_name=args[0], mode=mode,
312 remote_node=opts.dst_node,
313 iallocator=opts.iallocator,
314 early_release=opts.early_release)
315 result = SubmitOpCode(op, cl=cl, opts=opts)
317 # Keep track of submitted jobs
318 jex = JobExecutor(cl=cl, opts=opts)
320 for (status, job_id) in result[constants.JOB_IDS_KEY]:
321 jex.AddJobId(None, status, job_id)
323 results = jex.GetResults()
324 bad_cnt = len([row for row in results if not row[0]])
326 ToStdout("All instances evacuated successfully.")
327 rcode = constants.EXIT_SUCCESS
329 ToStdout("There were %s errors during the evacuation.", bad_cnt)
330 rcode = constants.EXIT_FAILURE
335 def FailoverNode(opts, args):
336 """Failover all primary instance on a node.
338 @param opts: the command line options selected by the user
340 @param args: should be an empty list
342 @return: the desired exit code
347 selected_fields = ["name", "pinst_list"]
349 # these fields are static data anyway, so it doesn't matter, but
350 # locking=True should be safer
351 result = cl.QueryNodes(names=args, fields=selected_fields,
353 node, pinst = result[0]
356 ToStderr("No primary instances on node %s, exiting.", node)
359 pinst = utils.NiceSort(pinst)
363 if not force and not AskUser("Fail over instance(s) %s?" %
364 (",".join("'%s'" % name for name in pinst))):
367 jex = JobExecutor(cl=cl, opts=opts)
369 op = opcodes.OpInstanceFailover(instance_name=iname,
370 ignore_consistency=opts.ignore_consistency,
371 iallocator=opts.iallocator)
372 jex.QueueJob(iname, op)
373 results = jex.GetResults()
374 bad_cnt = len([row for row in results if not row[0]])
376 ToStdout("All %d instance(s) failed over successfully.", len(results))
378 ToStdout("There were errors during the failover:\n"
379 "%d error(s) out of %d instance(s).", bad_cnt, len(results))
383 def MigrateNode(opts, args):
384 """Migrate all primary instance on a node.
389 selected_fields = ["name", "pinst_list"]
391 result = cl.QueryNodes(names=args, fields=selected_fields, use_locking=False)
392 ((node, pinst), ) = result
395 ToStdout("No primary instances on node %s, exiting." % node)
398 pinst = utils.NiceSort(pinst)
401 AskUser("Migrate instance(s) %s?" %
402 utils.CommaJoin(utils.NiceSort(pinst)))):
403 return constants.EXIT_CONFIRMATION
405 # this should be removed once --non-live is deprecated
406 if not opts.live and opts.migration_mode is not None:
407 raise errors.OpPrereqError("Only one of the --non-live and "
408 "--migration-mode options can be passed",
410 if not opts.live: # --non-live passed
411 mode = constants.HT_MIGRATION_NONLIVE
413 mode = opts.migration_mode
415 op = opcodes.OpNodeMigrate(node_name=args[0], mode=mode,
416 iallocator=opts.iallocator,
417 target_node=opts.dst_node)
419 result = SubmitOpCode(op, cl=cl, opts=opts)
421 # Keep track of submitted jobs
422 jex = JobExecutor(cl=cl, opts=opts)
424 for (status, job_id) in result[constants.JOB_IDS_KEY]:
425 jex.AddJobId(None, status, job_id)
427 results = jex.GetResults()
428 bad_cnt = len([row for row in results if not row[0]])
430 ToStdout("All instances migrated successfully.")
431 rcode = constants.EXIT_SUCCESS
433 ToStdout("There were %s errors during the node migration.", bad_cnt)
434 rcode = constants.EXIT_FAILURE
439 def ShowNodeConfig(opts, args):
440 """Show node information.
442 @param opts: the command line options selected by the user
444 @param args: should either be an empty list, in which case
445 we show information about all nodes, or should contain
446 a list of nodes to be queried for information
448 @return: the desired exit code
452 result = cl.QueryNodes(fields=["name", "pip", "sip",
453 "pinst_list", "sinst_list",
454 "master_candidate", "drained", "offline",
455 "master_capable", "vm_capable", "powered",
456 "ndparams", "custom_ndparams"],
457 names=args, use_locking=False)
459 for (name, primary_ip, secondary_ip, pinst, sinst, is_mc, drained, offline,
460 master_capable, vm_capable, powered, ndparams,
461 ndparams_custom) in result:
462 ToStdout("Node name: %s", name)
463 ToStdout(" primary ip: %s", primary_ip)
464 ToStdout(" secondary ip: %s", secondary_ip)
465 ToStdout(" master candidate: %s", is_mc)
466 ToStdout(" drained: %s", drained)
467 ToStdout(" offline: %s", offline)
468 if powered is not None:
469 ToStdout(" powered: %s", powered)
470 ToStdout(" master_capable: %s", master_capable)
471 ToStdout(" vm_capable: %s", vm_capable)
474 ToStdout(" primary for instances:")
475 for iname in utils.NiceSort(pinst):
476 ToStdout(" - %s", iname)
478 ToStdout(" primary for no instances")
480 ToStdout(" secondary for instances:")
481 for iname in utils.NiceSort(sinst):
482 ToStdout(" - %s", iname)
484 ToStdout(" secondary for no instances")
485 ToStdout(" node parameters:")
487 FormatParameterDict(buf, ndparams_custom, ndparams, level=2)
488 ToStdout(buf.getvalue().rstrip("\n"))
493 def RemoveNode(opts, args):
494 """Remove a node from the cluster.
496 @param opts: the command line options selected by the user
498 @param args: should contain only one element, the name of
499 the node to be removed
501 @return: the desired exit code
504 op = opcodes.OpNodeRemove(node_name=args[0])
505 SubmitOpCode(op, opts=opts)
509 def PowercycleNode(opts, args):
510 """Remove a node from the cluster.
512 @param opts: the command line options selected by the user
514 @param args: should contain only one element, the name of
515 the node to be removed
517 @return: the desired exit code
521 if (not opts.confirm and
522 not AskUser("Are you sure you want to hard powercycle node %s?" % node)):
525 op = opcodes.OpNodePowercycle(node_name=node, force=opts.force)
526 result = SubmitOpCode(op, opts=opts)
532 def PowerNode(opts, args):
533 """Change/ask power state of a node.
535 @param opts: the command line options selected by the user
537 @param args: should contain only one element, the name of
538 the node to be removed
540 @return: the desired exit code
543 command = args.pop(0)
548 headers = {"node": "Node", "status": "Status"}
550 if command not in _LIST_POWER_COMMANDS:
551 ToStderr("power subcommand %s not supported." % command)
552 return constants.EXIT_FAILURE
554 oob_command = "power-%s" % command
556 if oob_command in _OOB_COMMAND_ASK:
558 ToStderr("Please provide at least one node for this command")
559 return constants.EXIT_FAILURE
560 elif not opts.force and not ConfirmOperation(args, "nodes",
561 "power %s" % command):
562 return constants.EXIT_FAILURE
566 if not opts.ignore_status and oob_command == constants.OOB_POWER_OFF:
567 # TODO: This is a little ugly as we can't catch and revert
569 opcodelist.append(opcodes.OpNodeSetParams(node_name=node, offline=True,
570 auto_promote=opts.auto_promote))
572 opcodelist.append(opcodes.OpOobCommand(node_names=args,
574 ignore_status=opts.ignore_status,
575 timeout=opts.oob_timeout,
576 power_delay=opts.power_delay))
578 cli.SetGenericOpcodeOpts(opcodelist, opts)
580 job_id = cli.SendJob(opcodelist)
582 # We just want the OOB Opcode status
583 # If it fails PollJob gives us the error message in it
584 result = cli.PollJob(job_id)[-1]
588 for node_result in result:
589 (node_tuple, data_tuple) = node_result
590 (_, node_name) = node_tuple
591 (data_status, data_node) = data_tuple
592 if data_status == constants.RS_NORMAL:
593 if oob_command == constants.OOB_POWER_STATUS:
594 if data_node[constants.OOB_POWER_STATUS_POWERED]:
598 data.append([node_name, text])
600 # We don't expect data here, so we just say, it was successfully invoked
601 data.append([node_name, "invoked"])
604 data.append([node_name, cli.FormatResultError(data_status, True)])
606 data = GenerateTable(separator=opts.separator, headers=headers,
607 fields=["node", "status"], data=data)
613 return constants.EXIT_FAILURE
615 return constants.EXIT_SUCCESS
618 def Health(opts, args):
619 """Show health of a node using OOB.
621 @param opts: the command line options selected by the user
623 @param args: should contain only one element, the name of
624 the node to be removed
626 @return: the desired exit code
629 op = opcodes.OpOobCommand(node_names=args, command=constants.OOB_HEALTH,
630 timeout=opts.oob_timeout)
631 result = SubmitOpCode(op, opts=opts)
636 headers = {"node": "Node", "status": "Status"}
640 for node_result in result:
641 (node_tuple, data_tuple) = node_result
642 (_, node_name) = node_tuple
643 (data_status, data_node) = data_tuple
644 if data_status == constants.RS_NORMAL:
645 data.append([node_name, "%s=%s" % tuple(data_node[0])])
646 for item, status in data_node[1:]:
647 data.append(["", "%s=%s" % (item, status)])
650 data.append([node_name, cli.FormatResultError(data_status, True)])
652 data = GenerateTable(separator=opts.separator, headers=headers,
653 fields=["node", "status"], data=data)
659 return constants.EXIT_FAILURE
661 return constants.EXIT_SUCCESS
664 def ListVolumes(opts, args):
665 """List logical volumes on node(s).
667 @param opts: the command line options selected by the user
669 @param args: should either be an empty list, in which case
670 we list data for all nodes, or contain a list of nodes
671 to display data only for those
673 @return: the desired exit code
676 selected_fields = ParseFields(opts.output, _LIST_VOL_DEF_FIELDS)
678 op = opcodes.OpNodeQueryvols(nodes=args, output_fields=selected_fields)
679 output = SubmitOpCode(op, opts=opts)
681 if not opts.no_headers:
682 headers = {"node": "Node", "phys": "PhysDev",
683 "vg": "VG", "name": "Name",
684 "size": "Size", "instance": "Instance"}
688 unitfields = ["size"]
692 data = GenerateTable(separator=opts.separator, headers=headers,
693 fields=selected_fields, unitfields=unitfields,
694 numfields=numfields, data=output, units=opts.units)
702 def ListStorage(opts, args):
703 """List physical volumes on node(s).
705 @param opts: the command line options selected by the user
707 @param args: should either be an empty list, in which case
708 we list data for all nodes, or contain a list of nodes
709 to display data only for those
711 @return: the desired exit code
714 # TODO: Default to ST_FILE if LVM is disabled on the cluster
715 if opts.user_storage_type is None:
716 opts.user_storage_type = constants.ST_LVM_PV
718 storage_type = ConvertStorageType(opts.user_storage_type)
720 selected_fields = ParseFields(opts.output, _LIST_STOR_DEF_FIELDS)
722 op = opcodes.OpNodeQueryStorage(nodes=args,
723 storage_type=storage_type,
724 output_fields=selected_fields)
725 output = SubmitOpCode(op, opts=opts)
727 if not opts.no_headers:
729 constants.SF_NODE: "Node",
730 constants.SF_TYPE: "Type",
731 constants.SF_NAME: "Name",
732 constants.SF_SIZE: "Size",
733 constants.SF_USED: "Used",
734 constants.SF_FREE: "Free",
735 constants.SF_ALLOCATABLE: "Allocatable",
740 unitfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
741 numfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
743 # change raw values to nicer strings
745 for idx, field in enumerate(selected_fields):
747 if field == constants.SF_ALLOCATABLE:
754 data = GenerateTable(separator=opts.separator, headers=headers,
755 fields=selected_fields, unitfields=unitfields,
756 numfields=numfields, data=output, units=opts.units)
764 def ModifyStorage(opts, args):
765 """Modify storage volume on a node.
767 @param opts: the command line options selected by the user
769 @param args: should contain 3 items: node name, storage type and volume name
771 @return: the desired exit code
774 (node_name, user_storage_type, volume_name) = args
776 storage_type = ConvertStorageType(user_storage_type)
780 if opts.allocatable is not None:
781 changes[constants.SF_ALLOCATABLE] = opts.allocatable
784 op = opcodes.OpNodeModifyStorage(node_name=node_name,
785 storage_type=storage_type,
788 SubmitOpCode(op, opts=opts)
790 ToStderr("No changes to perform, exiting.")
793 def RepairStorage(opts, args):
794 """Repairs a storage volume on a node.
796 @param opts: the command line options selected by the user
798 @param args: should contain 3 items: node name, storage type and volume name
800 @return: the desired exit code
803 (node_name, user_storage_type, volume_name) = args
805 storage_type = ConvertStorageType(user_storage_type)
807 op = opcodes.OpRepairNodeStorage(node_name=node_name,
808 storage_type=storage_type,
810 ignore_consistency=opts.ignore_consistency)
811 SubmitOpCode(op, opts=opts)
814 def SetNodeParams(opts, args):
817 @param opts: the command line options selected by the user
819 @param args: should contain only one element, the node name
821 @return: the desired exit code
824 all_changes = [opts.master_candidate, opts.drained, opts.offline,
825 opts.master_capable, opts.vm_capable, opts.secondary_ip,
827 if all_changes.count(None) == len(all_changes):
828 ToStderr("Please give at least one of the parameters.")
831 op = opcodes.OpNodeSetParams(node_name=args[0],
832 master_candidate=opts.master_candidate,
833 offline=opts.offline,
834 drained=opts.drained,
835 master_capable=opts.master_capable,
836 vm_capable=opts.vm_capable,
837 secondary_ip=opts.secondary_ip,
839 ndparams=opts.ndparams,
840 auto_promote=opts.auto_promote,
841 powered=opts.node_powered)
843 # even if here we process the result, we allow submit only
844 result = SubmitOrSend(op, opts)
847 ToStdout("Modified node %s", args[0])
848 for param, data in result:
849 ToStdout(" - %-5s -> %s", param, data)
855 AddNode, [ArgHost(min=1, max=1)],
856 [SECONDARY_IP_OPT, READD_OPT, NOSSH_KEYCHECK_OPT, NODE_FORCE_JOIN_OPT,
857 NONODE_SETUP_OPT, VERBOSE_OPT, NODEGROUP_OPT, PRIORITY_OPT,
858 CAPAB_MASTER_OPT, CAPAB_VM_OPT, NODE_PARAMS_OPT],
859 "[-s ip] [--readd] [--no-ssh-key-check] [--force-join]"
860 " [--no-node-setup] [--verbose]"
862 "Add a node to the cluster"),
864 EvacuateNode, ARGS_ONE_NODE,
865 [FORCE_OPT, IALLOCATOR_OPT, NEW_SECONDARY_OPT, EARLY_RELEASE_OPT,
866 PRIORITY_OPT, PRIMARY_ONLY_OPT, SECONDARY_ONLY_OPT],
867 "[-f] {-I <iallocator> | -n <dst>} <node>",
868 "Relocate the secondary instances from a node"
871 FailoverNode, ARGS_ONE_NODE, [FORCE_OPT, IGNORE_CONSIST_OPT,
872 IALLOCATOR_OPT, PRIORITY_OPT],
874 "Stops the primary instances on a node and start them on their"
875 " secondary node (only for instances with drbd disk template)"),
877 MigrateNode, ARGS_ONE_NODE,
878 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, DST_NODE_OPT,
879 IALLOCATOR_OPT, PRIORITY_OPT],
881 "Migrate all the primary instance on a node away from it"
882 " (only for instances of type drbd)"),
884 ShowNodeConfig, ARGS_MANY_NODES, [],
885 "[<node_name>...]", "Show information about the node(s)"),
887 ListNodes, ARGS_MANY_NODES,
888 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT,
891 "Lists the nodes in the cluster. The available fields can be shown using"
892 " the \"list-fields\" command (see the man page for details)."
893 " The default field list is (in order): %s." %
894 utils.CommaJoin(_LIST_DEF_FIELDS)),
896 ListNodeFields, [ArgUnknown()],
897 [NOHDR_OPT, SEP_OPT],
899 "Lists all available fields for nodes"),
901 SetNodeParams, ARGS_ONE_NODE,
902 [FORCE_OPT, SUBMIT_OPT, MC_OPT, DRAINED_OPT, OFFLINE_OPT,
903 CAPAB_MASTER_OPT, CAPAB_VM_OPT, SECONDARY_IP_OPT,
904 AUTO_PROMOTE_OPT, DRY_RUN_OPT, PRIORITY_OPT, NODE_PARAMS_OPT,
906 "<node_name>", "Alters the parameters of a node"),
908 PowercycleNode, ARGS_ONE_NODE,
909 [FORCE_OPT, CONFIRM_OPT, DRY_RUN_OPT, PRIORITY_OPT],
910 "<node_name>", "Tries to forcefully powercycle a node"),
913 [ArgChoice(min=1, max=1, choices=_LIST_POWER_COMMANDS),
915 [SUBMIT_OPT, AUTO_PROMOTE_OPT, PRIORITY_OPT, IGNORE_STATUS_OPT,
916 FORCE_OPT, NOHDR_OPT, SEP_OPT, OOB_TIMEOUT_OPT, POWER_DELAY_OPT],
917 "on|off|cycle|status [nodes...]",
918 "Change power state of node by calling out-of-band helper."),
920 RemoveNode, ARGS_ONE_NODE, [DRY_RUN_OPT, PRIORITY_OPT],
921 "<node_name>", "Removes a node from the cluster"),
923 ListVolumes, [ArgNode()],
924 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, PRIORITY_OPT],
925 "[<node_name>...]", "List logical volumes on node(s)"),
927 ListStorage, ARGS_MANY_NODES,
928 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, _STORAGE_TYPE_OPT,
930 "[<node_name>...]", "List physical volumes on node(s). The available"
931 " fields are (see the man page for details): %s." %
932 (utils.CommaJoin(_LIST_STOR_HEADERS))),
935 [ArgNode(min=1, max=1),
936 ArgChoice(min=1, max=1, choices=_MODIFIABLE_STORAGE_TYPES),
937 ArgFile(min=1, max=1)],
938 [ALLOCATABLE_OPT, DRY_RUN_OPT, PRIORITY_OPT],
939 "<node_name> <storage_type> <name>", "Modify storage volume on a node"),
942 [ArgNode(min=1, max=1),
943 ArgChoice(min=1, max=1, choices=_REPAIRABLE_STORAGE_TYPES),
944 ArgFile(min=1, max=1)],
945 [IGNORE_CONSIST_OPT, DRY_RUN_OPT, PRIORITY_OPT],
946 "<node_name> <storage_type> <name>",
947 "Repairs a storage volume on a node"),
949 ListTags, ARGS_ONE_NODE, [],
950 "<node_name>", "List the tags of the given node"),
952 AddTags, [ArgNode(min=1, max=1), ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT],
953 "<node_name> tag...", "Add tags to the given node"),
955 RemoveTags, [ArgNode(min=1, max=1), ArgUnknown()],
956 [TAG_SRC_OPT, PRIORITY_OPT],
957 "<node_name> tag...", "Remove tags from the given node"),
959 Health, ARGS_MANY_NODES,
960 [NOHDR_OPT, SEP_OPT, SUBMIT_OPT, PRIORITY_OPT, OOB_TIMEOUT_OPT],
961 "[<node_name>...]", "List health of node(s) using out-of-band"),
966 return GenericMain(commands, override={"tag_type": constants.TAG_NODE})