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