1beb6b57bSJohn Snow""" 2beb6b57bSJohn SnowQEMU qtest library 3beb6b57bSJohn Snow 4beb6b57bSJohn Snowqtest offers the QEMUQtestProtocol and QEMUQTestMachine classes, which 5beb6b57bSJohn Snowoffer a connection to QEMU's qtest protocol socket, and a qtest-enabled 6beb6b57bSJohn Snowsubclass of QEMUMachine, respectively. 7beb6b57bSJohn Snow""" 8beb6b57bSJohn Snow 9beb6b57bSJohn Snow# Copyright (C) 2015 Red Hat Inc. 10beb6b57bSJohn Snow# 11beb6b57bSJohn Snow# Authors: 12beb6b57bSJohn Snow# Fam Zheng <famz@redhat.com> 13beb6b57bSJohn Snow# 14beb6b57bSJohn Snow# This work is licensed under the terms of the GNU GPL, version 2. See 15beb6b57bSJohn Snow# the COPYING file in the top-level directory. 16beb6b57bSJohn Snow# 17beb6b57bSJohn Snow# Based on qmp.py. 18beb6b57bSJohn Snow# 19beb6b57bSJohn Snow 20beb6b57bSJohn Snowimport os 21beb6b57bSJohn Snowimport socket 22beb6b57bSJohn Snowfrom typing import ( 23beb6b57bSJohn Snow List, 24beb6b57bSJohn Snow Optional, 25beb6b57bSJohn Snow Sequence, 26beb6b57bSJohn Snow TextIO, 27beb6b57bSJohn Snow) 28beb6b57bSJohn Snow 29d1e04769SJohn Snowfrom qemu.qmp import SocketAddrT # pylint: disable=import-error 30beb6b57bSJohn Snow 31beb6b57bSJohn Snowfrom .machine import QEMUMachine 32beb6b57bSJohn Snow 33beb6b57bSJohn Snow 34beb6b57bSJohn Snowclass QEMUQtestProtocol: 35beb6b57bSJohn Snow """ 36beb6b57bSJohn Snow QEMUQtestProtocol implements a connection to a qtest socket. 37beb6b57bSJohn Snow 38beb6b57bSJohn Snow :param address: QEMU address, can be either a unix socket path (string) 39beb6b57bSJohn Snow or a tuple in the form ( address, port ) for a TCP 40beb6b57bSJohn Snow connection 41beb6b57bSJohn Snow :param server: server mode, listens on the socket (bool) 42beb6b57bSJohn Snow :raise socket.error: on socket connection errors 43beb6b57bSJohn Snow 44beb6b57bSJohn Snow .. note:: 45beb6b57bSJohn Snow No conection is estabalished by __init__(), this is done 46beb6b57bSJohn Snow by the connect() or accept() methods. 47beb6b57bSJohn Snow """ 48beb6b57bSJohn Snow def __init__(self, address: SocketAddrT, 49beb6b57bSJohn Snow server: bool = False): 50beb6b57bSJohn Snow self._address = address 51beb6b57bSJohn Snow self._sock = self._get_sock() 52beb6b57bSJohn Snow self._sockfile: Optional[TextIO] = None 53beb6b57bSJohn Snow if server: 54beb6b57bSJohn Snow self._sock.bind(self._address) 55beb6b57bSJohn Snow self._sock.listen(1) 56beb6b57bSJohn Snow 57beb6b57bSJohn Snow def _get_sock(self) -> socket.socket: 58beb6b57bSJohn Snow if isinstance(self._address, tuple): 59beb6b57bSJohn Snow family = socket.AF_INET 60beb6b57bSJohn Snow else: 61beb6b57bSJohn Snow family = socket.AF_UNIX 62beb6b57bSJohn Snow return socket.socket(family, socket.SOCK_STREAM) 63beb6b57bSJohn Snow 64beb6b57bSJohn Snow def connect(self) -> None: 65beb6b57bSJohn Snow """ 66beb6b57bSJohn Snow Connect to the qtest socket. 67beb6b57bSJohn Snow 68beb6b57bSJohn Snow @raise socket.error on socket connection errors 69beb6b57bSJohn Snow """ 70beb6b57bSJohn Snow self._sock.connect(self._address) 71beb6b57bSJohn Snow self._sockfile = self._sock.makefile(mode='r') 72beb6b57bSJohn Snow 73beb6b57bSJohn Snow def accept(self) -> None: 74beb6b57bSJohn Snow """ 75beb6b57bSJohn Snow Await connection from QEMU. 76beb6b57bSJohn Snow 77beb6b57bSJohn Snow @raise socket.error on socket connection errors 78beb6b57bSJohn Snow """ 79beb6b57bSJohn Snow self._sock, _ = self._sock.accept() 80beb6b57bSJohn Snow self._sockfile = self._sock.makefile(mode='r') 81beb6b57bSJohn Snow 82beb6b57bSJohn Snow def cmd(self, qtest_cmd: str) -> str: 83beb6b57bSJohn Snow """ 84beb6b57bSJohn Snow Send a qtest command on the wire. 85beb6b57bSJohn Snow 86beb6b57bSJohn Snow @param qtest_cmd: qtest command text to be sent 87beb6b57bSJohn Snow """ 88beb6b57bSJohn Snow assert self._sockfile is not None 89beb6b57bSJohn Snow self._sock.sendall((qtest_cmd + "\n").encode('utf-8')) 90beb6b57bSJohn Snow resp = self._sockfile.readline() 91beb6b57bSJohn Snow return resp 92beb6b57bSJohn Snow 93beb6b57bSJohn Snow def close(self) -> None: 94beb6b57bSJohn Snow """ 95beb6b57bSJohn Snow Close this socket. 96beb6b57bSJohn Snow """ 97beb6b57bSJohn Snow self._sock.close() 98beb6b57bSJohn Snow if self._sockfile: 99beb6b57bSJohn Snow self._sockfile.close() 100beb6b57bSJohn Snow self._sockfile = None 101beb6b57bSJohn Snow 102beb6b57bSJohn Snow def settimeout(self, timeout: Optional[float]) -> None: 103beb6b57bSJohn Snow """Set a timeout, in seconds.""" 104beb6b57bSJohn Snow self._sock.settimeout(timeout) 105beb6b57bSJohn Snow 106beb6b57bSJohn Snow 107beb6b57bSJohn Snowclass QEMUQtestMachine(QEMUMachine): 108beb6b57bSJohn Snow """ 109beb6b57bSJohn Snow A QEMU VM, with a qtest socket available. 110beb6b57bSJohn Snow """ 111beb6b57bSJohn Snow 112beb6b57bSJohn Snow def __init__(self, 113beb6b57bSJohn Snow binary: str, 114beb6b57bSJohn Snow args: Sequence[str] = (), 115*804f7695SEmanuele Giuseppe Esposito wrapper: Sequence[str] = (), 116beb6b57bSJohn Snow name: Optional[str] = None, 117beb6b57bSJohn Snow base_temp_dir: str = "/var/tmp", 118beb6b57bSJohn Snow socket_scm_helper: Optional[str] = None, 119e2f948a8SEmanuele Giuseppe Esposito sock_dir: Optional[str] = None, 120e2f948a8SEmanuele Giuseppe Esposito qmp_timer: Optional[float] = None): 12182e6517dSJohn Snow # pylint: disable=too-many-arguments 12282e6517dSJohn Snow 123beb6b57bSJohn Snow if name is None: 124beb6b57bSJohn Snow name = "qemu-%d" % os.getpid() 125beb6b57bSJohn Snow if sock_dir is None: 126beb6b57bSJohn Snow sock_dir = base_temp_dir 127*804f7695SEmanuele Giuseppe Esposito super().__init__(binary, args, wrapper=wrapper, name=name, 128*804f7695SEmanuele Giuseppe Esposito base_temp_dir=base_temp_dir, 129beb6b57bSJohn Snow socket_scm_helper=socket_scm_helper, 130e2f948a8SEmanuele Giuseppe Esposito sock_dir=sock_dir, qmp_timer=qmp_timer) 131beb6b57bSJohn Snow self._qtest: Optional[QEMUQtestProtocol] = None 132beb6b57bSJohn Snow self._qtest_path = os.path.join(sock_dir, name + "-qtest.sock") 133beb6b57bSJohn Snow 134beb6b57bSJohn Snow @property 135beb6b57bSJohn Snow def _base_args(self) -> List[str]: 136beb6b57bSJohn Snow args = super()._base_args 137beb6b57bSJohn Snow args.extend([ 138beb6b57bSJohn Snow '-qtest', f"unix:path={self._qtest_path}", 139beb6b57bSJohn Snow '-accel', 'qtest' 140beb6b57bSJohn Snow ]) 141beb6b57bSJohn Snow return args 142beb6b57bSJohn Snow 143beb6b57bSJohn Snow def _pre_launch(self) -> None: 144beb6b57bSJohn Snow super()._pre_launch() 145beb6b57bSJohn Snow self._qtest = QEMUQtestProtocol(self._qtest_path, server=True) 146beb6b57bSJohn Snow 147beb6b57bSJohn Snow def _post_launch(self) -> None: 148beb6b57bSJohn Snow assert self._qtest is not None 149beb6b57bSJohn Snow super()._post_launch() 150beb6b57bSJohn Snow self._qtest.accept() 151beb6b57bSJohn Snow 152beb6b57bSJohn Snow def _post_shutdown(self) -> None: 153beb6b57bSJohn Snow super()._post_shutdown() 154beb6b57bSJohn Snow self._remove_if_exists(self._qtest_path) 155beb6b57bSJohn Snow 156beb6b57bSJohn Snow def qtest(self, cmd: str) -> str: 157beb6b57bSJohn Snow """ 158beb6b57bSJohn Snow Send a qtest command to the guest. 159beb6b57bSJohn Snow 160beb6b57bSJohn Snow :param cmd: qtest command to send 161beb6b57bSJohn Snow :return: qtest server response 162beb6b57bSJohn Snow """ 163beb6b57bSJohn Snow if self._qtest is None: 164beb6b57bSJohn Snow raise RuntimeError("qtest socket not available") 165beb6b57bSJohn Snow return self._qtest.cmd(cmd) 166