xref: /openbmc/qemu/python/qemu/machine/qtest.py (revision af76484e)
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
2937094b6dSJohn Snowfrom qemu.qmp import SocketAddrT
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::
45*af76484eSDongdong Zhang       No connection is established 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] = (),
115804f7695SEmanuele Giuseppe Esposito                 wrapper: Sequence[str] = (),
116beb6b57bSJohn Snow                 name: Optional[str] = None,
117beb6b57bSJohn Snow                 base_temp_dir: str = "/var/tmp",
118e2f948a8SEmanuele Giuseppe Esposito                 sock_dir: Optional[str] = None,
119e2f948a8SEmanuele Giuseppe Esposito                 qmp_timer: Optional[float] = None):
12082e6517dSJohn Snow        # pylint: disable=too-many-arguments
12182e6517dSJohn Snow
122beb6b57bSJohn Snow        if name is None:
123beb6b57bSJohn Snow            name = "qemu-%d" % os.getpid()
124beb6b57bSJohn Snow        if sock_dir is None:
125beb6b57bSJohn Snow            sock_dir = base_temp_dir
126804f7695SEmanuele Giuseppe Esposito        super().__init__(binary, args, wrapper=wrapper, name=name,
127804f7695SEmanuele Giuseppe Esposito                         base_temp_dir=base_temp_dir,
128e2f948a8SEmanuele Giuseppe Esposito                         sock_dir=sock_dir, qmp_timer=qmp_timer)
129beb6b57bSJohn Snow        self._qtest: Optional[QEMUQtestProtocol] = None
130beb6b57bSJohn Snow        self._qtest_path = os.path.join(sock_dir, name + "-qtest.sock")
131beb6b57bSJohn Snow
132beb6b57bSJohn Snow    @property
133beb6b57bSJohn Snow    def _base_args(self) -> List[str]:
134beb6b57bSJohn Snow        args = super()._base_args
135beb6b57bSJohn Snow        args.extend([
136beb6b57bSJohn Snow            '-qtest', f"unix:path={self._qtest_path}",
137beb6b57bSJohn Snow            '-accel', 'qtest'
138beb6b57bSJohn Snow        ])
139beb6b57bSJohn Snow        return args
140beb6b57bSJohn Snow
141beb6b57bSJohn Snow    def _pre_launch(self) -> None:
142beb6b57bSJohn Snow        super()._pre_launch()
143beb6b57bSJohn Snow        self._qtest = QEMUQtestProtocol(self._qtest_path, server=True)
144beb6b57bSJohn Snow
145beb6b57bSJohn Snow    def _post_launch(self) -> None:
146beb6b57bSJohn Snow        assert self._qtest is not None
147beb6b57bSJohn Snow        super()._post_launch()
148beb6b57bSJohn Snow        self._qtest.accept()
149beb6b57bSJohn Snow
150beb6b57bSJohn Snow    def _post_shutdown(self) -> None:
151beb6b57bSJohn Snow        super()._post_shutdown()
152beb6b57bSJohn Snow        self._remove_if_exists(self._qtest_path)
153beb6b57bSJohn Snow
154beb6b57bSJohn Snow    def qtest(self, cmd: str) -> str:
155beb6b57bSJohn Snow        """
156beb6b57bSJohn Snow        Send a qtest command to the guest.
157beb6b57bSJohn Snow
158beb6b57bSJohn Snow        :param cmd: qtest command to send
159beb6b57bSJohn Snow        :return: qtest server response
160beb6b57bSJohn Snow        """
161beb6b57bSJohn Snow        if self._qtest is None:
162beb6b57bSJohn Snow            raise RuntimeError("qtest socket not available")
163beb6b57bSJohn Snow        return self._qtest.cmd(cmd)
164