From 7acbda7b126816e2cb050ff7af4f62333791505f Mon Sep 17 00:00:00 2001
From: Iustin Pop <iustin@google.com>
Date: Wed, 18 Jul 2012 11:44:48 +0200
Subject: [PATCH] Add a new gnt-node command list-drbd

This uses confd to query the DRBD minors, which is very special; no
other command currently does so.

Since the backend is only implemented in the Haskell version of confd,
we have checks that 1) confd is enable, and 2) hs confd is also
enabled. If by mistake people do manage to query Python confd, the
error message will be clean:

  Query gave non-ok status '2': not implemented

So nothing breaks in an "ugly" way.

Signed-off-by: Iustin Pop <iustin@google.com>
Reviewed-by: Agata Murawska <agatamurawska@google.com>
---
 lib/client/gnt_node.py | 98 ++++++++++++++++++++++++++++++++++++++++++
 lib/confd/__init__.py  | 11 ++++-
 lib/constants.py       |  1 +
 man/gnt-node.rst       | 31 +++++++++++++
 4 files changed, 140 insertions(+), 1 deletion(-)

diff --git a/lib/client/gnt_node.py b/lib/client/gnt_node.py
index 8dfba722c..005501a9c 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 f40ebc355..c9cfc0784 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 7f85bad23..02aa6f673 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 35683c8cd..8e0fd6740 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
 ~~~~~~~~~~~
 
-- 
GitLab