4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 """Node related commands"""
23 # pylint: disable=W0401,W0613,W0614,C0103
24 # W0401: Wildcard import ganeti.cli
25 # W0613: Unused argument, since all functions follow the same API
26 # W0614: Unused import %s from wildcard import (since we need cli)
27 # C0103: Invalid name gnt-node
31 from ganeti.cli import *
32 from ganeti import cli
33 from ganeti import bootstrap
34 from ganeti import opcodes
35 from ganeti import utils
36 from ganeti import constants
37 from ganeti import errors
38 from ganeti import netutils
39 from cStringIO import StringIO
41 from ganeti import confd
42 from ganeti.confd import client as confd_client
44 #: default list of field for L{ListNodes}
46 "name", "dtotal", "dfree",
47 "mtotal", "mnode", "mfree",
48 "pinst_cnt", "sinst_cnt",
52 #: Default field list for L{ListVolumes}
53 _LIST_VOL_DEF_FIELDS = ["node", "phys", "vg", "name", "size", "instance"]
56 #: default list of field for L{ListStorage}
57 _LIST_STOR_DEF_FIELDS = [
64 constants.SF_ALLOCATABLE,
68 #: default list of power commands
69 _LIST_POWER_COMMANDS = ["on", "off", "cycle", "status"]
72 #: headers (and full field list) for L{ListStorage}
73 _LIST_STOR_HEADERS = {
74 constants.SF_NODE: "Node",
75 constants.SF_TYPE: "Type",
76 constants.SF_NAME: "Name",
77 constants.SF_SIZE: "Size",
78 constants.SF_USED: "Used",
79 constants.SF_FREE: "Free",
80 constants.SF_ALLOCATABLE: "Allocatable",
84 #: User-facing storage unit types
85 _USER_STORAGE_TYPE = {
86 constants.ST_FILE: "file",
87 constants.ST_LVM_PV: "lvm-pv",
88 constants.ST_LVM_VG: "lvm-vg",
92 cli_option("-t", "--storage-type",
93 dest="user_storage_type",
94 choices=_USER_STORAGE_TYPE.keys(),
96 metavar="STORAGE_TYPE",
97 help=("Storage type (%s)" %
98 utils.CommaJoin(_USER_STORAGE_TYPE.keys())))
100 _REPAIRABLE_STORAGE_TYPES = \
101 [st for st, so in constants.VALID_STORAGE_OPERATIONS.iteritems()
102 if constants.SO_FIX_CONSISTENCY in so]
104 _MODIFIABLE_STORAGE_TYPES = constants.MODIFIABLE_STORAGE_FIELDS.keys()
107 _OOB_COMMAND_ASK = frozenset([constants.OOB_POWER_OFF,
108 constants.OOB_POWER_CYCLE])
111 _ENV_OVERRIDE = frozenset(["list"])
114 NONODE_SETUP_OPT = cli_option("--no-node-setup", default=True,
115 action="store_false", dest="node_setup",
116 help=("Do not make initial SSH setup on remote"
117 " node (needs to be done manually)"))
119 IGNORE_STATUS_OPT = cli_option("--ignore-status", default=False,
120 action="store_true", dest="ignore_status",
121 help=("Ignore the Node(s) offline status"
122 " (potentially DANGEROUS)"))
125 def ConvertStorageType(user_storage_type):
126 """Converts a user storage type to its internal name.
130 return _USER_STORAGE_TYPE[user_storage_type]
132 raise errors.OpPrereqError("Unknown storage type: %s" % user_storage_type,
136 def _RunSetupSSH(options, nodes):
137 """Wrapper around utils.RunCmd to call setup-ssh
139 @param options: The command line options
140 @param nodes: The nodes to setup
144 assert nodes, "Empty node list"
146 cmd = [constants.SETUP_SSH]
148 # Pass --debug|--verbose to the external script if set on our invocation
149 # --debug overrides --verbose
151 cmd.append("--debug")
152 elif options.verbose:
153 cmd.append("--verbose")
154 if not options.ssh_key_check:
155 cmd.append("--no-ssh-key-check")
156 if options.force_join:
157 cmd.append("--force-join")
161 result = utils.RunCmd(cmd, interactive=True)
164 errmsg = ("Command '%s' failed with exit code %s; output %r" %
165 (result.cmd, result.exit_code, result.output))
166 raise errors.OpExecError(errmsg)
170 def AddNode(opts, args):
171 """Add a node to the cluster.
173 @param opts: the command line options selected by the user
175 @param args: should contain only one element, the new node name
177 @return: the desired exit code
181 node = netutils.GetHostname(name=args[0]).name
185 output = cl.QueryNodes(names=[node], fields=["name", "sip", "master"],
187 node_exists, sip, is_master = output[0]
188 except (errors.OpPrereqError, errors.OpExecError):
194 ToStderr("Node %s not in the cluster"
195 " - please retry without '--readd'", node)
198 ToStderr("Node %s is the master, cannot readd", node)
202 ToStderr("Node %s already in the cluster (as %s)"
203 " - please retry with '--readd'", node, node_exists)
205 sip = opts.secondary_ip
207 # read the cluster name from the master
208 output = cl.QueryConfigValues(["cluster_name"])
209 cluster_name = output[0]
211 if not readd and opts.node_setup:
212 ToStderr("-- WARNING -- \n"
213 "Performing this operation is going to replace the ssh daemon"
215 "on the target machine (%s) with the ones of the"
217 "and grant full intra-cluster ssh root access to/from it\n", node)
220 _RunSetupSSH(opts, [node])
222 bootstrap.SetupNodeDaemon(cluster_name, node, opts.ssh_key_check)
225 disk_state = utils.FlatToDict(opts.disk_state)
229 hv_state = dict(opts.hv_state)
231 op = opcodes.OpNodeAdd(node_name=args[0], secondary_ip=sip,
232 readd=opts.readd, group=opts.nodegroup,
233 vm_capable=opts.vm_capable, ndparams=opts.ndparams,
234 master_capable=opts.master_capable,
235 disk_state=disk_state,
237 SubmitOpCode(op, opts=opts)
240 def ListNodes(opts, args):
241 """List nodes and their properties.
243 @param opts: the command line options selected by the user
245 @param args: nodes to list, or empty for all
247 @return: the desired exit code
250 selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
252 fmtoverride = dict.fromkeys(["pinst_list", "sinst_list", "tags"],
255 return GenericList(constants.QR_NODE, selected_fields, args, opts.units,
256 opts.separator, not opts.no_headers,
257 format_override=fmtoverride, verbose=opts.verbose,
258 force_filter=opts.force_filter)
261 def ListNodeFields(opts, args):
264 @param opts: the command line options selected by the user
266 @param args: fields to list, or empty for all
268 @return: the desired exit code
271 return GenericListFields(constants.QR_NODE, args, opts.separator,
275 def EvacuateNode(opts, args):
276 """Relocate all secondary instance from a node.
278 @param opts: the command line options selected by the user
280 @param args: should be an empty list
282 @return: the desired exit code
285 if opts.dst_node is not None:
286 ToStderr("New secondary node given (disabling iallocator), hence evacuating"
287 " secondary instances only.")
288 opts.secondary_only = True
289 opts.primary_only = False
291 if opts.secondary_only and opts.primary_only:
292 raise errors.OpPrereqError("Only one of the --primary-only and"
293 " --secondary-only options can be passed",
295 elif opts.primary_only:
296 mode = constants.NODE_EVAC_PRI
297 elif opts.secondary_only:
298 mode = constants.NODE_EVAC_SEC
300 mode = constants.NODE_EVAC_ALL
302 # Determine affected instances
305 if not opts.secondary_only:
306 fields.append("pinst_list")
307 if not opts.primary_only:
308 fields.append("sinst_list")
312 result = cl.QueryNodes(names=args, fields=fields, use_locking=False)
313 instances = set(itertools.chain(*itertools.chain(*itertools.chain(result))))
316 # No instances to evacuate
317 ToStderr("No instances to evacuate on node(s) %s, exiting.",
318 utils.CommaJoin(args))
319 return constants.EXIT_SUCCESS
321 if not (opts.force or
322 AskUser("Relocate instance(s) %s from node(s) %s?" %
323 (utils.CommaJoin(utils.NiceSort(instances)),
324 utils.CommaJoin(args)))):
325 return constants.EXIT_CONFIRMATION
328 op = opcodes.OpNodeEvacuate(node_name=args[0], mode=mode,
329 remote_node=opts.dst_node,
330 iallocator=opts.iallocator,
331 early_release=opts.early_release)
332 result = SubmitOrSend(op, opts, cl=cl)
334 # Keep track of submitted jobs
335 jex = JobExecutor(cl=cl, opts=opts)
337 for (status, job_id) in result[constants.JOB_IDS_KEY]:
338 jex.AddJobId(None, status, job_id)
340 results = jex.GetResults()
341 bad_cnt = len([row for row in results if not row[0]])
343 ToStdout("All instances evacuated successfully.")
344 rcode = constants.EXIT_SUCCESS
346 ToStdout("There were %s errors during the evacuation.", bad_cnt)
347 rcode = constants.EXIT_FAILURE
352 def FailoverNode(opts, args):
353 """Failover all primary instance on a node.
355 @param opts: the command line options selected by the user
357 @param args: should be an empty list
359 @return: the desired exit code
364 selected_fields = ["name", "pinst_list"]
366 # these fields are static data anyway, so it doesn't matter, but
367 # locking=True should be safer
368 result = cl.QueryNodes(names=args, fields=selected_fields,
370 node, pinst = result[0]
373 ToStderr("No primary instances on node %s, exiting.", node)
376 pinst = utils.NiceSort(pinst)
380 if not force and not AskUser("Fail over instance(s) %s?" %
381 (",".join("'%s'" % name for name in pinst))):
384 jex = JobExecutor(cl=cl, opts=opts)
386 op = opcodes.OpInstanceFailover(instance_name=iname,
387 ignore_consistency=opts.ignore_consistency,
388 iallocator=opts.iallocator)
389 jex.QueueJob(iname, op)
390 results = jex.GetResults()
391 bad_cnt = len([row for row in results if not row[0]])
393 ToStdout("All %d instance(s) failed over successfully.", len(results))
395 ToStdout("There were errors during the failover:\n"
396 "%d error(s) out of %d instance(s).", bad_cnt, len(results))
400 def MigrateNode(opts, args):
401 """Migrate all primary instance on a node.
406 selected_fields = ["name", "pinst_list"]
408 result = cl.QueryNodes(names=args, fields=selected_fields, use_locking=False)
409 ((node, pinst), ) = result
412 ToStdout("No primary instances on node %s, exiting." % node)
415 pinst = utils.NiceSort(pinst)
418 AskUser("Migrate instance(s) %s?" %
419 utils.CommaJoin(utils.NiceSort(pinst)))):
420 return constants.EXIT_CONFIRMATION
422 # this should be removed once --non-live is deprecated
423 if not opts.live and opts.migration_mode is not None:
424 raise errors.OpPrereqError("Only one of the --non-live and "
425 "--migration-mode options can be passed",
427 if not opts.live: # --non-live passed
428 mode = constants.HT_MIGRATION_NONLIVE
430 mode = opts.migration_mode
432 op = opcodes.OpNodeMigrate(node_name=args[0], mode=mode,
433 iallocator=opts.iallocator,
434 target_node=opts.dst_node,
435 allow_runtime_changes=opts.allow_runtime_chgs,
436 ignore_ipolicy=opts.ignore_ipolicy)
438 result = SubmitOrSend(op, opts, cl=cl)
440 # Keep track of submitted jobs
441 jex = JobExecutor(cl=cl, opts=opts)
443 for (status, job_id) in result[constants.JOB_IDS_KEY]:
444 jex.AddJobId(None, status, job_id)
446 results = jex.GetResults()
447 bad_cnt = len([row for row in results if not row[0]])
449 ToStdout("All instances migrated successfully.")
450 rcode = constants.EXIT_SUCCESS
452 ToStdout("There were %s errors during the node migration.", bad_cnt)
453 rcode = constants.EXIT_FAILURE
458 def ShowNodeConfig(opts, args):
459 """Show node information.
461 @param opts: the command line options selected by the user
463 @param args: should either be an empty list, in which case
464 we show information about all nodes, or should contain
465 a list of nodes to be queried for information
467 @return: the desired exit code
471 result = cl.QueryNodes(fields=["name", "pip", "sip",
472 "pinst_list", "sinst_list",
473 "master_candidate", "drained", "offline",
474 "master_capable", "vm_capable", "powered",
475 "ndparams", "custom_ndparams"],
476 names=args, use_locking=False)
478 for (name, primary_ip, secondary_ip, pinst, sinst, is_mc, drained, offline,
479 master_capable, vm_capable, powered, ndparams,
480 ndparams_custom) in result:
481 ToStdout("Node name: %s", name)
482 ToStdout(" primary ip: %s", primary_ip)
483 ToStdout(" secondary ip: %s", secondary_ip)
484 ToStdout(" master candidate: %s", is_mc)
485 ToStdout(" drained: %s", drained)
486 ToStdout(" offline: %s", offline)
487 if powered is not None:
488 ToStdout(" powered: %s", powered)
489 ToStdout(" master_capable: %s", master_capable)
490 ToStdout(" vm_capable: %s", vm_capable)
493 ToStdout(" primary for instances:")
494 for iname in utils.NiceSort(pinst):
495 ToStdout(" - %s", iname)
497 ToStdout(" primary for no instances")
499 ToStdout(" secondary for instances:")
500 for iname in utils.NiceSort(sinst):
501 ToStdout(" - %s", iname)
503 ToStdout(" secondary for no instances")
504 ToStdout(" node parameters:")
506 FormatParameterDict(buf, ndparams_custom, ndparams, level=2)
507 ToStdout(buf.getvalue().rstrip("\n"))
512 def RemoveNode(opts, args):
513 """Remove a node from the cluster.
515 @param opts: the command line options selected by the user
517 @param args: should contain only one element, the name of
518 the node to be removed
520 @return: the desired exit code
523 op = opcodes.OpNodeRemove(node_name=args[0])
524 SubmitOpCode(op, opts=opts)
528 def PowercycleNode(opts, args):
529 """Remove a node from the cluster.
531 @param opts: the command line options selected by the user
533 @param args: should contain only one element, the name of
534 the node to be removed
536 @return: the desired exit code
540 if (not opts.confirm and
541 not AskUser("Are you sure you want to hard powercycle node %s?" % node)):
544 op = opcodes.OpNodePowercycle(node_name=node, force=opts.force)
545 result = SubmitOrSend(op, opts)
551 def PowerNode(opts, args):
552 """Change/ask power state of a node.
554 @param opts: the command line options selected by the user
556 @param args: should contain only one element, the name of
557 the node to be removed
559 @return: the desired exit code
562 command = args.pop(0)
567 headers = {"node": "Node", "status": "Status"}
569 if command not in _LIST_POWER_COMMANDS:
570 ToStderr("power subcommand %s not supported." % command)
571 return constants.EXIT_FAILURE
573 oob_command = "power-%s" % command
575 if oob_command in _OOB_COMMAND_ASK:
577 ToStderr("Please provide at least one node for this command")
578 return constants.EXIT_FAILURE
579 elif not opts.force and not ConfirmOperation(args, "nodes",
580 "power %s" % command):
581 return constants.EXIT_FAILURE
585 if not opts.ignore_status and oob_command == constants.OOB_POWER_OFF:
586 # TODO: This is a little ugly as we can't catch and revert
588 opcodelist.append(opcodes.OpNodeSetParams(node_name=node, offline=True,
589 auto_promote=opts.auto_promote))
591 opcodelist.append(opcodes.OpOobCommand(node_names=args,
593 ignore_status=opts.ignore_status,
594 timeout=opts.oob_timeout,
595 power_delay=opts.power_delay))
597 cli.SetGenericOpcodeOpts(opcodelist, opts)
599 job_id = cli.SendJob(opcodelist)
601 # We just want the OOB Opcode status
602 # If it fails PollJob gives us the error message in it
603 result = cli.PollJob(job_id)[-1]
607 for node_result in result:
608 (node_tuple, data_tuple) = node_result
609 (_, node_name) = node_tuple
610 (data_status, data_node) = data_tuple
611 if data_status == constants.RS_NORMAL:
612 if oob_command == constants.OOB_POWER_STATUS:
613 if data_node[constants.OOB_POWER_STATUS_POWERED]:
617 data.append([node_name, text])
619 # We don't expect data here, so we just say, it was successfully invoked
620 data.append([node_name, "invoked"])
623 data.append([node_name, cli.FormatResultError(data_status, True)])
625 data = GenerateTable(separator=opts.separator, headers=headers,
626 fields=["node", "status"], data=data)
632 return constants.EXIT_FAILURE
634 return constants.EXIT_SUCCESS
637 def Health(opts, args):
638 """Show health of a node using OOB.
640 @param opts: the command line options selected by the user
642 @param args: should contain only one element, the name of
643 the node to be removed
645 @return: the desired exit code
648 op = opcodes.OpOobCommand(node_names=args, command=constants.OOB_HEALTH,
649 timeout=opts.oob_timeout)
650 result = SubmitOpCode(op, opts=opts)
655 headers = {"node": "Node", "status": "Status"}
659 for node_result in result:
660 (node_tuple, data_tuple) = node_result
661 (_, node_name) = node_tuple
662 (data_status, data_node) = data_tuple
663 if data_status == constants.RS_NORMAL:
664 data.append([node_name, "%s=%s" % tuple(data_node[0])])
665 for item, status in data_node[1:]:
666 data.append(["", "%s=%s" % (item, status)])
669 data.append([node_name, cli.FormatResultError(data_status, True)])
671 data = GenerateTable(separator=opts.separator, headers=headers,
672 fields=["node", "status"], data=data)
678 return constants.EXIT_FAILURE
680 return constants.EXIT_SUCCESS
683 def ListVolumes(opts, args):
684 """List logical volumes on node(s).
686 @param opts: the command line options selected by the user
688 @param args: should either be an empty list, in which case
689 we list data for all nodes, or contain a list of nodes
690 to display data only for those
692 @return: the desired exit code
695 selected_fields = ParseFields(opts.output, _LIST_VOL_DEF_FIELDS)
697 op = opcodes.OpNodeQueryvols(nodes=args, output_fields=selected_fields)
698 output = SubmitOpCode(op, opts=opts)
700 if not opts.no_headers:
701 headers = {"node": "Node", "phys": "PhysDev",
702 "vg": "VG", "name": "Name",
703 "size": "Size", "instance": "Instance"}
707 unitfields = ["size"]
711 data = GenerateTable(separator=opts.separator, headers=headers,
712 fields=selected_fields, unitfields=unitfields,
713 numfields=numfields, data=output, units=opts.units)
721 def ListStorage(opts, args):
722 """List physical volumes on node(s).
724 @param opts: the command line options selected by the user
726 @param args: should either be an empty list, in which case
727 we list data for all nodes, or contain a list of nodes
728 to display data only for those
730 @return: the desired exit code
733 # TODO: Default to ST_FILE if LVM is disabled on the cluster
734 if opts.user_storage_type is None:
735 opts.user_storage_type = constants.ST_LVM_PV
737 storage_type = ConvertStorageType(opts.user_storage_type)
739 selected_fields = ParseFields(opts.output, _LIST_STOR_DEF_FIELDS)
741 op = opcodes.OpNodeQueryStorage(nodes=args,
742 storage_type=storage_type,
743 output_fields=selected_fields)
744 output = SubmitOpCode(op, opts=opts)
746 if not opts.no_headers:
748 constants.SF_NODE: "Node",
749 constants.SF_TYPE: "Type",
750 constants.SF_NAME: "Name",
751 constants.SF_SIZE: "Size",
752 constants.SF_USED: "Used",
753 constants.SF_FREE: "Free",
754 constants.SF_ALLOCATABLE: "Allocatable",
759 unitfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
760 numfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
762 # change raw values to nicer strings
764 for idx, field in enumerate(selected_fields):
766 if field == constants.SF_ALLOCATABLE:
773 data = GenerateTable(separator=opts.separator, headers=headers,
774 fields=selected_fields, unitfields=unitfields,
775 numfields=numfields, data=output, units=opts.units)
783 def ModifyStorage(opts, args):
784 """Modify storage volume on a node.
786 @param opts: the command line options selected by the user
788 @param args: should contain 3 items: node name, storage type and volume name
790 @return: the desired exit code
793 (node_name, user_storage_type, volume_name) = args
795 storage_type = ConvertStorageType(user_storage_type)
799 if opts.allocatable is not None:
800 changes[constants.SF_ALLOCATABLE] = opts.allocatable
803 op = opcodes.OpNodeModifyStorage(node_name=node_name,
804 storage_type=storage_type,
807 SubmitOrSend(op, opts)
809 ToStderr("No changes to perform, exiting.")
812 def RepairStorage(opts, args):
813 """Repairs a storage volume on a node.
815 @param opts: the command line options selected by the user
817 @param args: should contain 3 items: node name, storage type and volume name
819 @return: the desired exit code
822 (node_name, user_storage_type, volume_name) = args
824 storage_type = ConvertStorageType(user_storage_type)
826 op = opcodes.OpRepairNodeStorage(node_name=node_name,
827 storage_type=storage_type,
829 ignore_consistency=opts.ignore_consistency)
830 SubmitOrSend(op, opts)
833 def SetNodeParams(opts, args):
836 @param opts: the command line options selected by the user
838 @param args: should contain only one element, the node name
840 @return: the desired exit code
843 all_changes = [opts.master_candidate, opts.drained, opts.offline,
844 opts.master_capable, opts.vm_capable, opts.secondary_ip,
846 if (all_changes.count(None) == len(all_changes) and
847 not (opts.hv_state or opts.disk_state)):
848 ToStderr("Please give at least one of the parameters.")
852 disk_state = utils.FlatToDict(opts.disk_state)
856 hv_state = dict(opts.hv_state)
858 op = opcodes.OpNodeSetParams(node_name=args[0],
859 master_candidate=opts.master_candidate,
860 offline=opts.offline,
861 drained=opts.drained,
862 master_capable=opts.master_capable,
863 vm_capable=opts.vm_capable,
864 secondary_ip=opts.secondary_ip,
866 ndparams=opts.ndparams,
867 auto_promote=opts.auto_promote,
868 powered=opts.node_powered,
870 disk_state=disk_state)
872 # even if here we process the result, we allow submit only
873 result = SubmitOrSend(op, opts)
876 ToStdout("Modified node %s", args[0])
877 for param, data in result:
878 ToStdout(" - %-5s -> %s", param, data)
882 class ReplyStatus(object):
883 """Class holding a reply status for synchronous confd clients.
891 def ListDrbd(opts, args):
894 @param opts: the command line options selected by the user
896 @param args: should contain only one element, the node name
898 @return: the desired exit code
902 ToStderr("Please give one (and only one) node.")
903 return constants.EXIT_FAILURE
905 if not constants.ENABLE_CONFD:
906 ToStderr("Error: this command requires confd support, but it has not"
907 " been enabled at build time.")
908 return constants.EXIT_FAILURE
910 if not constants.HS_CONFD:
911 ToStderr("Error: this command requires the Haskell version of confd,"
912 " but it has not been enabled at build time.")
913 return constants.EXIT_FAILURE
915 status = ReplyStatus()
917 def ListDrbdConfdCallback(reply):
918 """Callback for confd queries"""
919 if reply.type == confd_client.UPCALL_REPLY:
920 answer = reply.server_reply.answer
921 reqtype = reply.orig_request.type
922 if reqtype == constants.CONFD_REQ_NODE_DRBD:
923 if reply.server_reply.status != constants.CONFD_REPL_STATUS_OK:
924 ToStderr("Query gave non-ok status '%s': %s" %
925 (reply.server_reply.status,
926 reply.server_reply.answer))
927 status.failure = True
929 if not confd.HTNodeDrbd(answer):
930 ToStderr("Invalid response from server: expected %s, got %s",
931 confd.HTNodeDrbd, answer)
932 status.failure = True
934 status.failure = False
935 status.answer = answer
937 ToStderr("Unexpected reply %s!?", reqtype)
938 status.failure = True
941 hmac = utils.ReadFile(constants.CONFD_HMAC_KEY)
942 filter_callback = confd_client.ConfdFilterCallback(ListDrbdConfdCallback)
943 counting_callback = confd_client.ConfdCountingCallback(filter_callback)
944 cf_client = confd_client.ConfdClient(hmac, [constants.IP4_ADDRESS_LOCALHOST],
946 req = confd_client.ConfdClientRequest(type=constants.CONFD_REQ_NODE_DRBD,
949 def DoConfdRequestReply(req):
950 counting_callback.RegisterQuery(req.rsalt)
951 cf_client.SendRequest(req, async=False)
952 while not counting_callback.AllAnswered():
953 if not cf_client.ReceiveReply():
954 ToStderr("Did not receive all expected confd replies")
957 DoConfdRequestReply(req)
960 return constants.EXIT_FAILURE
962 fields = ["node", "minor", "instance", "disk", "role", "peer"]
966 headers = {"node": "Node", "minor": "Minor", "instance": "Instance",
967 "disk": "Disk", "role": "Role", "peer": "PeerNode"}
969 data = GenerateTable(separator=opts.separator, headers=headers,
970 fields=fields, data=sorted(status.answer),
975 return constants.EXIT_SUCCESS
979 AddNode, [ArgHost(min=1, max=1)],
980 [SECONDARY_IP_OPT, READD_OPT, NOSSH_KEYCHECK_OPT, NODE_FORCE_JOIN_OPT,
981 NONODE_SETUP_OPT, VERBOSE_OPT, NODEGROUP_OPT, PRIORITY_OPT,
982 CAPAB_MASTER_OPT, CAPAB_VM_OPT, NODE_PARAMS_OPT, HV_STATE_OPT,
984 "[-s ip] [--readd] [--no-ssh-key-check] [--force-join]"
985 " [--no-node-setup] [--verbose]"
987 "Add a node to the cluster"),
989 EvacuateNode, ARGS_ONE_NODE,
990 [FORCE_OPT, IALLOCATOR_OPT, NEW_SECONDARY_OPT, EARLY_RELEASE_OPT,
991 PRIORITY_OPT, PRIMARY_ONLY_OPT, SECONDARY_ONLY_OPT, SUBMIT_OPT],
992 "[-f] {-I <iallocator> | -n <dst>} [-p | -s] [options...] <node>",
993 "Relocate the primary and/or secondary instances from a node"),
995 FailoverNode, ARGS_ONE_NODE, [FORCE_OPT, IGNORE_CONSIST_OPT,
996 IALLOCATOR_OPT, PRIORITY_OPT],
998 "Stops the primary instances on a node and start them on their"
999 " secondary node (only for instances with drbd disk template)"),
1001 MigrateNode, ARGS_ONE_NODE,
1002 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, DST_NODE_OPT,
1003 IALLOCATOR_OPT, PRIORITY_OPT, IGNORE_IPOLICY_OPT,
1004 NORUNTIME_CHGS_OPT, SUBMIT_OPT],
1006 "Migrate all the primary instance on a node away from it"
1007 " (only for instances of type drbd)"),
1009 ShowNodeConfig, ARGS_MANY_NODES, [],
1010 "[<node_name>...]", "Show information about the node(s)"),
1012 ListNodes, ARGS_MANY_NODES,
1013 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT,
1016 "Lists the nodes in the cluster. The available fields can be shown using"
1017 " the \"list-fields\" command (see the man page for details)."
1018 " The default field list is (in order): %s." %
1019 utils.CommaJoin(_LIST_DEF_FIELDS)),
1021 ListNodeFields, [ArgUnknown()],
1022 [NOHDR_OPT, SEP_OPT],
1024 "Lists all available fields for nodes"),
1026 SetNodeParams, ARGS_ONE_NODE,
1027 [FORCE_OPT, SUBMIT_OPT, MC_OPT, DRAINED_OPT, OFFLINE_OPT,
1028 CAPAB_MASTER_OPT, CAPAB_VM_OPT, SECONDARY_IP_OPT,
1029 AUTO_PROMOTE_OPT, DRY_RUN_OPT, PRIORITY_OPT, NODE_PARAMS_OPT,
1030 NODE_POWERED_OPT, HV_STATE_OPT, DISK_STATE_OPT],
1031 "<node_name>", "Alters the parameters of a node"),
1033 PowercycleNode, ARGS_ONE_NODE,
1034 [FORCE_OPT, CONFIRM_OPT, DRY_RUN_OPT, PRIORITY_OPT, SUBMIT_OPT],
1035 "<node_name>", "Tries to forcefully powercycle a node"),
1038 [ArgChoice(min=1, max=1, choices=_LIST_POWER_COMMANDS),
1040 [SUBMIT_OPT, AUTO_PROMOTE_OPT, PRIORITY_OPT, IGNORE_STATUS_OPT,
1041 FORCE_OPT, NOHDR_OPT, SEP_OPT, OOB_TIMEOUT_OPT, POWER_DELAY_OPT],
1042 "on|off|cycle|status [nodes...]",
1043 "Change power state of node by calling out-of-band helper."),
1045 RemoveNode, ARGS_ONE_NODE, [DRY_RUN_OPT, PRIORITY_OPT],
1046 "<node_name>", "Removes a node from the cluster"),
1048 ListVolumes, [ArgNode()],
1049 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, PRIORITY_OPT],
1050 "[<node_name>...]", "List logical volumes on node(s)"),
1052 ListStorage, ARGS_MANY_NODES,
1053 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, _STORAGE_TYPE_OPT,
1055 "[<node_name>...]", "List physical volumes on node(s). The available"
1056 " fields are (see the man page for details): %s." %
1057 (utils.CommaJoin(_LIST_STOR_HEADERS))),
1060 [ArgNode(min=1, max=1),
1061 ArgChoice(min=1, max=1, choices=_MODIFIABLE_STORAGE_TYPES),
1062 ArgFile(min=1, max=1)],
1063 [ALLOCATABLE_OPT, DRY_RUN_OPT, PRIORITY_OPT, SUBMIT_OPT],
1064 "<node_name> <storage_type> <name>", "Modify storage volume on a node"),
1067 [ArgNode(min=1, max=1),
1068 ArgChoice(min=1, max=1, choices=_REPAIRABLE_STORAGE_TYPES),
1069 ArgFile(min=1, max=1)],
1070 [IGNORE_CONSIST_OPT, DRY_RUN_OPT, PRIORITY_OPT, SUBMIT_OPT],
1071 "<node_name> <storage_type> <name>",
1072 "Repairs a storage volume on a node"),
1074 ListTags, ARGS_ONE_NODE, [],
1075 "<node_name>", "List the tags of the given node"),
1077 AddTags, [ArgNode(min=1, max=1), ArgUnknown()],
1078 [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
1079 "<node_name> tag...", "Add tags to the given node"),
1081 RemoveTags, [ArgNode(min=1, max=1), ArgUnknown()],
1082 [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
1083 "<node_name> tag...", "Remove tags from the given node"),
1085 Health, ARGS_MANY_NODES,
1086 [NOHDR_OPT, SEP_OPT, PRIORITY_OPT, OOB_TIMEOUT_OPT],
1087 "[<node_name>...]", "List health of node(s) using out-of-band"),
1089 ListDrbd, ARGS_ONE_NODE,
1090 [NOHDR_OPT, SEP_OPT],
1091 "[<node_name>]", "Query the list of used DRBD minors on the given node"),
1094 #: dictionary with aliases for commands
1101 return GenericMain(commands, aliases=aliases,
1102 override={"tag_type": constants.TAG_NODE},
1103 env_override=_ENV_OVERRIDE)