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