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 29*d1e04769SJohn 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] = (), 115beb6b57bSJohn Snow name: Optional[str] = None, 116beb6b57bSJohn Snow base_temp_dir: str = "/var/tmp", 117beb6b57bSJohn Snow socket_scm_helper: Optional[str] = None, 118beb6b57bSJohn Snow sock_dir: Optional[str] = None): 119beb6b57bSJohn Snow if name is None: 120beb6b57bSJohn Snow name = "qemu-%d" % os.getpid() 121beb6b57bSJohn Snow if sock_dir is None: 122beb6b57bSJohn Snow sock_dir = base_temp_dir 123beb6b57bSJohn Snow super().__init__(binary, args, name=name, base_temp_dir=base_temp_dir, 124beb6b57bSJohn Snow socket_scm_helper=socket_scm_helper, 125beb6b57bSJohn Snow sock_dir=sock_dir) 126beb6b57bSJohn Snow self._qtest: Optional[QEMUQtestProtocol] = None 127beb6b57bSJohn Snow self._qtest_path = os.path.join(sock_dir, name + "-qtest.sock") 128beb6b57bSJohn Snow 129beb6b57bSJohn Snow @property 130beb6b57bSJohn Snow def _base_args(self) -> List[str]: 131beb6b57bSJohn Snow args = super()._base_args 132beb6b57bSJohn Snow args.extend([ 133beb6b57bSJohn Snow '-qtest', f"unix:path={self._qtest_path}", 134beb6b57bSJohn Snow '-accel', 'qtest' 135beb6b57bSJohn Snow ]) 136beb6b57bSJohn Snow return args 137beb6b57bSJohn Snow 138beb6b57bSJohn Snow def _pre_launch(self) -> None: 139beb6b57bSJohn Snow super()._pre_launch() 140beb6b57bSJohn Snow self._qtest = QEMUQtestProtocol(self._qtest_path, server=True) 141beb6b57bSJohn Snow 142beb6b57bSJohn Snow def _post_launch(self) -> None: 143beb6b57bSJohn Snow assert self._qtest is not None 144beb6b57bSJohn Snow super()._post_launch() 145beb6b57bSJohn Snow self._qtest.accept() 146beb6b57bSJohn Snow 147beb6b57bSJohn Snow def _post_shutdown(self) -> None: 148beb6b57bSJohn Snow super()._post_shutdown() 149beb6b57bSJohn Snow self._remove_if_exists(self._qtest_path) 150beb6b57bSJohn Snow 151beb6b57bSJohn Snow def qtest(self, cmd: str) -> str: 152beb6b57bSJohn Snow """ 153beb6b57bSJohn Snow Send a qtest command to the guest. 154beb6b57bSJohn Snow 155beb6b57bSJohn Snow :param cmd: qtest command to send 156beb6b57bSJohn Snow :return: qtest server response 157beb6b57bSJohn Snow """ 158beb6b57bSJohn Snow if self._qtest is None: 159beb6b57bSJohn Snow raise RuntimeError("qtest socket not available") 160beb6b57bSJohn Snow return self._qtest.cmd(cmd) 161