On Wed, Dec 15, 2021 at 02:39:37PM -0500, John Snow wrote: > Thank you for your service! > > Signed-off-by: John Snow <js...@redhat.com> > --- > python/PACKAGE.rst | 4 +- > python/README.rst | 2 +- > python/qemu/qmp/README.rst | 9 - > python/qemu/qmp/__init__.py | 396 ------------------------------------ > python/qemu/qmp/py.typed | 0 > python/setup.cfg | 3 +- > 6 files changed, 4 insertions(+), 410 deletions(-) > delete mode 100644 python/qemu/qmp/README.rst > delete mode 100644 python/qemu/qmp/__init__.py > delete mode 100644 python/qemu/qmp/py.typed > > diff --git a/python/PACKAGE.rst b/python/PACKAGE.rst > index b0b86cc4c3..ddfa9ba3f5 100644 > --- a/python/PACKAGE.rst > +++ b/python/PACKAGE.rst > @@ -8,11 +8,11 @@ to change at any time. > Usage > ----- > > -The ``qemu.qmp`` subpackage provides a library for communicating with > +The ``qemu.aqmp`` subpackage provides a library for communicating with > QMP servers. The ``qemu.machine`` subpackage offers rudimentary > facilities for launching and managing QEMU processes. Refer to each > package's documentation > -(``>>> help(qemu.qmp)``, ``>>> help(qemu.machine)``) > +(``>>> help(qemu.aqmp)``, ``>>> help(qemu.machine)``) > for more information. > > Contributing > diff --git a/python/README.rst b/python/README.rst > index fcf74f69ea..eb5213337d 100644 > --- a/python/README.rst > +++ b/python/README.rst > @@ -3,7 +3,7 @@ QEMU Python Tooling > > This directory houses Python tooling used by the QEMU project to build, > configure, and test QEMU. It is organized by namespace (``qemu``), and > -then by package (e.g. ``qemu/machine``, ``qemu/qmp``, etc). > +then by package (e.g. ``qemu/machine``, ``qemu/aqmp``, etc). > > ``setup.py`` is used by ``pip`` to install this tooling to the current > environment. ``setup.cfg`` provides the packaging configuration used by > diff --git a/python/qemu/qmp/README.rst b/python/qemu/qmp/README.rst > deleted file mode 100644 > index 5bfb82535f..0000000000 > --- a/python/qemu/qmp/README.rst > +++ /dev/null > @@ -1,9 +0,0 @@ > -qemu.qmp package > -================ > - > -This package provides a library used for connecting to and communicating > -with QMP servers. It is used extensively by iotests, vm tests, > -avocado tests, and other utilities in the ./scripts directory. It is > -not a fully-fledged SDK and is subject to change at any time. > - > -See the documentation in ``__init__.py`` for more information. > diff --git a/python/qemu/qmp/__init__.py b/python/qemu/qmp/__init__.py > deleted file mode 100644 > index 4e08641154..0000000000 > --- a/python/qemu/qmp/__init__.py > +++ /dev/null > @@ -1,396 +0,0 @@ > -""" > -QEMU Monitor Protocol (QMP) development library & tooling. > - > -This package provides a fairly low-level class for communicating to QMP > -protocol servers, as implemented by QEMU, the QEMU Guest Agent, and the > -QEMU Storage Daemon. This library is not intended for production use. > - > -`QEMUMonitorProtocol` is the primary class of interest, and all errors > -raised derive from `QMPError`. > -""" > - > -# Copyright (C) 2009, 2010 Red Hat Inc. > -# > -# Authors: > -# Luiz Capitulino <lcapitul...@redhat.com> > -# > -# This work is licensed under the terms of the GNU GPL, version 2. See > -# the COPYING file in the top-level directory. > - > -import errno > -import json > -import logging > -import socket > -import struct > -from types import TracebackType > -from typing import ( > - Any, > - Dict, > - List, > - Optional, > - TextIO, > - Tuple, > - Type, > - TypeVar, > - Union, > - cast, > -) > - > - > -#: QMPMessage is an entire QMP message of any kind. > -QMPMessage = Dict[str, Any] > - > -#: QMPReturnValue is the 'return' value of a command. > -QMPReturnValue = object > - > -#: QMPObject is any object in a QMP message. > -QMPObject = Dict[str, object] > - > -# QMPMessage can be outgoing commands or incoming events/returns. > -# QMPReturnValue is usually a dict/json object, but due to QAPI's > -# 'returns-whitelist', it can actually be anything. > -# > -# {'return': {}} is a QMPMessage, > -# {} is the QMPReturnValue. > - > - > -InternetAddrT = Tuple[str, int] > -UnixAddrT = str > -SocketAddrT = Union[InternetAddrT, UnixAddrT] > - > - > -class QMPError(Exception): > - """ > - QMP base exception > - """ > - > - > -class QMPConnectError(QMPError): > - """ > - QMP connection exception > - """ > - > - > -class QMPCapabilitiesError(QMPError): > - """ > - QMP negotiate capabilities exception > - """ > - > - > -class QMPTimeoutError(QMPError): > - """ > - QMP timeout exception > - """ > - > - > -class QMPProtocolError(QMPError): > - """ > - QMP protocol error; unexpected response > - """ > - > - > -class QMPResponseError(QMPError): > - """ > - Represents erroneous QMP monitor reply > - """ > - def __init__(self, reply: QMPMessage): > - try: > - desc = reply['error']['desc'] > - except KeyError: > - desc = reply > - super().__init__(desc) > - self.reply = reply > - > - > -class QEMUMonitorProtocol: > - """ > - Provide an API to connect to QEMU via QEMU Monitor Protocol (QMP) and > then > - allow to handle commands and events. > - """ > - > - #: Logger object for debugging messages > - logger = logging.getLogger('QMP') > - > - def __init__(self, address: SocketAddrT, > - server: bool = False, > - nickname: Optional[str] = None): > - """ > - Create a QEMUMonitorProtocol class. > - > - @param address: QEMU address, can be either a unix socket path > (string) > - or a tuple in the form ( address, port ) for a TCP > - connection > - @param server: server mode listens on the socket (bool) > - @raise OSError on socket connection errors > - @note No connection is established, this is done by the connect() or > - accept() methods > - """ > - self.__events: List[QMPMessage] = [] > - self.__address = address > - self.__sock = self.__get_sock() > - self.__sockfile: Optional[TextIO] = None > - self._nickname = nickname > - if self._nickname: > - self.logger = logging.getLogger('QMP').getChild(self._nickname) > - if server: > - self.__sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) > - self.__sock.bind(self.__address) > - self.__sock.listen(1) > - > - def __get_sock(self) -> socket.socket: > - if isinstance(self.__address, tuple): > - family = socket.AF_INET > - else: > - family = socket.AF_UNIX > - return socket.socket(family, socket.SOCK_STREAM) > - > - def __negotiate_capabilities(self) -> QMPMessage: > - greeting = self.__json_read() > - if greeting is None or "QMP" not in greeting: > - raise QMPConnectError > - # Greeting seems ok, negotiate capabilities > - resp = self.cmd('qmp_capabilities') > - if resp and "return" in resp: > - return greeting > - raise QMPCapabilitiesError > - > - def __json_read(self, only_event: bool = False) -> Optional[QMPMessage]: > - assert self.__sockfile is not None > - while True: > - data = self.__sockfile.readline() > - if not data: > - return None > - # By definition, any JSON received from QMP is a QMPMessage, > - # and we are asserting only at static analysis time that it > - # has a particular shape. > - resp: QMPMessage = json.loads(data) > - if 'event' in resp: > - self.logger.debug("<<< %s", resp) > - self.__events.append(resp) > - if not only_event: > - continue > - return resp > - > - def __get_events(self, wait: Union[bool, float] = False) -> None: > - """ > - Check for new events in the stream and cache them in __events. > - > - @param wait (bool): block until an event is available. > - @param wait (float): If wait is a float, treat it as a timeout value. > - > - @raise QMPTimeoutError: If a timeout float is provided and the > timeout > - period elapses. > - @raise QMPConnectError: If wait is True but no events could be > - retrieved or if some other error occurred. > - """ > - > - # Current timeout and blocking status > - current_timeout = self.__sock.gettimeout() > - > - # Check for new events regardless and pull them into the cache: > - self.__sock.settimeout(0) # i.e. setblocking(False) > - try: > - self.__json_read() > - except OSError as err: > - # EAGAIN: No data available; not critical > - if err.errno != errno.EAGAIN: > - raise > - finally: > - self.__sock.settimeout(current_timeout) > - > - # Wait for new events, if needed. > - # if wait is 0.0, this means "no wait" and is also implicitly false. > - if not self.__events and wait: > - if isinstance(wait, float): > - self.__sock.settimeout(wait) > - try: > - ret = self.__json_read(only_event=True) > - except socket.timeout as err: > - raise QMPTimeoutError("Timeout waiting for event") from err > - except Exception as err: > - msg = "Error while reading from socket" > - raise QMPConnectError(msg) from err > - finally: > - self.__sock.settimeout(current_timeout) > - > - if ret is None: > - raise QMPConnectError("Error while reading from socket") > - > - T = TypeVar('T') > - > - def __enter__(self: T) -> T: > - # Implement context manager enter function. > - return self > - > - def __exit__(self, > - # pylint: disable=duplicate-code > - # see https://github.com/PyCQA/pylint/issues/3619 > - exc_type: Optional[Type[BaseException]], > - exc_val: Optional[BaseException], > - exc_tb: Optional[TracebackType]) -> None: > - # Implement context manager exit function. > - self.close() > - > - def connect(self, negotiate: bool = True) -> Optional[QMPMessage]: > - """ > - Connect to the QMP Monitor and perform capabilities negotiation. > - > - @return QMP greeting dict, or None if negotiate is false > - @raise OSError on socket connection errors > - @raise QMPConnectError if the greeting is not received > - @raise QMPCapabilitiesError if fails to negotiate capabilities > - """ > - self.__sock.connect(self.__address) > - self.__sockfile = self.__sock.makefile(mode='r') > - if negotiate: > - return self.__negotiate_capabilities() > - return None > - > - def accept(self, timeout: Optional[float] = 15.0) -> QMPMessage: > - """ > - Await connection from QMP Monitor and perform capabilities > negotiation. > - > - @param timeout: timeout in seconds (nonnegative float number, or > - None). The value passed will set the behavior of the > - underneath QMP socket as described in [1]. > - Default value is set to 15.0. > - > - @return QMP greeting dict > - @raise OSError on socket connection errors > - @raise QMPConnectError if the greeting is not received > - @raise QMPCapabilitiesError if fails to negotiate capabilities > - > - [1] > - > https://docs.python.org/3/library/socket.html#socket.socket.settimeout > - """ > - self.__sock.settimeout(timeout) > - self.__sock, _ = self.__sock.accept() > - self.__sockfile = self.__sock.makefile(mode='r') > - return self.__negotiate_capabilities() > - > - def cmd_obj(self, qmp_cmd: QMPMessage) -> QMPMessage: > - """ > - Send a QMP command to the QMP Monitor. > - > - @param qmp_cmd: QMP command to be sent as a Python dict > - @return QMP response as a Python dict > - """ > - self.logger.debug(">>> %s", qmp_cmd) > - self.__sock.sendall(json.dumps(qmp_cmd).encode('utf-8')) > - resp = self.__json_read() > - if resp is None: > - raise QMPConnectError("Unexpected empty reply from server") > - self.logger.debug("<<< %s", resp) > - return resp > - > - def cmd(self, name: str, > - args: Optional[Dict[str, object]] = None, > - cmd_id: Optional[object] = None) -> QMPMessage: > - """ > - Build a QMP command and send it to the QMP Monitor. > - > - @param name: command name (string) > - @param args: command arguments (dict) > - @param cmd_id: command id (dict, list, string or int) > - """ > - qmp_cmd: QMPMessage = {'execute': name} > - if args: > - qmp_cmd['arguments'] = args > - if cmd_id: > - qmp_cmd['id'] = cmd_id > - return self.cmd_obj(qmp_cmd) > - > - def command(self, cmd: str, **kwds: object) -> QMPReturnValue: > - """ > - Build and send a QMP command to the monitor, report errors if any > - """ > - ret = self.cmd(cmd, kwds) > - if 'error' in ret: > - raise QMPResponseError(ret) > - if 'return' not in ret: > - raise QMPProtocolError( > - "'return' key not found in QMP response > '{}'".format(str(ret)) > - ) > - return cast(QMPReturnValue, ret['return']) > - > - def pull_event(self, > - wait: Union[bool, float] = False) -> Optional[QMPMessage]: > - """ > - Pulls a single event. > - > - @param wait (bool): block until an event is available. > - @param wait (float): If wait is a float, treat it as a timeout value. > - > - @raise QMPTimeoutError: If a timeout float is provided and the > timeout > - period elapses. > - @raise QMPConnectError: If wait is True but no events could be > - retrieved or if some other error occurred. > - > - @return The first available QMP event, or None. > - """ > - self.__get_events(wait) > - > - if self.__events: > - return self.__events.pop(0) > - return None > - > - def get_events(self, wait: bool = False) -> List[QMPMessage]: > - """ > - 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. > - > - @raise QMPTimeoutError: If a timeout float is provided and the > timeout > - period elapses. > - @raise QMPConnectError: If wait is True but no events could be > - retrieved or if some other error occurred. > - > - @return The list of available QMP events. > - """ > - self.__get_events(wait) > - events = self.__events > - self.__events = [] > - return events > - > - def clear_events(self) -> None: > - """ > - Clear current list of pending events. > - """ > - self.__events = [] > - > - def close(self) -> None: > - """ > - Close the socket and socket file. > - """ > - if self.__sock: > - self.__sock.close() > - if self.__sockfile: > - self.__sockfile.close() > - > - def settimeout(self, timeout: Optional[float]) -> None: > - """ > - Set the socket timeout. > - > - @param timeout (float): timeout in seconds (non-zero), or None. > - @note This is a wrap around socket.settimeout > - > - @raise ValueError: if timeout was set to 0. > - """ > - if timeout == 0: > - msg = "timeout cannot be 0; this engages non-blocking mode." > - msg += " Use 'None' instead to disable timeouts." > - raise ValueError(msg) > - self.__sock.settimeout(timeout) > - > - def send_fd_scm(self, fd: int) -> None: > - """ > - Send a file descriptor to the remote via SCM_RIGHTS. > - """ > - if self.__sock.family != socket.AF_UNIX: > - raise RuntimeError("Can't use SCM_RIGHTS on non-AF_UNIX socket.") > - > - self.__sock.sendmsg( > - [b' '], > - [(socket.SOL_SOCKET, socket.SCM_RIGHTS, struct.pack('@i', fd))] > - ) > diff --git a/python/qemu/qmp/py.typed b/python/qemu/qmp/py.typed > deleted file mode 100644 > index e69de29bb2..0000000000 > diff --git a/python/setup.cfg b/python/setup.cfg > index 510df23698..5140a5b322 100644 > --- a/python/setup.cfg > +++ b/python/setup.cfg > @@ -24,10 +24,9 @@ classifiers = > [options] > python_requires = >= 3.6 > packages = > - qemu.qmp > + qemu.aqmp > qemu.machine > qemu.utils > - qemu.aqmp > > [options.package_data] > * = py.typed
Reviewed-by: Beraldo Leal <bl...@redhat.com> -- Beraldo