diff --git a/lib/client/gnt_node.py b/lib/client/gnt_node.py index 8dfba722ca1afab2ef832ed5e753f687328f7088..005501a9c58d5d92ab98b48a76b19b820eadf072 100644 --- a/lib/client/gnt_node.py +++ b/lib/client/gnt_node.py @@ -38,6 +38,8 @@ from ganeti import errors from ganeti import netutils from cStringIO import StringIO +from ganeti import confd +from ganeti.confd import client as confd_client #: default list of field for L{ListNodes} _LIST_DEF_FIELDS = [ @@ -877,6 +879,98 @@ def SetNodeParams(opts, args): return 0 +class ReplyStatus(object): + """Class holding a reply status for synchronous confd clients. + + """ + def __init__(self): + self.failure = True + self.answer = False + + +def ListDrbd(opts, args): + """Modifies a node. + + @param opts: the command line options selected by the user + @type args: list + @param args: should contain only one element, the node name + @rtype: int + @return: the desired exit code + + """ + if len(args) != 1: + ToStderr("Please give one (and only one) node.") + return constants.EXIT_FAILURE + + if not constants.ENABLE_CONFD: + ToStderr("Error: this command requires confd support, but it has not" + " been enabled at build time.") + return constants.EXIT_FAILURE + + if not constants.HS_CONFD: + ToStderr("Error: this command requires the Haskell version of confd," + " but it has not been enabled at build time.") + return constants.EXIT_FAILURE + + status = ReplyStatus() + + def ListDrbdConfdCallback(reply): + """Callback for confd queries""" + if reply.type == confd_client.UPCALL_REPLY: + answer = reply.server_reply.answer + reqtype = reply.orig_request.type + if reqtype == constants.CONFD_REQ_NODE_DRBD: + if reply.server_reply.status != constants.CONFD_REPL_STATUS_OK: + ToStderr("Query gave non-ok status '%s': %s" % + (reply.server_reply.status, + reply.server_reply.answer)) + status.failure = True + return + if not confd.HTNodeDrbd(answer): + ToStderr("Invalid response from server: expected %s, got %s", + confd.HTNodeDrbd, answer) + status.failure = True + else: + status.failure = False + status.answer = answer + else: + ToStderr("Unexpected reply %s!?", reqtype) + status.failure = True + + node = args[0] + hmac = utils.ReadFile(constants.CONFD_HMAC_KEY) + filter_callback = confd_client.ConfdFilterCallback(ListDrbdConfdCallback) + counting_callback = confd_client.ConfdCountingCallback(filter_callback) + cf_client = confd_client.ConfdClient(hmac, [constants.IP4_ADDRESS_LOCALHOST], + counting_callback) + req = confd_client.ConfdClientRequest(type=constants.CONFD_REQ_NODE_DRBD, + query=node) + + def DoConfdRequestReply(req): + counting_callback.RegisterQuery(req.rsalt) + cf_client.SendRequest(req, async=False) + while not counting_callback.AllAnswered(): + if not cf_client.ReceiveReply(): + ToStderr("Did not receive all expected confd replies") + break + + DoConfdRequestReply(req) + + if status.failure: + return constants.EXIT_FAILURE + + fields = ["node", "minor", "instance", "disk", "role", "peer"] + headers = {"node": "Node", "minor": "Minor", "instance": "Instance", + "disk": "Disk", "role": "Role", "peer": "PeerNode"} + + data = GenerateTable(separator=opts.separator, headers=headers, + fields=fields, data=sorted(status.answer), + numfields=["minor"]) + for line in data: + ToStdout(line) + + return constants.EXIT_SUCCESS + commands = { "add": ( AddNode, [ArgHost(min=1, max=1)], @@ -988,6 +1082,10 @@ commands = { Health, ARGS_MANY_NODES, [NOHDR_OPT, SEP_OPT, PRIORITY_OPT, OOB_TIMEOUT_OPT], "[<node_name>...]", "List health of node(s) using out-of-band"), + "list-drbd": ( + ListDrbd, ARGS_ONE_NODE, + [NOHDR_OPT, SEP_OPT], + "[<node_name>]", "Query the list of used DRBD minors on the given node"), } #: dictionary with aliases for commands diff --git a/lib/confd/__init__.py b/lib/confd/__init__.py index f40ebc355a61eb192f5377f5d04d816b0b988fd1..c9cfc07841cace7d11df61f47577c57edc31d300 100644 --- a/lib/confd/__init__.py +++ b/lib/confd/__init__.py @@ -1,7 +1,7 @@ # # -# Copyright (C) 2009 Google Inc. +# Copyright (C) 2009, 2012 Google Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -25,11 +25,20 @@ from ganeti import constants from ganeti import errors +from ganeti import ht _FOURCC_LEN = 4 +#: Items in the individual rows of the NodeDrbd query +_HTNodeDrbdItems = [ht.TString, ht.TInt, ht.TString, + ht.TString, ht.TString, ht.TString] +#: Type for the (top-level) result of NodeDrbd query +HTNodeDrbd = ht.TListOf(ht.TAnd(ht.TList, ht.TIsLength(len(_HTNodeDrbdItems)), + ht.TItems(_HTNodeDrbdItems))) + + def PackMagic(payload): """Prepend the confd magic fourcc to a payload. diff --git a/lib/constants.py b/lib/constants.py index 7f85bad23bc4dc5971d0b41b4e6bb577d36dd92d..02aa6f673f7875b0a840bf15002472234d7a3613 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -174,6 +174,7 @@ TOOLSDIR = _autoconf.TOOLSDIR CONF_DIR = SYSCONFDIR + "/ganeti" USER_SCRIPTS_DIR = CONF_DIR + "/scripts" ENABLE_CONFD = _autoconf.ENABLE_CONFD +HS_CONFD = _autoconf.HS_CONFD #: Lock file for watcher, locked in shared mode by watcher; lock in exclusive # mode to block watcher (see L{cli._RunWhileClusterStoppedHelper.Call} diff --git a/man/gnt-node.rst b/man/gnt-node.rst index 35683c8cdf85fad32fcf3a37f85306eeb15a5bcc..8e0fd6740755eb9b382f26ff4d260ed674e8daec 100644 --- a/man/gnt-node.rst +++ b/man/gnt-node.rst @@ -235,6 +235,37 @@ If no node names are given, then all nodes are queried. Otherwise, only the given nodes will be listed. +LIST-DRBD +~~~~~~~~~ + +**list-drbd** [\--no-headers] [\--separator=*SEPARATOR*] node + +Lists the mapping of DRBD minors for a given node. This outputs a static +list of fields (it doesn't accept the ``--output`` option), as follows: + +``Node`` + The (full) name of the node we are querying +``Minor`` + The DRBD minor +``Instance`` + The instance the DRBD minor belongs to +``Disk`` + The disk index that the DRBD minor belongs to +``Role`` + Either ``primary`` or ``secondary``, denoting the role of the node for + the instance (note: this is not the live status of the DRBD device, + but the configuration value) +``PeerNode`` + The node that the minor is connected to on the other end + +This command can be used as a reverse lookup (from node and minor) to a +given instance, which can be useful when debugging DRBD issues. + +Note that this command queries Ganeti via :manpage:`ganeti-confd(8)`, so +it won't be available if support for ``confd`` has not been enabled at +build time; furthermore, in Ganeti 2.6 this is only available via the +Haskell version of confd (again selected at build time). + LIST-FIELDS ~~~~~~~~~~~