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)
431 result = SubmitOpCode(op, cl=cl, opts=opts)
433 # Keep track of submitted jobs
434 jex = JobExecutor(cl=cl, opts=opts)
436 for (status, job_id) in result[constants.JOB_IDS_KEY]:
437 jex.AddJobId(None, status, job_id)
439 results = jex.GetResults()
440 bad_cnt = len([row for row in results if not row[0]])
442 ToStdout("All instances migrated successfully.")
443 rcode = constants.EXIT_SUCCESS
445 ToStdout("There were %s errors during the node migration.", bad_cnt)
446 rcode = constants.EXIT_FAILURE
451 def ShowNodeConfig(opts, args):
452 """Show node information.
454 @param opts: the command line options selected by the user
456 @param args: should either be an empty list, in which case
457 we show information about all nodes, or should contain
458 a list of nodes to be queried for information
460 @return: the desired exit code
464 result = cl.QueryNodes(fields=["name", "pip", "sip",
465 "pinst_list", "sinst_list",
466 "master_candidate", "drained", "offline",
467 "master_capable", "vm_capable", "powered",
468 "ndparams", "custom_ndparams"],
469 names=args, use_locking=False)
471 for (name, primary_ip, secondary_ip, pinst, sinst, is_mc, drained, offline,
472 master_capable, vm_capable, powered, ndparams,
473 ndparams_custom) in result:
474 ToStdout("Node name: %s", name)
475 ToStdout(" primary ip: %s", primary_ip)
476 ToStdout(" secondary ip: %s", secondary_ip)
477 ToStdout(" master candidate: %s", is_mc)
478 ToStdout(" drained: %s", drained)
479 ToStdout(" offline: %s", offline)
480 if powered is not None:
481 ToStdout(" powered: %s", powered)
482 ToStdout(" master_capable: %s", master_capable)
483 ToStdout(" vm_capable: %s", vm_capable)
486 ToStdout(" primary for instances:")
487 for iname in utils.NiceSort(pinst):
488 ToStdout(" - %s", iname)
490 ToStdout(" primary for no instances")
492 ToStdout(" secondary for instances:")
493 for iname in utils.NiceSort(sinst):
494 ToStdout(" - %s", iname)
496 ToStdout(" secondary for no instances")
497 ToStdout(" node parameters:")
499 FormatParameterDict(buf, ndparams_custom, ndparams, level=2)
500 ToStdout(buf.getvalue().rstrip("\n"))
505 def RemoveNode(opts, args):
506 """Remove a node from the cluster.
508 @param opts: the command line options selected by the user
510 @param args: should contain only one element, the name of
511 the node to be removed
513 @return: the desired exit code
516 op = opcodes.OpNodeRemove(node_name=args[0])
517 SubmitOpCode(op, opts=opts)
521 def PowercycleNode(opts, args):
522 """Remove a node from the cluster.
524 @param opts: the command line options selected by the user
526 @param args: should contain only one element, the name of
527 the node to be removed
529 @return: the desired exit code
533 if (not opts.confirm and
534 not AskUser("Are you sure you want to hard powercycle node %s?" % node)):
537 op = opcodes.OpNodePowercycle(node_name=node, force=opts.force)
538 result = SubmitOpCode(op, opts=opts)
544 def PowerNode(opts, args):
545 """Change/ask power state of a node.
547 @param opts: the command line options selected by the user
549 @param args: should contain only one element, the name of
550 the node to be removed
552 @return: the desired exit code
555 command = args.pop(0)
560 headers = {"node": "Node", "status": "Status"}
562 if command not in _LIST_POWER_COMMANDS:
563 ToStderr("power subcommand %s not supported." % command)
564 return constants.EXIT_FAILURE
566 oob_command = "power-%s" % command
568 if oob_command in _OOB_COMMAND_ASK:
570 ToStderr("Please provide at least one node for this command")
571 return constants.EXIT_FAILURE
572 elif not opts.force and not ConfirmOperation(args, "nodes",
573 "power %s" % command):
574 return constants.EXIT_FAILURE
578 if not opts.ignore_status and oob_command == constants.OOB_POWER_OFF:
579 # TODO: This is a little ugly as we can't catch and revert
581 opcodelist.append(opcodes.OpNodeSetParams(node_name=node, offline=True,
582 auto_promote=opts.auto_promote))
584 opcodelist.append(opcodes.OpOobCommand(node_names=args,
586 ignore_status=opts.ignore_status,
587 timeout=opts.oob_timeout,
588 power_delay=opts.power_delay))
590 cli.SetGenericOpcodeOpts(opcodelist, opts)
592 job_id = cli.SendJob(opcodelist)
594 # We just want the OOB Opcode status
595 # If it fails PollJob gives us the error message in it
596 result = cli.PollJob(job_id)[-1]
600 for node_result in result:
601 (node_tuple, data_tuple) = node_result
602 (_, node_name) = node_tuple
603 (data_status, data_node) = data_tuple
604 if data_status == constants.RS_NORMAL:
605 if oob_command == constants.OOB_POWER_STATUS:
606 if data_node[constants.OOB_POWER_STATUS_POWERED]:
610 data.append([node_name, text])
612 # We don't expect data here, so we just say, it was successfully invoked
613 data.append([node_name, "invoked"])
616 data.append([node_name, cli.FormatResultError(data_status, True)])
618 data = GenerateTable(separator=opts.separator, headers=headers,
619 fields=["node", "status"], data=data)
625 return constants.EXIT_FAILURE
627 return constants.EXIT_SUCCESS
630 def Health(opts, args):
631 """Show health of a node using OOB.
633 @param opts: the command line options selected by the user
635 @param args: should contain only one element, the name of
636 the node to be removed
638 @return: the desired exit code
641 op = opcodes.OpOobCommand(node_names=args, command=constants.OOB_HEALTH,
642 timeout=opts.oob_timeout)
643 result = SubmitOpCode(op, opts=opts)
648 headers = {"node": "Node", "status": "Status"}
652 for node_result in result:
653 (node_tuple, data_tuple) = node_result
654 (_, node_name) = node_tuple
655 (data_status, data_node) = data_tuple
656 if data_status == constants.RS_NORMAL:
657 data.append([node_name, "%s=%s" % tuple(data_node[0])])
658 for item, status in data_node[1:]:
659 data.append(["", "%s=%s" % (item, status)])
662 data.append([node_name, cli.FormatResultError(data_status, True)])
664 data = GenerateTable(separator=opts.separator, headers=headers,
665 fields=["node", "status"], data=data)
671 return constants.EXIT_FAILURE
673 return constants.EXIT_SUCCESS
676 def ListVolumes(opts, args):
677 """List logical volumes on node(s).
679 @param opts: the command line options selected by the user
681 @param args: should either be an empty list, in which case
682 we list data for all nodes, or contain a list of nodes
683 to display data only for those
685 @return: the desired exit code
688 selected_fields = ParseFields(opts.output, _LIST_VOL_DEF_FIELDS)
690 op = opcodes.OpNodeQueryvols(nodes=args, output_fields=selected_fields)
691 output = SubmitOpCode(op, opts=opts)
693 if not opts.no_headers:
694 headers = {"node": "Node", "phys": "PhysDev",
695 "vg": "VG", "name": "Name",
696 "size": "Size", "instance": "Instance"}
700 unitfields = ["size"]
704 data = GenerateTable(separator=opts.separator, headers=headers,
705 fields=selected_fields, unitfields=unitfields,
706 numfields=numfields, data=output, units=opts.units)
714 def ListStorage(opts, args):
715 """List physical volumes on node(s).
717 @param opts: the command line options selected by the user
719 @param args: should either be an empty list, in which case
720 we list data for all nodes, or contain a list of nodes
721 to display data only for those
723 @return: the desired exit code
726 # TODO: Default to ST_FILE if LVM is disabled on the cluster
727 if opts.user_storage_type is None:
728 opts.user_storage_type = constants.ST_LVM_PV
730 storage_type = ConvertStorageType(opts.user_storage_type)
732 selected_fields = ParseFields(opts.output, _LIST_STOR_DEF_FIELDS)
734 op = opcodes.OpNodeQueryStorage(nodes=args,
735 storage_type=storage_type,
736 output_fields=selected_fields)
737 output = SubmitOpCode(op, opts=opts)
739 if not opts.no_headers:
741 constants.SF_NODE: "Node",
742 constants.SF_TYPE: "Type",
743 constants.SF_NAME: "Name",
744 constants.SF_SIZE: "Size",
745 constants.SF_USED: "Used",
746 constants.SF_FREE: "Free",
747 constants.SF_ALLOCATABLE: "Allocatable",
752 unitfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
753 numfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
755 # change raw values to nicer strings
757 for idx, field in enumerate(selected_fields):
759 if field == constants.SF_ALLOCATABLE:
766 data = GenerateTable(separator=opts.separator, headers=headers,
767 fields=selected_fields, unitfields=unitfields,
768 numfields=numfields, data=output, units=opts.units)
776 def ModifyStorage(opts, args):
777 """Modify storage volume on a node.
779 @param opts: the command line options selected by the user
781 @param args: should contain 3 items: node name, storage type and volume name
783 @return: the desired exit code
786 (node_name, user_storage_type, volume_name) = args
788 storage_type = ConvertStorageType(user_storage_type)
792 if opts.allocatable is not None:
793 changes[constants.SF_ALLOCATABLE] = opts.allocatable
796 op = opcodes.OpNodeModifyStorage(node_name=node_name,
797 storage_type=storage_type,
800 SubmitOpCode(op, opts=opts)
802 ToStderr("No changes to perform, exiting.")
805 def RepairStorage(opts, args):
806 """Repairs a storage volume on a node.
808 @param opts: the command line options selected by the user
810 @param args: should contain 3 items: node name, storage type and volume name
812 @return: the desired exit code
815 (node_name, user_storage_type, volume_name) = args
817 storage_type = ConvertStorageType(user_storage_type)
819 op = opcodes.OpRepairNodeStorage(node_name=node_name,
820 storage_type=storage_type,
822 ignore_consistency=opts.ignore_consistency)
823 SubmitOpCode(op, opts=opts)
826 def SetNodeParams(opts, args):
829 @param opts: the command line options selected by the user
831 @param args: should contain only one element, the node name
833 @return: the desired exit code
836 all_changes = [opts.master_candidate, opts.drained, opts.offline,
837 opts.master_capable, opts.vm_capable, opts.secondary_ip,
839 if (all_changes.count(None) == len(all_changes) and
840 not (opts.hv_state or opts.disk_state)):
841 ToStderr("Please give at least one of the parameters.")
845 disk_state = utils.FlatToDict(opts.disk_state)
849 hv_state = dict(opts.hv_state)
851 op = opcodes.OpNodeSetParams(node_name=args[0],
852 master_candidate=opts.master_candidate,
853 offline=opts.offline,
854 drained=opts.drained,
855 master_capable=opts.master_capable,
856 vm_capable=opts.vm_capable,
857 secondary_ip=opts.secondary_ip,
859 ndparams=opts.ndparams,
860 auto_promote=opts.auto_promote,
861 powered=opts.node_powered,
863 disk_state=disk_state)
865 # even if here we process the result, we allow submit only
866 result = SubmitOrSend(op, opts)
869 ToStdout("Modified node %s", args[0])
870 for param, data in result:
871 ToStdout(" - %-5s -> %s", param, data)
877 AddNode, [ArgHost(min=1, max=1)],
878 [SECONDARY_IP_OPT, READD_OPT, NOSSH_KEYCHECK_OPT, NODE_FORCE_JOIN_OPT,
879 NONODE_SETUP_OPT, VERBOSE_OPT, NODEGROUP_OPT, PRIORITY_OPT,
880 CAPAB_MASTER_OPT, CAPAB_VM_OPT, NODE_PARAMS_OPT, HV_STATE_OPT,
882 "[-s ip] [--readd] [--no-ssh-key-check] [--force-join]"
883 " [--no-node-setup] [--verbose]"
885 "Add a node to the cluster"),
887 EvacuateNode, ARGS_ONE_NODE,
888 [FORCE_OPT, IALLOCATOR_OPT, NEW_SECONDARY_OPT, EARLY_RELEASE_OPT,
889 PRIORITY_OPT, PRIMARY_ONLY_OPT, SECONDARY_ONLY_OPT],
890 "[-f] {-I <iallocator> | -n <dst>} <node>",
891 "Relocate the secondary instances from a node"
894 FailoverNode, ARGS_ONE_NODE, [FORCE_OPT, IGNORE_CONSIST_OPT,
895 IALLOCATOR_OPT, PRIORITY_OPT],
897 "Stops the primary instances on a node and start them on their"
898 " secondary node (only for instances with drbd disk template)"),
900 MigrateNode, ARGS_ONE_NODE,
901 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, DST_NODE_OPT,
902 IALLOCATOR_OPT, PRIORITY_OPT],
904 "Migrate all the primary instance on a node away from it"
905 " (only for instances of type drbd)"),
907 ShowNodeConfig, ARGS_MANY_NODES, [],
908 "[<node_name>...]", "Show information about the node(s)"),
910 ListNodes, ARGS_MANY_NODES,
911 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT,
914 "Lists the nodes in the cluster. The available fields can be shown using"
915 " the \"list-fields\" command (see the man page for details)."
916 " The default field list is (in order): %s." %
917 utils.CommaJoin(_LIST_DEF_FIELDS)),
919 ListNodeFields, [ArgUnknown()],
920 [NOHDR_OPT, SEP_OPT],
922 "Lists all available fields for nodes"),
924 SetNodeParams, ARGS_ONE_NODE,
925 [FORCE_OPT, SUBMIT_OPT, MC_OPT, DRAINED_OPT, OFFLINE_OPT,
926 CAPAB_MASTER_OPT, CAPAB_VM_OPT, SECONDARY_IP_OPT,
927 AUTO_PROMOTE_OPT, DRY_RUN_OPT, PRIORITY_OPT, NODE_PARAMS_OPT,
928 NODE_POWERED_OPT, HV_STATE_OPT, DISK_STATE_OPT],
929 "<node_name>", "Alters the parameters of a node"),
931 PowercycleNode, ARGS_ONE_NODE,
932 [FORCE_OPT, CONFIRM_OPT, DRY_RUN_OPT, PRIORITY_OPT],
933 "<node_name>", "Tries to forcefully powercycle a node"),
936 [ArgChoice(min=1, max=1, choices=_LIST_POWER_COMMANDS),
938 [SUBMIT_OPT, AUTO_PROMOTE_OPT, PRIORITY_OPT, IGNORE_STATUS_OPT,
939 FORCE_OPT, NOHDR_OPT, SEP_OPT, OOB_TIMEOUT_OPT, POWER_DELAY_OPT],
940 "on|off|cycle|status [nodes...]",
941 "Change power state of node by calling out-of-band helper."),
943 RemoveNode, ARGS_ONE_NODE, [DRY_RUN_OPT, PRIORITY_OPT],
944 "<node_name>", "Removes a node from the cluster"),
946 ListVolumes, [ArgNode()],
947 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, PRIORITY_OPT],
948 "[<node_name>...]", "List logical volumes on node(s)"),
950 ListStorage, ARGS_MANY_NODES,
951 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, _STORAGE_TYPE_OPT,
953 "[<node_name>...]", "List physical volumes on node(s). The available"
954 " fields are (see the man page for details): %s." %
955 (utils.CommaJoin(_LIST_STOR_HEADERS))),
958 [ArgNode(min=1, max=1),
959 ArgChoice(min=1, max=1, choices=_MODIFIABLE_STORAGE_TYPES),
960 ArgFile(min=1, max=1)],
961 [ALLOCATABLE_OPT, DRY_RUN_OPT, PRIORITY_OPT],
962 "<node_name> <storage_type> <name>", "Modify storage volume on a node"),
965 [ArgNode(min=1, max=1),
966 ArgChoice(min=1, max=1, choices=_REPAIRABLE_STORAGE_TYPES),
967 ArgFile(min=1, max=1)],
968 [IGNORE_CONSIST_OPT, DRY_RUN_OPT, PRIORITY_OPT],
969 "<node_name> <storage_type> <name>",
970 "Repairs a storage volume on a node"),
972 ListTags, ARGS_ONE_NODE, [],
973 "<node_name>", "List the tags of the given node"),
975 AddTags, [ArgNode(min=1, max=1), ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT],
976 "<node_name> tag...", "Add tags to the given node"),
978 RemoveTags, [ArgNode(min=1, max=1), ArgUnknown()],
979 [TAG_SRC_OPT, PRIORITY_OPT],
980 "<node_name> tag...", "Remove tags from the given node"),
982 Health, ARGS_MANY_NODES,
983 [NOHDR_OPT, SEP_OPT, SUBMIT_OPT, PRIORITY_OPT, OOB_TIMEOUT_OPT],
984 "[<node_name>...]", "List health of node(s) using out-of-band"),
989 return GenericMain(commands, override={"tag_type": constants.TAG_NODE},
990 env_override=_ENV_OVERRIDE)