xref: /qemu/python/qemu/qmp/legacy.py (revision 37094b6d)
1*37094b6dSJohn Snow"""
2*37094b6dSJohn Snow(Legacy) Sync QMP Wrapper
3*37094b6dSJohn Snow
4*37094b6dSJohn SnowThis module provides the `QEMUMonitorProtocol` class, which is a
5*37094b6dSJohn Snowsynchronous wrapper around `QMPClient`.
6*37094b6dSJohn Snow
7*37094b6dSJohn SnowIts design closely resembles that of the original QEMUMonitorProtocol
8*37094b6dSJohn Snowclass, originally written by Luiz Capitulino. It is provided here for
9*37094b6dSJohn Snowcompatibility with scripts inside the QEMU source tree that expect the
10*37094b6dSJohn Snowold interface.
11*37094b6dSJohn Snow"""
12*37094b6dSJohn Snow
13*37094b6dSJohn Snow#
14*37094b6dSJohn Snow# Copyright (C) 2009-2022 Red Hat Inc.
15*37094b6dSJohn Snow#
16*37094b6dSJohn Snow# Authors:
17*37094b6dSJohn Snow#  Luiz Capitulino <lcapitulino@redhat.com>
18*37094b6dSJohn Snow#  John Snow <jsnow@redhat.com>
19*37094b6dSJohn Snow#
20*37094b6dSJohn Snow# This work is licensed under the terms of the GNU GPL, version 2.  See
21*37094b6dSJohn Snow# the COPYING file in the top-level directory.
22*37094b6dSJohn Snow#
23*37094b6dSJohn Snow
24*37094b6dSJohn Snowimport asyncio
25*37094b6dSJohn Snowfrom types import TracebackType
26*37094b6dSJohn Snowfrom typing import (
27*37094b6dSJohn Snow    Any,
28*37094b6dSJohn Snow    Awaitable,
29*37094b6dSJohn Snow    Dict,
30*37094b6dSJohn Snow    List,
31*37094b6dSJohn Snow    Optional,
32*37094b6dSJohn Snow    Type,
33*37094b6dSJohn Snow    TypeVar,
34*37094b6dSJohn Snow    Union,
35*37094b6dSJohn Snow)
36*37094b6dSJohn Snow
37*37094b6dSJohn Snowfrom .error import QMPError
38*37094b6dSJohn Snowfrom .protocol import Runstate, SocketAddrT
39*37094b6dSJohn Snowfrom .qmp_client import QMPClient
40*37094b6dSJohn Snow
41*37094b6dSJohn Snow
42*37094b6dSJohn Snow#: QMPMessage is an entire QMP message of any kind.
43*37094b6dSJohn SnowQMPMessage = Dict[str, Any]
44*37094b6dSJohn Snow
45*37094b6dSJohn Snow#: QMPReturnValue is the 'return' value of a command.
46*37094b6dSJohn SnowQMPReturnValue = object
47*37094b6dSJohn Snow
48*37094b6dSJohn Snow#: QMPObject is any object in a QMP message.
49*37094b6dSJohn SnowQMPObject = Dict[str, object]
50*37094b6dSJohn Snow
51*37094b6dSJohn Snow# QMPMessage can be outgoing commands or incoming events/returns.
52*37094b6dSJohn Snow# QMPReturnValue is usually a dict/json object, but due to QAPI's
53*37094b6dSJohn Snow# 'returns-whitelist', it can actually be anything.
54*37094b6dSJohn Snow#
55*37094b6dSJohn Snow# {'return': {}} is a QMPMessage,
56*37094b6dSJohn Snow# {} is the QMPReturnValue.
57*37094b6dSJohn Snow
58*37094b6dSJohn Snow
59*37094b6dSJohn Snowclass QMPBadPortError(QMPError):
60*37094b6dSJohn Snow    """
61*37094b6dSJohn Snow    Unable to parse socket address: Port was non-numerical.
62*37094b6dSJohn Snow    """
63*37094b6dSJohn Snow
64*37094b6dSJohn Snow
65*37094b6dSJohn Snowclass QEMUMonitorProtocol:
66*37094b6dSJohn Snow    """
67*37094b6dSJohn Snow    Provide an API to connect to QEMU via QEMU Monitor Protocol (QMP)
68*37094b6dSJohn Snow    and then allow to handle commands and events.
69*37094b6dSJohn Snow
70*37094b6dSJohn Snow    :param address:  QEMU address, can be either a unix socket path (string)
71*37094b6dSJohn Snow                     or a tuple in the form ( address, port ) for a TCP
72*37094b6dSJohn Snow                     connection
73*37094b6dSJohn Snow    :param server:   Act as the socket server. (See 'accept')
74*37094b6dSJohn Snow    :param nickname: Optional nickname used for logging.
75*37094b6dSJohn Snow    """
76*37094b6dSJohn Snow
77*37094b6dSJohn Snow    def __init__(self, address: SocketAddrT,
78*37094b6dSJohn Snow                 server: bool = False,
79*37094b6dSJohn Snow                 nickname: Optional[str] = None):
80*37094b6dSJohn Snow
81*37094b6dSJohn Snow        self._qmp = QMPClient(nickname)
82*37094b6dSJohn Snow        self._aloop = asyncio.get_event_loop()
83*37094b6dSJohn Snow        self._address = address
84*37094b6dSJohn Snow        self._timeout: Optional[float] = None
85*37094b6dSJohn Snow
86*37094b6dSJohn Snow        if server:
87*37094b6dSJohn Snow            self._sync(self._qmp.start_server(self._address))
88*37094b6dSJohn Snow
89*37094b6dSJohn Snow    _T = TypeVar('_T')
90*37094b6dSJohn Snow
91*37094b6dSJohn Snow    def _sync(
92*37094b6dSJohn Snow            self, future: Awaitable[_T], timeout: Optional[float] = None
93*37094b6dSJohn Snow    ) -> _T:
94*37094b6dSJohn Snow        return self._aloop.run_until_complete(
95*37094b6dSJohn Snow            asyncio.wait_for(future, timeout=timeout)
96*37094b6dSJohn Snow        )
97*37094b6dSJohn Snow
98*37094b6dSJohn Snow    def _get_greeting(self) -> Optional[QMPMessage]:
99*37094b6dSJohn Snow        if self._qmp.greeting is not None:
100*37094b6dSJohn Snow            # pylint: disable=protected-access
101*37094b6dSJohn Snow            return self._qmp.greeting._asdict()
102*37094b6dSJohn Snow        return None
103*37094b6dSJohn Snow
104*37094b6dSJohn Snow    def __enter__(self: _T) -> _T:
105*37094b6dSJohn Snow        # Implement context manager enter function.
106*37094b6dSJohn Snow        return self
107*37094b6dSJohn Snow
108*37094b6dSJohn Snow    def __exit__(self,
109*37094b6dSJohn Snow                 # pylint: disable=duplicate-code
110*37094b6dSJohn Snow                 # see https://github.com/PyCQA/pylint/issues/3619
111*37094b6dSJohn Snow                 exc_type: Optional[Type[BaseException]],
112*37094b6dSJohn Snow                 exc_val: Optional[BaseException],
113*37094b6dSJohn Snow                 exc_tb: Optional[TracebackType]) -> None:
114*37094b6dSJohn Snow        # Implement context manager exit function.
115*37094b6dSJohn Snow        self.close()
116*37094b6dSJohn Snow
117*37094b6dSJohn Snow    @classmethod
118*37094b6dSJohn Snow    def parse_address(cls, address: str) -> SocketAddrT:
119*37094b6dSJohn Snow        """
120*37094b6dSJohn Snow        Parse a string into a QMP address.
121*37094b6dSJohn Snow
122*37094b6dSJohn Snow        Figure out if the argument is in the port:host form.
123*37094b6dSJohn Snow        If it's not, it's probably a file path.
124*37094b6dSJohn Snow        """
125*37094b6dSJohn Snow        components = address.split(':')
126*37094b6dSJohn Snow        if len(components) == 2:
127*37094b6dSJohn Snow            try:
128*37094b6dSJohn Snow                port = int(components[1])
129*37094b6dSJohn Snow            except ValueError:
130*37094b6dSJohn Snow                msg = f"Bad port: '{components[1]}' in '{address}'."
131*37094b6dSJohn Snow                raise QMPBadPortError(msg) from None
132*37094b6dSJohn Snow            return (components[0], port)
133*37094b6dSJohn Snow
134*37094b6dSJohn Snow        # Treat as filepath.
135*37094b6dSJohn Snow        return address
136*37094b6dSJohn Snow
137*37094b6dSJohn Snow    def connect(self, negotiate: bool = True) -> Optional[QMPMessage]:
138*37094b6dSJohn Snow        """
139*37094b6dSJohn Snow        Connect to the QMP Monitor and perform capabilities negotiation.
140*37094b6dSJohn Snow
141*37094b6dSJohn Snow        :return: QMP greeting dict, or None if negotiate is false
142*37094b6dSJohn Snow        :raise ConnectError: on connection errors
143*37094b6dSJohn Snow        """
144*37094b6dSJohn Snow        self._qmp.await_greeting = negotiate
145*37094b6dSJohn Snow        self._qmp.negotiate = negotiate
146*37094b6dSJohn Snow
147*37094b6dSJohn Snow        self._sync(
148*37094b6dSJohn Snow            self._qmp.connect(self._address)
149*37094b6dSJohn Snow        )
150*37094b6dSJohn Snow        return self._get_greeting()
151*37094b6dSJohn Snow
152*37094b6dSJohn Snow    def accept(self, timeout: Optional[float] = 15.0) -> QMPMessage:
153*37094b6dSJohn Snow        """
154*37094b6dSJohn Snow        Await connection from QMP Monitor and perform capabilities negotiation.
155*37094b6dSJohn Snow
156*37094b6dSJohn Snow        :param timeout:
157*37094b6dSJohn Snow            timeout in seconds (nonnegative float number, or None).
158*37094b6dSJohn Snow            If None, there is no timeout, and this may block forever.
159*37094b6dSJohn Snow
160*37094b6dSJohn Snow        :return: QMP greeting dict
161*37094b6dSJohn Snow        :raise ConnectError: on connection errors
162*37094b6dSJohn Snow        """
163*37094b6dSJohn Snow        self._qmp.await_greeting = True
164*37094b6dSJohn Snow        self._qmp.negotiate = True
165*37094b6dSJohn Snow
166*37094b6dSJohn Snow        self._sync(self._qmp.accept(), timeout)
167*37094b6dSJohn Snow
168*37094b6dSJohn Snow        ret = self._get_greeting()
169*37094b6dSJohn Snow        assert ret is not None
170*37094b6dSJohn Snow        return ret
171*37094b6dSJohn Snow
172*37094b6dSJohn Snow    def cmd_obj(self, qmp_cmd: QMPMessage) -> QMPMessage:
173*37094b6dSJohn Snow        """
174*37094b6dSJohn Snow        Send a QMP command to the QMP Monitor.
175*37094b6dSJohn Snow
176*37094b6dSJohn Snow        :param qmp_cmd: QMP command to be sent as a Python dict
177*37094b6dSJohn Snow        :return: QMP response as a Python dict
178*37094b6dSJohn Snow        """
179*37094b6dSJohn Snow        return dict(
180*37094b6dSJohn Snow            self._sync(
181*37094b6dSJohn Snow                # pylint: disable=protected-access
182*37094b6dSJohn Snow
183*37094b6dSJohn Snow                # _raw() isn't a public API, because turning off
184*37094b6dSJohn Snow                # automatic ID assignment is discouraged. For
185*37094b6dSJohn Snow                # compatibility with iotests *only*, do it anyway.
186*37094b6dSJohn Snow                self._qmp._raw(qmp_cmd, assign_id=False),
187*37094b6dSJohn Snow                self._timeout
188*37094b6dSJohn Snow            )
189*37094b6dSJohn Snow        )
190*37094b6dSJohn Snow
191*37094b6dSJohn Snow    def cmd(self, name: str,
192*37094b6dSJohn Snow            args: Optional[Dict[str, object]] = None,
193*37094b6dSJohn Snow            cmd_id: Optional[object] = None) -> QMPMessage:
194*37094b6dSJohn Snow        """
195*37094b6dSJohn Snow        Build a QMP command and send it to the QMP Monitor.
196*37094b6dSJohn Snow
197*37094b6dSJohn Snow        :param name: command name (string)
198*37094b6dSJohn Snow        :param args: command arguments (dict)
199*37094b6dSJohn Snow        :param cmd_id: command id (dict, list, string or int)
200*37094b6dSJohn Snow        """
201*37094b6dSJohn Snow        qmp_cmd: QMPMessage = {'execute': name}
202*37094b6dSJohn Snow        if args:
203*37094b6dSJohn Snow            qmp_cmd['arguments'] = args
204*37094b6dSJohn Snow        if cmd_id:
205*37094b6dSJohn Snow            qmp_cmd['id'] = cmd_id
206*37094b6dSJohn Snow        return self.cmd_obj(qmp_cmd)
207*37094b6dSJohn Snow
208*37094b6dSJohn Snow    def command(self, cmd: str, **kwds: object) -> QMPReturnValue:
209*37094b6dSJohn Snow        """
210*37094b6dSJohn Snow        Build and send a QMP command to the monitor, report errors if any
211*37094b6dSJohn Snow        """
212*37094b6dSJohn Snow        return self._sync(
213*37094b6dSJohn Snow            self._qmp.execute(cmd, kwds),
214*37094b6dSJohn Snow            self._timeout
215*37094b6dSJohn Snow        )
216*37094b6dSJohn Snow
217*37094b6dSJohn Snow    def pull_event(self,
218*37094b6dSJohn Snow                   wait: Union[bool, float] = False) -> Optional[QMPMessage]:
219*37094b6dSJohn Snow        """
220*37094b6dSJohn Snow        Pulls a single event.
221*37094b6dSJohn Snow
222*37094b6dSJohn Snow        :param wait:
223*37094b6dSJohn Snow            If False or 0, do not wait. Return None if no events ready.
224*37094b6dSJohn Snow            If True, wait forever until the next event.
225*37094b6dSJohn Snow            Otherwise, wait for the specified number of seconds.
226*37094b6dSJohn Snow
227*37094b6dSJohn Snow        :raise asyncio.TimeoutError:
228*37094b6dSJohn Snow            When a timeout is requested and the timeout period elapses.
229*37094b6dSJohn Snow
230*37094b6dSJohn Snow        :return: The first available QMP event, or None.
231*37094b6dSJohn Snow        """
232*37094b6dSJohn Snow        if not wait:
233*37094b6dSJohn Snow            # wait is False/0: "do not wait, do not except."
234*37094b6dSJohn Snow            if self._qmp.events.empty():
235*37094b6dSJohn Snow                return None
236*37094b6dSJohn Snow
237*37094b6dSJohn Snow        # If wait is 'True', wait forever. If wait is False/0, the events
238*37094b6dSJohn Snow        # queue must not be empty; but it still needs some real amount
239*37094b6dSJohn Snow        # of time to complete.
240*37094b6dSJohn Snow        timeout = None
241*37094b6dSJohn Snow        if wait and isinstance(wait, float):
242*37094b6dSJohn Snow            timeout = wait
243*37094b6dSJohn Snow
244*37094b6dSJohn Snow        return dict(
245*37094b6dSJohn Snow            self._sync(
246*37094b6dSJohn Snow                self._qmp.events.get(),
247*37094b6dSJohn Snow                timeout
248*37094b6dSJohn Snow            )
249*37094b6dSJohn Snow        )
250*37094b6dSJohn Snow
251*37094b6dSJohn Snow    def get_events(self, wait: Union[bool, float] = False) -> List[QMPMessage]:
252*37094b6dSJohn Snow        """
253*37094b6dSJohn Snow        Get a list of QMP events and clear all pending events.
254*37094b6dSJohn Snow
255*37094b6dSJohn Snow        :param wait:
256*37094b6dSJohn Snow            If False or 0, do not wait. Return None if no events ready.
257*37094b6dSJohn Snow            If True, wait until we have at least one event.
258*37094b6dSJohn Snow            Otherwise, wait for up to the specified number of seconds for at
259*37094b6dSJohn Snow            least one event.
260*37094b6dSJohn Snow
261*37094b6dSJohn Snow        :raise asyncio.TimeoutError:
262*37094b6dSJohn Snow            When a timeout is requested and the timeout period elapses.
263*37094b6dSJohn Snow
264*37094b6dSJohn Snow        :return: A list of QMP events.
265*37094b6dSJohn Snow        """
266*37094b6dSJohn Snow        events = [dict(x) for x in self._qmp.events.clear()]
267*37094b6dSJohn Snow        if events:
268*37094b6dSJohn Snow            return events
269*37094b6dSJohn Snow
270*37094b6dSJohn Snow        event = self.pull_event(wait)
271*37094b6dSJohn Snow        return [event] if event is not None else []
272*37094b6dSJohn Snow
273*37094b6dSJohn Snow    def clear_events(self) -> None:
274*37094b6dSJohn Snow        """Clear current list of pending events."""
275*37094b6dSJohn Snow        self._qmp.events.clear()
276*37094b6dSJohn Snow
277*37094b6dSJohn Snow    def close(self) -> None:
278*37094b6dSJohn Snow        """Close the connection."""
279*37094b6dSJohn Snow        self._sync(
280*37094b6dSJohn Snow            self._qmp.disconnect()
281*37094b6dSJohn Snow        )
282*37094b6dSJohn Snow
283*37094b6dSJohn Snow    def settimeout(self, timeout: Optional[float]) -> None:
284*37094b6dSJohn Snow        """
285*37094b6dSJohn Snow        Set the timeout for QMP RPC execution.
286*37094b6dSJohn Snow
287*37094b6dSJohn Snow        This timeout affects the `cmd`, `cmd_obj`, and `command` methods.
288*37094b6dSJohn Snow        The `accept`, `pull_event` and `get_event` methods have their
289*37094b6dSJohn Snow        own configurable timeouts.
290*37094b6dSJohn Snow
291*37094b6dSJohn Snow        :param timeout:
292*37094b6dSJohn Snow            timeout in seconds, or None.
293*37094b6dSJohn Snow            None will wait indefinitely.
294*37094b6dSJohn Snow        """
295*37094b6dSJohn Snow        self._timeout = timeout
296*37094b6dSJohn Snow
297*37094b6dSJohn Snow    def send_fd_scm(self, fd: int) -> None:
298*37094b6dSJohn Snow        """
299*37094b6dSJohn Snow        Send a file descriptor to the remote via SCM_RIGHTS.
300*37094b6dSJohn Snow        """
301*37094b6dSJohn Snow        self._qmp.send_fd_scm(fd)
302*37094b6dSJohn Snow
303*37094b6dSJohn Snow    def __del__(self) -> None:
304*37094b6dSJohn Snow        if self._qmp.runstate == Runstate.IDLE:
305*37094b6dSJohn Snow            return
306*37094b6dSJohn Snow
307*37094b6dSJohn Snow        if not self._aloop.is_running():
308*37094b6dSJohn Snow            self.close()
309*37094b6dSJohn Snow        else:
310*37094b6dSJohn Snow            # Garbage collection ran while the event loop was running.
311*37094b6dSJohn Snow            # Nothing we can do about it now, but if we don't raise our
312*37094b6dSJohn Snow            # own error, the user will be treated to a lot of traceback
313*37094b6dSJohn Snow            # they might not understand.
314*37094b6dSJohn Snow            raise QMPError(
315*37094b6dSJohn Snow                "QEMUMonitorProtocol.close()"
316*37094b6dSJohn Snow                " was not called before object was garbage collected"
317*37094b6dSJohn Snow            )
318