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, 27d3967378SJohn Snow Tuple, 28beb6b57bSJohn Snow) 29beb6b57bSJohn Snow 3037094b6dSJohn Snowfrom qemu.qmp import SocketAddrT 31beb6b57bSJohn Snow 32beb6b57bSJohn Snowfrom .machine import QEMUMachine 33beb6b57bSJohn Snow 34beb6b57bSJohn Snow 35beb6b57bSJohn Snowclass QEMUQtestProtocol: 36beb6b57bSJohn Snow """ 37beb6b57bSJohn Snow QEMUQtestProtocol implements a connection to a qtest socket. 38beb6b57bSJohn Snow 39beb6b57bSJohn Snow :param address: QEMU address, can be either a unix socket path (string) 40beb6b57bSJohn Snow or a tuple in the form ( address, port ) for a TCP 41beb6b57bSJohn Snow connection 42d3967378SJohn Snow :param sock: An existing socket can be provided as an alternative to 43d3967378SJohn Snow an address. One of address or sock must be provided. 44d3967378SJohn Snow :param server: server mode, listens on the socket. Only meaningful 45d3967378SJohn Snow in conjunction with an address and not an existing 46d3967378SJohn Snow socket. 47d3967378SJohn Snow 48beb6b57bSJohn Snow :raise socket.error: on socket connection errors 49beb6b57bSJohn Snow 50beb6b57bSJohn Snow .. note:: 51af76484eSDongdong Zhang No connection is established by __init__(), this is done 52beb6b57bSJohn Snow by the connect() or accept() methods. 53beb6b57bSJohn Snow """ 54d3967378SJohn Snow def __init__(self, 55d3967378SJohn Snow address: Optional[SocketAddrT] = None, 56d3967378SJohn Snow sock: Optional[socket.socket] = None, 57beb6b57bSJohn Snow server: bool = False): 58d3967378SJohn Snow if address is None and sock is None: 59d3967378SJohn Snow raise ValueError("Either 'address' or 'sock' must be specified") 60d3967378SJohn Snow if address is not None and sock is not None: 61d3967378SJohn Snow raise ValueError( 62d3967378SJohn Snow "Either 'address' or 'sock' must be specified, but not both") 63d3967378SJohn Snow if sock is not None and server: 64d3967378SJohn Snow raise ValueError("server=True is meaningless when passing socket") 65d3967378SJohn Snow 66beb6b57bSJohn Snow self._address = address 67d3967378SJohn Snow self._sock = sock or self._get_sock() 68beb6b57bSJohn Snow self._sockfile: Optional[TextIO] = None 69d3967378SJohn Snow 70beb6b57bSJohn Snow if server: 71d3967378SJohn Snow assert self._address is not None 72beb6b57bSJohn Snow self._sock.bind(self._address) 73beb6b57bSJohn Snow self._sock.listen(1) 74beb6b57bSJohn Snow 75beb6b57bSJohn Snow def _get_sock(self) -> socket.socket: 76d3967378SJohn Snow assert self._address is not None 77beb6b57bSJohn Snow if isinstance(self._address, tuple): 78beb6b57bSJohn Snow family = socket.AF_INET 79beb6b57bSJohn Snow else: 80beb6b57bSJohn Snow family = socket.AF_UNIX 81beb6b57bSJohn Snow return socket.socket(family, socket.SOCK_STREAM) 82beb6b57bSJohn Snow 83beb6b57bSJohn Snow def connect(self) -> None: 84beb6b57bSJohn Snow """ 85beb6b57bSJohn Snow Connect to the qtest socket. 86beb6b57bSJohn Snow 87beb6b57bSJohn Snow @raise socket.error on socket connection errors 88beb6b57bSJohn Snow """ 89d3967378SJohn Snow if self._address is not None: 90beb6b57bSJohn Snow self._sock.connect(self._address) 91beb6b57bSJohn Snow self._sockfile = self._sock.makefile(mode='r') 92beb6b57bSJohn Snow 93beb6b57bSJohn Snow def accept(self) -> None: 94beb6b57bSJohn Snow """ 95beb6b57bSJohn Snow Await connection from QEMU. 96beb6b57bSJohn Snow 97beb6b57bSJohn Snow @raise socket.error on socket connection errors 98beb6b57bSJohn Snow """ 99beb6b57bSJohn Snow self._sock, _ = self._sock.accept() 100beb6b57bSJohn Snow self._sockfile = self._sock.makefile(mode='r') 101beb6b57bSJohn Snow 102beb6b57bSJohn Snow def cmd(self, qtest_cmd: str) -> str: 103beb6b57bSJohn Snow """ 104beb6b57bSJohn Snow Send a qtest command on the wire. 105beb6b57bSJohn Snow 106beb6b57bSJohn Snow @param qtest_cmd: qtest command text to be sent 107beb6b57bSJohn Snow """ 108beb6b57bSJohn Snow assert self._sockfile is not None 109beb6b57bSJohn Snow self._sock.sendall((qtest_cmd + "\n").encode('utf-8')) 110beb6b57bSJohn Snow resp = self._sockfile.readline() 111beb6b57bSJohn Snow return resp 112beb6b57bSJohn Snow 113beb6b57bSJohn Snow def close(self) -> None: 114beb6b57bSJohn Snow """ 115beb6b57bSJohn Snow Close this socket. 116beb6b57bSJohn Snow """ 117beb6b57bSJohn Snow self._sock.close() 118beb6b57bSJohn Snow if self._sockfile: 119beb6b57bSJohn Snow self._sockfile.close() 120beb6b57bSJohn Snow self._sockfile = None 121beb6b57bSJohn Snow 122beb6b57bSJohn Snow def settimeout(self, timeout: Optional[float]) -> None: 123beb6b57bSJohn Snow """Set a timeout, in seconds.""" 124beb6b57bSJohn Snow self._sock.settimeout(timeout) 125beb6b57bSJohn Snow 126beb6b57bSJohn Snow 127beb6b57bSJohn Snowclass QEMUQtestMachine(QEMUMachine): 128beb6b57bSJohn Snow """ 129beb6b57bSJohn Snow A QEMU VM, with a qtest socket available. 130beb6b57bSJohn Snow """ 131beb6b57bSJohn Snow 132beb6b57bSJohn Snow def __init__(self, 133beb6b57bSJohn Snow binary: str, 134beb6b57bSJohn Snow args: Sequence[str] = (), 135804f7695SEmanuele Giuseppe Esposito wrapper: Sequence[str] = (), 136beb6b57bSJohn Snow name: Optional[str] = None, 137beb6b57bSJohn Snow base_temp_dir: str = "/var/tmp", 138e2f948a8SEmanuele Giuseppe Esposito qmp_timer: Optional[float] = None): 13982e6517dSJohn Snow # pylint: disable=too-many-arguments 14082e6517dSJohn Snow 141beb6b57bSJohn Snow if name is None: 142beb6b57bSJohn Snow name = "qemu-%d" % os.getpid() 143804f7695SEmanuele Giuseppe Esposito super().__init__(binary, args, wrapper=wrapper, name=name, 144804f7695SEmanuele Giuseppe Esposito base_temp_dir=base_temp_dir, 145*46d4747aSJohn Snow qmp_timer=qmp_timer) 146beb6b57bSJohn Snow self._qtest: Optional[QEMUQtestProtocol] = None 147d3967378SJohn Snow self._qtest_sock_pair: Optional[ 148d3967378SJohn Snow Tuple[socket.socket, socket.socket]] = None 149beb6b57bSJohn Snow 150beb6b57bSJohn Snow @property 151beb6b57bSJohn Snow def _base_args(self) -> List[str]: 152beb6b57bSJohn Snow args = super()._base_args 153d3967378SJohn Snow assert self._qtest_sock_pair is not None 154d3967378SJohn Snow fd = self._qtest_sock_pair[0].fileno() 155beb6b57bSJohn Snow args.extend([ 156d3967378SJohn Snow '-chardev', f"socket,id=qtest,fd={fd}", 157d3967378SJohn Snow '-qtest', 'chardev:qtest', 158beb6b57bSJohn Snow '-accel', 'qtest' 159beb6b57bSJohn Snow ]) 160beb6b57bSJohn Snow return args 161beb6b57bSJohn Snow 162beb6b57bSJohn Snow def _pre_launch(self) -> None: 163d3967378SJohn Snow self._qtest_sock_pair = socket.socketpair() 164d3967378SJohn Snow os.set_inheritable(self._qtest_sock_pair[0].fileno(), True) 165beb6b57bSJohn Snow super()._pre_launch() 166d3967378SJohn Snow self._qtest = QEMUQtestProtocol(sock=self._qtest_sock_pair[1]) 167beb6b57bSJohn Snow 168beb6b57bSJohn Snow def _post_launch(self) -> None: 169beb6b57bSJohn Snow assert self._qtest is not None 170beb6b57bSJohn Snow super()._post_launch() 171d3967378SJohn Snow if self._qtest_sock_pair: 172d3967378SJohn Snow self._qtest_sock_pair[0].close() 173d3967378SJohn Snow self._qtest.connect() 174beb6b57bSJohn Snow 175beb6b57bSJohn Snow def _post_shutdown(self) -> None: 176d3967378SJohn Snow if self._qtest_sock_pair: 177d3967378SJohn Snow self._qtest_sock_pair[0].close() 178d3967378SJohn Snow self._qtest_sock_pair[1].close() 179d3967378SJohn Snow self._qtest_sock_pair = None 180beb6b57bSJohn Snow super()._post_shutdown() 181beb6b57bSJohn Snow 182beb6b57bSJohn Snow def qtest(self, cmd: str) -> str: 183beb6b57bSJohn Snow """ 184beb6b57bSJohn Snow Send a qtest command to the guest. 185beb6b57bSJohn Snow 186beb6b57bSJohn Snow :param cmd: qtest command to send 187beb6b57bSJohn Snow :return: qtest server response 188beb6b57bSJohn Snow """ 189beb6b57bSJohn Snow if self._qtest is None: 190beb6b57bSJohn Snow raise RuntimeError("qtest socket not available") 191beb6b57bSJohn Snow return self._qtest.cmd(cmd) 192