aboutsummaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorRichard Henderson <richard.henderson@linaro.org>2021-10-12 16:08:33 -0700
committerRichard Henderson <richard.henderson@linaro.org>2021-10-12 16:08:33 -0700
commitee26ce674a93c824713542cec3b6a9ca85459165 (patch)
treedfe2cd8dfc063e4e739a990b6754cbe20f73bf79 /python
parent8be1d4ed9838f807c63695753b30865f6edc4a5c (diff)
parentc163c723ef92d0f629d015902396f2c67328b2e5 (diff)
downloadqemu-ee26ce674a93c824713542cec3b6a9ca85459165.zip
qemu-ee26ce674a93c824713542cec3b6a9ca85459165.tar.gz
qemu-ee26ce674a93c824713542cec3b6a9ca85459165.tar.bz2
Merge remote-tracking branch 'remotes/jsnow/tags/python-pull-request' into staging
Pull request # gpg: Signature made Tue 12 Oct 2021 02:36:07 PM PDT # gpg: using RSA key F9B7ABDBBCACDF95BE76CBD07DEF8106AAFC390E # gpg: Good signature from "John Snow (John Huston) <jsnow@redhat.com>" [full] * remotes/jsnow/tags/python-pull-request: python, iotests: remove socket_scm_helper python/qmp: add send_fd_scm directly to QEMUMonitorProtocol python/qmp: clear events on get_events() call python/aqmp: Disable logging messages by default python/aqmp: Reduce severity of EOFError-caused loop terminations python/aqmp: Add dict conversion method to Greeting object python/aqmp: add send_fd_scm python/aqmp: Return cleared events from EventListener.clear() python/aqmp: add .empty() method to EventListener python/aqmp: add greeting property to QMPClient Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
Diffstat (limited to 'python')
-rw-r--r--python/qemu/aqmp/__init__.py4
-rw-r--r--python/qemu/aqmp/events.py15
-rw-r--r--python/qemu/aqmp/models.py13
-rw-r--r--python/qemu/aqmp/protocol.py7
-rw-r--r--python/qemu/aqmp/qmp_client.py27
-rw-r--r--python/qemu/machine/machine.py48
-rw-r--r--python/qemu/machine/qtest.py2
-rw-r--r--python/qemu/qmp/__init__.py27
-rw-r--r--python/qemu/qmp/qmp_shell.py1
9 files changed, 84 insertions, 60 deletions
diff --git a/python/qemu/aqmp/__init__.py b/python/qemu/aqmp/__init__.py
index ab17829..d1b0e4d 100644
--- a/python/qemu/aqmp/__init__.py
+++ b/python/qemu/aqmp/__init__.py
@@ -21,6 +21,7 @@ managing QMP events.
# This work is licensed under the terms of the GNU GPL, version 2. See
# the COPYING file in the top-level directory.
+import logging
import warnings
from .error import AQMPError
@@ -41,6 +42,9 @@ Proceed with caution!
warnings.warn(_WMSG, FutureWarning)
+# Suppress logging unless an application engages it.
+logging.getLogger('qemu.aqmp').addHandler(logging.NullHandler())
+
# The order of these fields impact the Sphinx documentation order.
__all__ = (
diff --git a/python/qemu/aqmp/events.py b/python/qemu/aqmp/events.py
index fb81d21..5f7150c 100644
--- a/python/qemu/aqmp/events.py
+++ b/python/qemu/aqmp/events.py
@@ -556,7 +556,13 @@ class EventListener:
"""
return await self._queue.get()
- def clear(self) -> None:
+ def empty(self) -> bool:
+ """
+ Return `True` if there are no pending events.
+ """
+ return self._queue.empty()
+
+ def clear(self) -> List[Message]:
"""
Clear this listener of all pending events.
@@ -564,17 +570,22 @@ class EventListener:
pending FIFO queue synchronously. It can be also be used to
manually clear any pending events, if desired.
+ :return: The cleared events, if any.
+
.. warning::
Take care when discarding events. Cleared events will be
silently tossed on the floor. All events that were ever
accepted by this listener are visible in `history()`.
"""
+ events = []
while True:
try:
- self._queue.get_nowait()
+ events.append(self._queue.get_nowait())
except asyncio.QueueEmpty:
break
+ return events
+
def __aiter__(self) -> AsyncIterator[Message]:
return self
diff --git a/python/qemu/aqmp/models.py b/python/qemu/aqmp/models.py
index 24c9412..de87f87 100644
--- a/python/qemu/aqmp/models.py
+++ b/python/qemu/aqmp/models.py
@@ -8,8 +8,10 @@ data to make sure it conforms to spec.
# pylint: disable=too-few-public-methods
from collections import abc
+import copy
from typing import (
Any,
+ Dict,
Mapping,
Optional,
Sequence,
@@ -66,6 +68,17 @@ class Greeting(Model):
self._check_member('QMP', abc.Mapping, "JSON object")
self.QMP = QMPGreeting(self._raw['QMP'])
+ def _asdict(self) -> Dict[str, object]:
+ """
+ For compatibility with the iotests sync QMP wrapper.
+
+ The legacy QMP interface needs Greetings as a garden-variety Dict.
+
+ This interface is private in the hopes that it will be able to
+ be dropped again in the near-future. Caller beware!
+ """
+ return dict(copy.deepcopy(self._raw))
+
class QMPGreeting(Model):
"""
diff --git a/python/qemu/aqmp/protocol.py b/python/qemu/aqmp/protocol.py
index 32e7874..ae1df24 100644
--- a/python/qemu/aqmp/protocol.py
+++ b/python/qemu/aqmp/protocol.py
@@ -721,8 +721,11 @@ class AsyncProtocol(Generic[T]):
self.logger.debug("Task.%s: cancelled.", name)
return
except BaseException as err:
- self.logger.error("Task.%s: %s",
- name, exception_summary(err))
+ self.logger.log(
+ logging.INFO if isinstance(err, EOFError) else logging.ERROR,
+ "Task.%s: %s",
+ name, exception_summary(err)
+ )
self.logger.debug("Task.%s: failure:\n%s\n",
name, pretty_traceback())
self._schedule_disconnect()
diff --git a/python/qemu/aqmp/qmp_client.py b/python/qemu/aqmp/qmp_client.py
index 82e9dab..f987da0 100644
--- a/python/qemu/aqmp/qmp_client.py
+++ b/python/qemu/aqmp/qmp_client.py
@@ -9,6 +9,8 @@ accept an incoming connection from that server.
import asyncio
import logging
+import socket
+import struct
from typing import (
Dict,
List,
@@ -224,6 +226,11 @@ class QMPClient(AsyncProtocol[Message], Events):
'asyncio.Queue[QMPClient._PendingT]'
] = {}
+ @property
+ def greeting(self) -> Optional[Greeting]:
+ """The `Greeting` from the QMP server, if any."""
+ return self._greeting
+
@upper_half
async def _establish_session(self) -> None:
"""
@@ -619,3 +626,23 @@ class QMPClient(AsyncProtocol[Message], Events):
"""
msg = self.make_execute_msg(cmd, arguments, oob=oob)
return await self.execute_msg(msg)
+
+ @upper_half
+ @require(Runstate.RUNNING)
+ def send_fd_scm(self, fd: int) -> None:
+ """
+ Send a file descriptor to the remote via SCM_RIGHTS.
+ """
+ assert self._writer is not None
+ sock = self._writer.transport.get_extra_info('socket')
+
+ if sock.family != socket.AF_UNIX:
+ raise AQMPError("Sending file descriptors requires a UNIX socket.")
+
+ # Void the warranty sticker.
+ # Access to sendmsg in asyncio is scheduled for removal in Python 3.11.
+ sock = sock._sock # pylint: disable=protected-access
+ sock.sendmsg(
+ [b' '],
+ [(socket.SOL_SOCKET, socket.SCM_RIGHTS, struct.pack('@i', fd))]
+ )
diff --git a/python/qemu/machine/machine.py b/python/qemu/machine/machine.py
index 3413188..056d340 100644
--- a/python/qemu/machine/machine.py
+++ b/python/qemu/machine/machine.py
@@ -98,7 +98,6 @@ class QEMUMachine:
name: Optional[str] = None,
base_temp_dir: str = "/var/tmp",
monitor_address: Optional[SocketAddrT] = None,
- socket_scm_helper: Optional[str] = None,
sock_dir: Optional[str] = None,
drain_console: bool = False,
console_log: Optional[str] = None,
@@ -113,7 +112,6 @@ class QEMUMachine:
@param name: prefix for socket and log file names (default: qemu-PID)
@param base_temp_dir: default location where temp files are created
@param monitor_address: address for QMP monitor
- @param socket_scm_helper: helper program, required for send_fd_scm()
@param sock_dir: where to create socket (defaults to base_temp_dir)
@param drain_console: (optional) True to drain console socket to buffer
@param console_log: (optional) path to console log file
@@ -134,7 +132,6 @@ class QEMUMachine:
self._base_temp_dir = base_temp_dir
self._sock_dir = sock_dir or self._base_temp_dir
self._log_dir = log_dir
- self._socket_scm_helper = socket_scm_helper
if monitor_address is not None:
self._monitor_address = monitor_address
@@ -213,48 +210,22 @@ class QEMUMachine:
def send_fd_scm(self, fd: Optional[int] = None,
file_path: Optional[str] = None) -> int:
"""
- Send an fd or file_path to socket_scm_helper.
+ Send an fd or file_path to the remote via SCM_RIGHTS.
- Exactly one of fd and file_path must be given.
- If it is file_path, the helper will open that file and pass its own fd.
+ Exactly one of fd and file_path must be given. If it is
+ file_path, the file will be opened read-only and the new file
+ descriptor will be sent to the remote.
"""
- # In iotest.py, the qmp should always use unix socket.
- assert self._qmp.is_scm_available()
- if self._socket_scm_helper is None:
- raise QEMUMachineError("No path to socket_scm_helper set")
- if not os.path.exists(self._socket_scm_helper):
- raise QEMUMachineError("%s does not exist" %
- self._socket_scm_helper)
-
- # This did not exist before 3.4, but since then it is
- # mandatory for our purpose
- if hasattr(os, 'set_inheritable'):
- os.set_inheritable(self._qmp.get_sock_fd(), True)
- if fd is not None:
- os.set_inheritable(fd, True)
-
- fd_param = ["%s" % self._socket_scm_helper,
- "%d" % self._qmp.get_sock_fd()]
-
if file_path is not None:
assert fd is None
- fd_param.append(file_path)
+ with open(file_path, "rb") as passfile:
+ fd = passfile.fileno()
+ self._qmp.send_fd_scm(fd)
else:
assert fd is not None
- fd_param.append(str(fd))
-
- proc = subprocess.run(
- fd_param,
- stdin=subprocess.DEVNULL,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- check=False,
- close_fds=False,
- )
- if proc.stdout:
- LOG.debug(proc.stdout)
+ self._qmp.send_fd_scm(fd)
- return proc.returncode
+ return 0
@staticmethod
def _remove_if_exists(path: str) -> None:
@@ -631,7 +602,6 @@ class QEMUMachine:
events = self._qmp.get_events(wait=wait)
events.extend(self._events)
del self._events[:]
- self._qmp.clear_events()
return events
@staticmethod
diff --git a/python/qemu/machine/qtest.py b/python/qemu/machine/qtest.py
index 395cc8f..f2f9aaa 100644
--- a/python/qemu/machine/qtest.py
+++ b/python/qemu/machine/qtest.py
@@ -115,7 +115,6 @@ class QEMUQtestMachine(QEMUMachine):
wrapper: Sequence[str] = (),
name: Optional[str] = None,
base_temp_dir: str = "/var/tmp",
- socket_scm_helper: Optional[str] = None,
sock_dir: Optional[str] = None,
qmp_timer: Optional[float] = None):
# pylint: disable=too-many-arguments
@@ -126,7 +125,6 @@ class QEMUQtestMachine(QEMUMachine):
sock_dir = base_temp_dir
super().__init__(binary, args, wrapper=wrapper, name=name,
base_temp_dir=base_temp_dir,
- socket_scm_helper=socket_scm_helper,
sock_dir=sock_dir, qmp_timer=qmp_timer)
self._qtest: Optional[QEMUQtestProtocol] = None
self._qtest_path = os.path.join(sock_dir, name + "-qtest.sock")
diff --git a/python/qemu/qmp/__init__.py b/python/qemu/qmp/__init__.py
index 269516a..358c097 100644
--- a/python/qemu/qmp/__init__.py
+++ b/python/qemu/qmp/__init__.py
@@ -21,6 +21,7 @@ import errno
import json
import logging
import socket
+import struct
from types import TracebackType
from typing import (
Any,
@@ -361,7 +362,7 @@ class QEMUMonitorProtocol:
def get_events(self, wait: bool = False) -> List[QMPMessage]:
"""
- Get a list of available QMP events.
+ Get a list of available QMP events and clear all pending events.
@param wait (bool): block until an event is available.
@param wait (float): If wait is a float, treat it as a timeout value.
@@ -374,7 +375,9 @@ class QEMUMonitorProtocol:
@return The list of available QMP events.
"""
self.__get_events(wait)
- return self.__events
+ events = self.__events
+ self.__events = []
+ return events
def clear_events(self) -> None:
"""
@@ -406,18 +409,14 @@ class QEMUMonitorProtocol:
raise ValueError(msg)
self.__sock.settimeout(timeout)
- def get_sock_fd(self) -> int:
+ def send_fd_scm(self, fd: int) -> None:
"""
- Get the socket file descriptor.
-
- @return The file descriptor number.
+ Send a file descriptor to the remote via SCM_RIGHTS.
"""
- return self.__sock.fileno()
+ if self.__sock.family != socket.AF_UNIX:
+ raise RuntimeError("Can't use SCM_RIGHTS on non-AF_UNIX socket.")
- def is_scm_available(self) -> bool:
- """
- Check if the socket allows for SCM_RIGHTS.
-
- @return True if SCM_RIGHTS is available, otherwise False.
- """
- return self.__sock.family == socket.AF_UNIX
+ self.__sock.sendmsg(
+ [b' '],
+ [(socket.SOL_SOCKET, socket.SCM_RIGHTS, struct.pack('@i', fd))]
+ )
diff --git a/python/qemu/qmp/qmp_shell.py b/python/qemu/qmp/qmp_shell.py
index 337acfc..e7d7eb1 100644
--- a/python/qemu/qmp/qmp_shell.py
+++ b/python/qemu/qmp/qmp_shell.py
@@ -381,7 +381,6 @@ class QMPShell(qmp.QEMUMonitorProtocol):
if cmdline == '':
for event in self.get_events():
print(event)
- self.clear_events()
return True
return self._execute_cmd(cmdline)