]> git.proxmox.com Git - mirror_qemu.git/blame - python/qemu/machine/qtest.py
python/machine: use socketpair() for qtest connection
[mirror_qemu.git] / python / qemu / machine / qtest.py
CommitLineData
9b8ccd6d
JS
1"""
2QEMU qtest library
3
4qtest offers the QEMUQtestProtocol and QEMUQTestMachine classes, which
5offer a connection to QEMU's qtest protocol socket, and a qtest-enabled
6subclass of QEMUMachine, respectively.
7"""
8
a628daa4
FZ
9# Copyright (C) 2015 Red Hat Inc.
10#
11# Authors:
12# Fam Zheng <famz@redhat.com>
13#
14# This work is licensed under the terms of the GNU GPL, version 2. See
15# the COPYING file in the top-level directory.
16#
17# Based on qmp.py.
18#
19
66613974 20import os
932ca4bb 21import socket
aad3f3bb
JS
22from typing import (
23 List,
24 Optional,
25 Sequence,
26 TextIO,
d3967378 27 Tuple,
aad3f3bb 28)
8f8fd9ed 29
37094b6d 30from qemu.qmp import SocketAddrT
beb6b57b 31
abf0bf99 32from .machine import QEMUMachine
a628daa4 33
4d934297 34
9b8ccd6d
JS
35class QEMUQtestProtocol:
36 """
37 QEMUQtestProtocol implements a connection to a qtest socket.
a628daa4 38
9b8ccd6d
JS
39 :param address: QEMU address, can be either a unix socket path (string)
40 or a tuple in the form ( address, port ) for a TCP
41 connection
d3967378
JS
42 :param sock: An existing socket can be provided as an alternative to
43 an address. One of address or sock must be provided.
44 :param server: server mode, listens on the socket. Only meaningful
45 in conjunction with an address and not an existing
46 socket.
47
9b8ccd6d
JS
48 :raise socket.error: on socket connection errors
49
50 .. note::
af76484e 51 No connection is established by __init__(), this is done
9b8ccd6d
JS
52 by the connect() or accept() methods.
53 """
d3967378
JS
54 def __init__(self,
55 address: Optional[SocketAddrT] = None,
56 sock: Optional[socket.socket] = None,
f12a282f 57 server: bool = False):
d3967378
JS
58 if address is None and sock is None:
59 raise ValueError("Either 'address' or 'sock' must be specified")
60 if address is not None and sock is not None:
61 raise ValueError(
62 "Either 'address' or 'sock' must be specified, but not both")
63 if sock is not None and server:
64 raise ValueError("server=True is meaningless when passing socket")
65
a628daa4 66 self._address = address
d3967378 67 self._sock = sock or self._get_sock()
0add048f 68 self._sockfile: Optional[TextIO] = None
d3967378 69
a628daa4 70 if server:
d3967378 71 assert self._address is not None
a628daa4
FZ
72 self._sock.bind(self._address)
73 self._sock.listen(1)
74
f12a282f 75 def _get_sock(self) -> socket.socket:
d3967378 76 assert self._address is not None
a628daa4
FZ
77 if isinstance(self._address, tuple):
78 family = socket.AF_INET
79 else:
80 family = socket.AF_UNIX
81 return socket.socket(family, socket.SOCK_STREAM)
82
f12a282f 83 def connect(self) -> None:
a628daa4
FZ
84 """
85 Connect to the qtest socket.
86
87 @raise socket.error on socket connection errors
88 """
d3967378
JS
89 if self._address is not None:
90 self._sock.connect(self._address)
0add048f 91 self._sockfile = self._sock.makefile(mode='r')
a628daa4 92
f12a282f 93 def accept(self) -> None:
a628daa4
FZ
94 """
95 Await connection from QEMU.
96
97 @raise socket.error on socket connection errors
98 """
99 self._sock, _ = self._sock.accept()
0add048f 100 self._sockfile = self._sock.makefile(mode='r')
a628daa4 101
f12a282f 102 def cmd(self, qtest_cmd: str) -> str:
a628daa4
FZ
103 """
104 Send a qtest command on the wire.
105
106 @param qtest_cmd: qtest command text to be sent
107 """
0add048f 108 assert self._sockfile is not None
8eb5e674 109 self._sock.sendall((qtest_cmd + "\n").encode('utf-8'))
a5df73ba
AG
110 resp = self._sockfile.readline()
111 return resp
a628daa4 112
f12a282f
JS
113 def close(self) -> None:
114 """
115 Close this socket.
116 """
a628daa4 117 self._sock.close()
0add048f
JS
118 if self._sockfile:
119 self._sockfile.close()
120 self._sockfile = None
a628daa4 121
f12a282f 122 def settimeout(self, timeout: Optional[float]) -> None:
9b8ccd6d 123 """Set a timeout, in seconds."""
a628daa4 124 self._sock.settimeout(timeout)
66613974
DB
125
126
8f8fd9ed 127class QEMUQtestMachine(QEMUMachine):
9b8ccd6d
JS
128 """
129 A QEMU VM, with a qtest socket available.
130 """
66613974 131
aad3f3bb
JS
132 def __init__(self,
133 binary: str,
134 args: Sequence[str] = (),
804f7695 135 wrapper: Sequence[str] = (),
aad3f3bb 136 name: Optional[str] = None,
2ca6e26c 137 base_temp_dir: str = "/var/tmp",
e2f948a8
EGE
138 sock_dir: Optional[str] = None,
139 qmp_timer: Optional[float] = None):
82e6517d
JS
140 # pylint: disable=too-many-arguments
141
4c44b4a4
DB
142 if name is None:
143 name = "qemu-%d" % os.getpid()
32558ce7 144 if sock_dir is None:
2ca6e26c 145 sock_dir = base_temp_dir
804f7695
EGE
146 super().__init__(binary, args, wrapper=wrapper, name=name,
147 base_temp_dir=base_temp_dir,
e2f948a8 148 sock_dir=sock_dir, qmp_timer=qmp_timer)
f12a282f 149 self._qtest: Optional[QEMUQtestProtocol] = None
d3967378
JS
150 self._qtest_sock_pair: Optional[
151 Tuple[socket.socket, socket.socket]] = None
66613974 152
652809df
JS
153 @property
154 def _base_args(self) -> List[str]:
155 args = super()._base_args
d3967378
JS
156 assert self._qtest_sock_pair is not None
157 fd = self._qtest_sock_pair[0].fileno()
aad3f3bb 158 args.extend([
d3967378
JS
159 '-chardev', f"socket,id=qtest,fd={fd}",
160 '-qtest', 'chardev:qtest',
aad3f3bb
JS
161 '-accel', 'qtest'
162 ])
66613974
DB
163 return args
164
f12a282f 165 def _pre_launch(self) -> None:
d3967378
JS
166 self._qtest_sock_pair = socket.socketpair()
167 os.set_inheritable(self._qtest_sock_pair[0].fileno(), True)
3797dbcb 168 super()._pre_launch()
d3967378 169 self._qtest = QEMUQtestProtocol(sock=self._qtest_sock_pair[1])
66613974 170
c95dddce
JS
171 def _post_launch(self) -> None:
172 assert self._qtest is not None
3797dbcb 173 super()._post_launch()
d3967378
JS
174 if self._qtest_sock_pair:
175 self._qtest_sock_pair[0].close()
176 self._qtest.connect()
66613974 177
f12a282f 178 def _post_shutdown(self) -> None:
d3967378
JS
179 if self._qtest_sock_pair:
180 self._qtest_sock_pair[0].close()
181 self._qtest_sock_pair[1].close()
182 self._qtest_sock_pair = None
3797dbcb 183 super()._post_shutdown()
66613974 184
c95dddce
JS
185 def qtest(self, cmd: str) -> str:
186 """
187 Send a qtest command to the guest.
188
189 :param cmd: qtest command to send
190 :return: qtest server response
191 """
192 if self._qtest is None:
193 raise RuntimeError("qtest socket not available")
66613974 194 return self._qtest.cmd(cmd)