xref: /openbmc/qemu/python/qemu/machine/qtest.py (revision 46d4747a)
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