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