xref: /qemu/python/qemu/qmp/legacy.py (revision 684750ab)
137094b6dSJohn Snow"""
237094b6dSJohn Snow(Legacy) Sync QMP Wrapper
337094b6dSJohn Snow
437094b6dSJohn SnowThis module provides the `QEMUMonitorProtocol` class, which is a
537094b6dSJohn Snowsynchronous wrapper around `QMPClient`.
637094b6dSJohn Snow
737094b6dSJohn SnowIts design closely resembles that of the original QEMUMonitorProtocol
837094b6dSJohn Snowclass, originally written by Luiz Capitulino. It is provided here for
937094b6dSJohn Snowcompatibility with scripts inside the QEMU source tree that expect the
1037094b6dSJohn Snowold interface.
1137094b6dSJohn Snow"""
1237094b6dSJohn Snow
1337094b6dSJohn Snow#
1437094b6dSJohn Snow# Copyright (C) 2009-2022 Red Hat Inc.
1537094b6dSJohn Snow#
1637094b6dSJohn Snow# Authors:
1737094b6dSJohn Snow#  Luiz Capitulino <lcapitulino@redhat.com>
1837094b6dSJohn Snow#  John Snow <jsnow@redhat.com>
1937094b6dSJohn Snow#
2037094b6dSJohn Snow# This work is licensed under the terms of the GNU GPL, version 2.  See
2137094b6dSJohn Snow# the COPYING file in the top-level directory.
2237094b6dSJohn Snow#
2337094b6dSJohn Snow
2437094b6dSJohn Snowimport asyncio
25603a3badSMarc-André Lureauimport socket
2637094b6dSJohn Snowfrom types import TracebackType
2737094b6dSJohn Snowfrom typing import (
2837094b6dSJohn Snow    Any,
2937094b6dSJohn Snow    Awaitable,
3037094b6dSJohn Snow    Dict,
3137094b6dSJohn Snow    List,
3237094b6dSJohn Snow    Optional,
3337094b6dSJohn Snow    Type,
3437094b6dSJohn Snow    TypeVar,
3537094b6dSJohn Snow    Union,
3637094b6dSJohn Snow)
3737094b6dSJohn Snow
3837094b6dSJohn Snowfrom .error import QMPError
3937094b6dSJohn Snowfrom .protocol import Runstate, SocketAddrT
4037094b6dSJohn Snowfrom .qmp_client import QMPClient
4137094b6dSJohn Snow
4237094b6dSJohn Snow
4337094b6dSJohn Snow#: QMPMessage is an entire QMP message of any kind.
4437094b6dSJohn SnowQMPMessage = Dict[str, Any]
4537094b6dSJohn Snow
4637094b6dSJohn Snow#: QMPReturnValue is the 'return' value of a command.
4737094b6dSJohn SnowQMPReturnValue = object
4837094b6dSJohn Snow
4937094b6dSJohn Snow#: QMPObject is any object in a QMP message.
5037094b6dSJohn SnowQMPObject = Dict[str, object]
5137094b6dSJohn Snow
5237094b6dSJohn Snow# QMPMessage can be outgoing commands or incoming events/returns.
5337094b6dSJohn Snow# QMPReturnValue is usually a dict/json object, but due to QAPI's
549b0ecfabSThomas Huth# 'command-returns-exceptions', it can actually be anything.
5537094b6dSJohn Snow#
5637094b6dSJohn Snow# {'return': {}} is a QMPMessage,
5737094b6dSJohn Snow# {} is the QMPReturnValue.
5837094b6dSJohn Snow
5937094b6dSJohn Snow
6037094b6dSJohn Snowclass QMPBadPortError(QMPError):
6137094b6dSJohn Snow    """
6237094b6dSJohn Snow    Unable to parse socket address: Port was non-numerical.
6337094b6dSJohn Snow    """
6437094b6dSJohn Snow
6537094b6dSJohn Snow
6637094b6dSJohn Snowclass QEMUMonitorProtocol:
6737094b6dSJohn Snow    """
6837094b6dSJohn Snow    Provide an API to connect to QEMU via QEMU Monitor Protocol (QMP)
6937094b6dSJohn Snow    and then allow to handle commands and events.
7037094b6dSJohn Snow
715bbc5936SJohn Snow    :param address:  QEMU address, can be a unix socket path (string), a tuple
725bbc5936SJohn Snow                     in the form ( address, port ) for a TCP connection, or an
735bbc5936SJohn Snow                     existing `socket.socket` object.
7437094b6dSJohn Snow    :param server:   Act as the socket server. (See 'accept')
755bbc5936SJohn Snow                     Not applicable when passing a socket directly.
7637094b6dSJohn Snow    :param nickname: Optional nickname used for logging.
7737094b6dSJohn Snow    """
7837094b6dSJohn Snow
79603a3badSMarc-André Lureau    def __init__(self,
805bbc5936SJohn Snow                 address: Union[SocketAddrT, socket.socket],
8137094b6dSJohn Snow                 server: bool = False,
8237094b6dSJohn Snow                 nickname: Optional[str] = None):
8337094b6dSJohn Snow
845bbc5936SJohn Snow        if server and isinstance(address, socket.socket):
855bbc5936SJohn Snow            raise ValueError(
865bbc5936SJohn Snow                "server argument should be False when passing a socket")
875bbc5936SJohn Snow
8837094b6dSJohn Snow        self._qmp = QMPClient(nickname)
8937094b6dSJohn Snow        self._aloop = asyncio.get_event_loop()
9037094b6dSJohn Snow        self._address = address
9137094b6dSJohn Snow        self._timeout: Optional[float] = None
9237094b6dSJohn Snow
9337094b6dSJohn Snow        if server:
945bbc5936SJohn Snow            assert not isinstance(self._address, socket.socket)
9537094b6dSJohn Snow            self._sync(self._qmp.start_server(self._address))
9637094b6dSJohn Snow
9737094b6dSJohn Snow    _T = TypeVar('_T')
9837094b6dSJohn Snow
9937094b6dSJohn Snow    def _sync(
10037094b6dSJohn Snow            self, future: Awaitable[_T], timeout: Optional[float] = None
10137094b6dSJohn Snow    ) -> _T:
10237094b6dSJohn Snow        return self._aloop.run_until_complete(
10337094b6dSJohn Snow            asyncio.wait_for(future, timeout=timeout)
10437094b6dSJohn Snow        )
10537094b6dSJohn Snow
10637094b6dSJohn Snow    def _get_greeting(self) -> Optional[QMPMessage]:
10737094b6dSJohn Snow        if self._qmp.greeting is not None:
10837094b6dSJohn Snow            # pylint: disable=protected-access
10937094b6dSJohn Snow            return self._qmp.greeting._asdict()
11037094b6dSJohn Snow        return None
11137094b6dSJohn Snow
11237094b6dSJohn Snow    def __enter__(self: _T) -> _T:
11337094b6dSJohn Snow        # Implement context manager enter function.
11437094b6dSJohn Snow        return self
11537094b6dSJohn Snow
11637094b6dSJohn Snow    def __exit__(self,
11737094b6dSJohn Snow                 exc_type: Optional[Type[BaseException]],
11837094b6dSJohn Snow                 exc_val: Optional[BaseException],
11937094b6dSJohn Snow                 exc_tb: Optional[TracebackType]) -> None:
12037094b6dSJohn Snow        # Implement context manager exit function.
12137094b6dSJohn Snow        self.close()
12237094b6dSJohn Snow
12337094b6dSJohn Snow    @classmethod
12437094b6dSJohn Snow    def parse_address(cls, address: str) -> SocketAddrT:
12537094b6dSJohn Snow        """
12637094b6dSJohn Snow        Parse a string into a QMP address.
12737094b6dSJohn Snow
12837094b6dSJohn Snow        Figure out if the argument is in the port:host form.
12937094b6dSJohn Snow        If it's not, it's probably a file path.
13037094b6dSJohn Snow        """
13137094b6dSJohn Snow        components = address.split(':')
13237094b6dSJohn Snow        if len(components) == 2:
13337094b6dSJohn Snow            try:
13437094b6dSJohn Snow                port = int(components[1])
13537094b6dSJohn Snow            except ValueError:
13637094b6dSJohn Snow                msg = f"Bad port: '{components[1]}' in '{address}'."
13737094b6dSJohn Snow                raise QMPBadPortError(msg) from None
13837094b6dSJohn Snow            return (components[0], port)
13937094b6dSJohn Snow
14037094b6dSJohn Snow        # Treat as filepath.
14137094b6dSJohn Snow        return address
14237094b6dSJohn Snow
14337094b6dSJohn Snow    def connect(self, negotiate: bool = True) -> Optional[QMPMessage]:
14437094b6dSJohn Snow        """
14537094b6dSJohn Snow        Connect to the QMP Monitor and perform capabilities negotiation.
14637094b6dSJohn Snow
14737094b6dSJohn Snow        :return: QMP greeting dict, or None if negotiate is false
14837094b6dSJohn Snow        :raise ConnectError: on connection errors
14937094b6dSJohn Snow        """
15037094b6dSJohn Snow        self._qmp.await_greeting = negotiate
15137094b6dSJohn Snow        self._qmp.negotiate = negotiate
15237094b6dSJohn Snow
15337094b6dSJohn Snow        self._sync(
1545bbc5936SJohn Snow            self._qmp.connect(self._address)
15537094b6dSJohn Snow        )
15637094b6dSJohn Snow        return self._get_greeting()
15737094b6dSJohn Snow
15837094b6dSJohn Snow    def accept(self, timeout: Optional[float] = 15.0) -> QMPMessage:
15937094b6dSJohn Snow        """
16037094b6dSJohn Snow        Await connection from QMP Monitor and perform capabilities negotiation.
16137094b6dSJohn Snow
16237094b6dSJohn Snow        :param timeout:
16337094b6dSJohn Snow            timeout in seconds (nonnegative float number, or None).
16437094b6dSJohn Snow            If None, there is no timeout, and this may block forever.
16537094b6dSJohn Snow
16637094b6dSJohn Snow        :return: QMP greeting dict
16737094b6dSJohn Snow        :raise ConnectError: on connection errors
16837094b6dSJohn Snow        """
16937094b6dSJohn Snow        self._qmp.await_greeting = True
17037094b6dSJohn Snow        self._qmp.negotiate = True
17137094b6dSJohn Snow
17237094b6dSJohn Snow        self._sync(self._qmp.accept(), timeout)
17337094b6dSJohn Snow
17437094b6dSJohn Snow        ret = self._get_greeting()
17537094b6dSJohn Snow        assert ret is not None
17637094b6dSJohn Snow        return ret
17737094b6dSJohn Snow
17837094b6dSJohn Snow    def cmd_obj(self, qmp_cmd: QMPMessage) -> QMPMessage:
17937094b6dSJohn Snow        """
18037094b6dSJohn Snow        Send a QMP command to the QMP Monitor.
18137094b6dSJohn Snow
18237094b6dSJohn Snow        :param qmp_cmd: QMP command to be sent as a Python dict
18337094b6dSJohn Snow        :return: QMP response as a Python dict
18437094b6dSJohn Snow        """
18537094b6dSJohn Snow        return dict(
18637094b6dSJohn Snow            self._sync(
18737094b6dSJohn Snow                # pylint: disable=protected-access
18837094b6dSJohn Snow
18937094b6dSJohn Snow                # _raw() isn't a public API, because turning off
19037094b6dSJohn Snow                # automatic ID assignment is discouraged. For
19137094b6dSJohn Snow                # compatibility with iotests *only*, do it anyway.
19237094b6dSJohn Snow                self._qmp._raw(qmp_cmd, assign_id=False),
19337094b6dSJohn Snow                self._timeout
19437094b6dSJohn Snow            )
19537094b6dSJohn Snow        )
19637094b6dSJohn Snow
19737274707SVladimir Sementsov-Ogievskiy    def cmd_raw(self, name: str,
198f187cfefSVladimir Sementsov-Ogievskiy                args: Optional[Dict[str, object]] = None) -> QMPMessage:
19937094b6dSJohn Snow        """
20037094b6dSJohn Snow        Build a QMP command and send it to the QMP Monitor.
20137094b6dSJohn Snow
20237094b6dSJohn Snow        :param name: command name (string)
20337094b6dSJohn Snow        :param args: command arguments (dict)
20437094b6dSJohn Snow        """
20537094b6dSJohn Snow        qmp_cmd: QMPMessage = {'execute': name}
20637094b6dSJohn Snow        if args:
20737094b6dSJohn Snow            qmp_cmd['arguments'] = args
20837094b6dSJohn Snow        return self.cmd_obj(qmp_cmd)
20937094b6dSJohn Snow
210684750abSVladimir Sementsov-Ogievskiy    def cmd(self, cmd: str, **kwds: object) -> QMPReturnValue:
21137094b6dSJohn Snow        """
21237094b6dSJohn Snow        Build and send a QMP command to the monitor, report errors if any
21337094b6dSJohn Snow        """
21437094b6dSJohn Snow        return self._sync(
21537094b6dSJohn Snow            self._qmp.execute(cmd, kwds),
21637094b6dSJohn Snow            self._timeout
21737094b6dSJohn Snow        )
21837094b6dSJohn Snow
21937094b6dSJohn Snow    def pull_event(self,
22037094b6dSJohn Snow                   wait: Union[bool, float] = False) -> Optional[QMPMessage]:
22137094b6dSJohn Snow        """
22237094b6dSJohn Snow        Pulls a single event.
22337094b6dSJohn Snow
22437094b6dSJohn Snow        :param wait:
22537094b6dSJohn Snow            If False or 0, do not wait. Return None if no events ready.
22637094b6dSJohn Snow            If True, wait forever until the next event.
22737094b6dSJohn Snow            Otherwise, wait for the specified number of seconds.
22837094b6dSJohn Snow
22937094b6dSJohn Snow        :raise asyncio.TimeoutError:
23037094b6dSJohn Snow            When a timeout is requested and the timeout period elapses.
23137094b6dSJohn Snow
23237094b6dSJohn Snow        :return: The first available QMP event, or None.
23337094b6dSJohn Snow        """
23437094b6dSJohn Snow        if not wait:
23537094b6dSJohn Snow            # wait is False/0: "do not wait, do not except."
23637094b6dSJohn Snow            if self._qmp.events.empty():
23737094b6dSJohn Snow                return None
23837094b6dSJohn Snow
23937094b6dSJohn Snow        # If wait is 'True', wait forever. If wait is False/0, the events
24037094b6dSJohn Snow        # queue must not be empty; but it still needs some real amount
24137094b6dSJohn Snow        # of time to complete.
24237094b6dSJohn Snow        timeout = None
24337094b6dSJohn Snow        if wait and isinstance(wait, float):
24437094b6dSJohn Snow            timeout = wait
24537094b6dSJohn Snow
24637094b6dSJohn Snow        return dict(
24737094b6dSJohn Snow            self._sync(
24837094b6dSJohn Snow                self._qmp.events.get(),
24937094b6dSJohn Snow                timeout
25037094b6dSJohn Snow            )
25137094b6dSJohn Snow        )
25237094b6dSJohn Snow
25337094b6dSJohn Snow    def get_events(self, wait: Union[bool, float] = False) -> List[QMPMessage]:
25437094b6dSJohn Snow        """
25537094b6dSJohn Snow        Get a list of QMP events and clear all pending events.
25637094b6dSJohn Snow
25737094b6dSJohn Snow        :param wait:
25837094b6dSJohn Snow            If False or 0, do not wait. Return None if no events ready.
25937094b6dSJohn Snow            If True, wait until we have at least one event.
26037094b6dSJohn Snow            Otherwise, wait for up to the specified number of seconds for at
26137094b6dSJohn Snow            least one event.
26237094b6dSJohn Snow
26337094b6dSJohn Snow        :raise asyncio.TimeoutError:
26437094b6dSJohn Snow            When a timeout is requested and the timeout period elapses.
26537094b6dSJohn Snow
26637094b6dSJohn Snow        :return: A list of QMP events.
26737094b6dSJohn Snow        """
26837094b6dSJohn Snow        events = [dict(x) for x in self._qmp.events.clear()]
26937094b6dSJohn Snow        if events:
27037094b6dSJohn Snow            return events
27137094b6dSJohn Snow
27237094b6dSJohn Snow        event = self.pull_event(wait)
27337094b6dSJohn Snow        return [event] if event is not None else []
27437094b6dSJohn Snow
27537094b6dSJohn Snow    def clear_events(self) -> None:
27637094b6dSJohn Snow        """Clear current list of pending events."""
27737094b6dSJohn Snow        self._qmp.events.clear()
27837094b6dSJohn Snow
27937094b6dSJohn Snow    def close(self) -> None:
28037094b6dSJohn Snow        """Close the connection."""
28137094b6dSJohn Snow        self._sync(
28237094b6dSJohn Snow            self._qmp.disconnect()
28337094b6dSJohn Snow        )
28437094b6dSJohn Snow
28537094b6dSJohn Snow    def settimeout(self, timeout: Optional[float]) -> None:
28637094b6dSJohn Snow        """
28737094b6dSJohn Snow        Set the timeout for QMP RPC execution.
28837094b6dSJohn Snow
28937094b6dSJohn Snow        This timeout affects the `cmd`, `cmd_obj`, and `command` methods.
29037094b6dSJohn Snow        The `accept`, `pull_event` and `get_event` methods have their
29137094b6dSJohn Snow        own configurable timeouts.
29237094b6dSJohn Snow
29337094b6dSJohn Snow        :param timeout:
29437094b6dSJohn Snow            timeout in seconds, or None.
29537094b6dSJohn Snow            None will wait indefinitely.
29637094b6dSJohn Snow        """
29737094b6dSJohn Snow        self._timeout = timeout
29837094b6dSJohn Snow
29937094b6dSJohn Snow    def send_fd_scm(self, fd: int) -> None:
30037094b6dSJohn Snow        """
30137094b6dSJohn Snow        Send a file descriptor to the remote via SCM_RIGHTS.
30237094b6dSJohn Snow        """
30337094b6dSJohn Snow        self._qmp.send_fd_scm(fd)
30437094b6dSJohn Snow
30537094b6dSJohn Snow    def __del__(self) -> None:
30637094b6dSJohn Snow        if self._qmp.runstate == Runstate.IDLE:
30737094b6dSJohn Snow            return
30837094b6dSJohn Snow
30937094b6dSJohn Snow        if not self._aloop.is_running():
31037094b6dSJohn Snow            self.close()
31137094b6dSJohn Snow        else:
31237094b6dSJohn Snow            # Garbage collection ran while the event loop was running.
31337094b6dSJohn Snow            # Nothing we can do about it now, but if we don't raise our
31437094b6dSJohn Snow            # own error, the user will be treated to a lot of traceback
31537094b6dSJohn Snow            # they might not understand.
31637094b6dSJohn Snow            raise QMPError(
31737094b6dSJohn Snow                "QEMUMonitorProtocol.close()"
31837094b6dSJohn Snow                " was not called before object was garbage collected"
31937094b6dSJohn Snow            )
320