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 19737094b6dSJohn Snow def cmd(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 21037094b6dSJohn Snow def command(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