diff options
author | Richard Henderson <richard.henderson@linaro.org> | 2021-10-12 16:08:33 -0700 |
---|---|---|
committer | Richard Henderson <richard.henderson@linaro.org> | 2021-10-12 16:08:33 -0700 |
commit | ee26ce674a93c824713542cec3b6a9ca85459165 (patch) | |
tree | dfe2cd8dfc063e4e739a990b6754cbe20f73bf79 /python | |
parent | 8be1d4ed9838f807c63695753b30865f6edc4a5c (diff) | |
parent | c163c723ef92d0f629d015902396f2c67328b2e5 (diff) | |
download | qemu-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__.py | 4 | ||||
-rw-r--r-- | python/qemu/aqmp/events.py | 15 | ||||
-rw-r--r-- | python/qemu/aqmp/models.py | 13 | ||||
-rw-r--r-- | python/qemu/aqmp/protocol.py | 7 | ||||
-rw-r--r-- | python/qemu/aqmp/qmp_client.py | 27 | ||||
-rw-r--r-- | python/qemu/machine/machine.py | 48 | ||||
-rw-r--r-- | python/qemu/machine/qtest.py | 2 | ||||
-rw-r--r-- | python/qemu/qmp/__init__.py | 27 | ||||
-rw-r--r-- | python/qemu/qmp/qmp_shell.py | 1 |
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) |