According to the QEMU Machine Protocol Specification, the messages sent
by QMP as a response to a command can be of two types: either an error
message (identified by the "error" key), or a success message
(identified by the "return" key).

Since we explicitly handle errors by raising a HypervisorError, there is
no reason to return the whole response message; instead we return only
the response part, which is the dual behavior of accepting commands from
the callers without the surrounding {"execute": ...} object.

We also unexport the RETURN_KEY constant, which is now only used
internally.

Signed-off-by: Apollon Oikonomopoulos <[email protected]>
---
 lib/hypervisor/hv_kvm/__init__.py            |  4 ++--
 lib/hypervisor/hv_kvm/monitor.py             | 18 ++++++++++++------
 test/py/ganeti.hypervisor.hv_kvm_unittest.py | 15 ++++++++-------
 3 files changed, 22 insertions(+), 15 deletions(-)

diff --git a/lib/hypervisor/hv_kvm/__init__.py 
b/lib/hypervisor/hv_kvm/__init__.py
index 227dafd..496bc6b 100644
--- a/lib/hypervisor/hv_kvm/__init__.py
+++ b/lib/hypervisor/hv_kvm/__init__.py
@@ -856,10 +856,10 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     try:
       qmp = QmpConnection(self._InstanceQmpMonitor(instance_name))
       qmp.connect()
-      vcpus = len(qmp.Execute("query-cpus")[qmp.RETURN_KEY])
+      vcpus = len(qmp.Execute("query-cpus"))
       # Will fail if ballooning is not enabled, but we can then just resort to
       # the value above.
-      mem_bytes = qmp.Execute("query-balloon")[qmp.RETURN_KEY][qmp.ACTUAL_KEY]
+      mem_bytes = qmp.Execute("query-balloon")[qmp.ACTUAL_KEY]
       memory = mem_bytes / 1048576
     except errors.HypervisorError:
       pass
diff --git a/lib/hypervisor/hv_kvm/monitor.py b/lib/hypervisor/hv_kvm/monitor.py
index 8176a41..b93cacd 100644
--- a/lib/hypervisor/hv_kvm/monitor.py
+++ b/lib/hypervisor/hv_kvm/monitor.py
@@ -189,7 +189,7 @@ class QmpConnection(MonitorSocket):
   _FIRST_MESSAGE_KEY = "QMP"
   _EVENT_KEY = "event"
   _ERROR_KEY = "error"
-  _RETURN_KEY = RETURN_KEY = "return"
+  _RETURN_KEY = "return"
   _ACTUAL_KEY = ACTUAL_KEY = "actual"
   _ERROR_CLASS_KEY = "class"
   _ERROR_DESC_KEY = "desc"
@@ -339,7 +339,7 @@ class QmpConnection(MonitorSocket):
 
     """
     result = self.Execute(self._QUERY_COMMANDS)
-    return frozenset(com["name"] for com in result[self._RETURN_KEY])
+    return frozenset(com["name"] for com in result)
 
   def Execute(self, command, arguments=None):
     """Executes a QMP command and returns the response of the server.
@@ -368,8 +368,11 @@ class QmpConnection(MonitorSocket):
       message[self._ARGUMENTS_KEY] = arguments
     self._Send(message)
 
-    # Events can occur between the sending of the command and the reception
-    # of the response, so we need to filter out messages with the event key.
+    # According the the QMP specification, there are only two reply types to a
+    # command: either error (containing the "error" key) or success (containing
+    # the "return" key). There is also a third possibility, that of an
+    # (unrelated to the command) asynchronous event notification, identified by
+    # the "event" key.
     while True:
       response = self._Recv()
       err = response[self._ERROR_KEY]
@@ -380,5 +383,8 @@ class QmpConnection(MonitorSocket):
                                       err[self._ERROR_DESC_KEY],
                                       err[self._ERROR_CLASS_KEY]))
 
-      elif not response[self._EVENT_KEY]:
-        return response
+      elif response[self._EVENT_KEY]:
+        # Filter-out any asynchronous events
+        continue
+
+      return response[self._RETURN_KEY]
diff --git a/test/py/ganeti.hypervisor.hv_kvm_unittest.py 
b/test/py/ganeti.hypervisor.hv_kvm_unittest.py
index bc153ba..aebf1f6 100755
--- a/test/py/ganeti.hypervisor.hv_kvm_unittest.py
+++ b/test/py/ganeti.hypervisor.hv_kvm_unittest.py
@@ -189,10 +189,10 @@ class TestQmp(testutils.GanetiTestCase):
       ]
 
     expected_responses = [
-      {"return": {"enabled": True, "present": True}},
-      {"return": {}},
-      {"return": [{"name": "quit"}, {"name": "eject"}]},
-      {"return": {"running": True, "singlestep": False}},
+      {"enabled": True, "present": True},
+      {},
+      [{"name": "quit"}, {"name": "eject"}],
+      {"running": True, "singlestep": False},
       ]
 
     # Set up the stub
@@ -207,11 +207,12 @@ class TestQmp(testutils.GanetiTestCase):
 
     # Format the script
     for request, expected_response in zip(requests, expected_responses):
-      response = qmp_connection.Execute(request)
-      msg = hv_kvm.QmpMessage(expected_response)
+      response = qmp_connection.Execute(request["execute"],
+                                        request["arguments"])
+      self.assertEqual(response, expected_response)
+      msg = hv_kvm.QmpMessage({"return": expected_response})
       self.assertEqual(len(str(msg).splitlines()), 1,
                        msg="Got multi-line message")
-      self.assertEqual(response, msg)
 
     self.assertRaises(monitor.QmpCommandNotSupported,
                       qmp_connection.Execute,
-- 
1.9.1

Reply via email to