Bits and pieces, kibbles'n'bits
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEE+ber27ys35W+dsvQfe+BBqr8OQ4FAmPQlMIACgkQfe+BBqr8
 OQ5RIxAAqaG8Dx63CXa8WHMsGWc0CKTOcwTcRDw92GT3qhVkebZiNmNlZwckaU/c
 CkVunJnU5T6T2qkploysUXwdlQ+XsY4fQlACNciZeffmT2E4siNQ/4H1uPB4xca6
 8Sgmg2VH7OF+EWwuBihY1pbe7g+sOJg9w9isRduBnLGrLbOrewGIJBNbiVzFlz5W
 30RdvfLoUUak5qTlMT/6yl98r6fkkDmfPX653iYmpA/H/Ah+17ZJXB2XNigkqBdD
 Cp8OxtFceKQdZOqNiADJRzT3Gore4lBkPnULKwct/5U0B/tUiBdZ2YDJW8EObUMY
 zFE7giE5mCnyFSmfBmjKu8yS8zJm9NooYEjunTcodop/FDb96c3sh8376ZLamTii
 /p5WSwfo4a6DXPUTx0aiCkqpeCdPncRgwKc5TvqyKLKxQHbfjt6UZrcL6iYbe6O6
 ltBcdvfdzL41TNjS678QqiGuYkADVa/nhig3ano4msx/Tf5e0O8eMoK9bDbVS9KF
 QuONtOcut1YhnAHJp4oYN2Nimtr0t8j07iOOfc4X3+WwdbMCfR+toDM4wWVJ3u/O
 8Phy8hinfndMXdP9Q4eeFAiJ1zuD/XkpaKoDe0gHcEvp3zMEXmHiEOdv4hFeWTQB
 ivU3oM/j2uVcHU4CSxra3B54vfLc1gudJ2yLPvhwPKoIRbJ/kbc=
 =36NA
 -----END PGP SIGNATURE-----

Merge tag 'python-pull-request' of https://gitlab.com/jsnow/qemu into staging

Python

Bits and pieces, kibbles'n'bits

# -----BEGIN PGP SIGNATURE-----
#
# iQIzBAABCAAdFiEE+ber27ys35W+dsvQfe+BBqr8OQ4FAmPQlMIACgkQfe+BBqr8
# OQ5RIxAAqaG8Dx63CXa8WHMsGWc0CKTOcwTcRDw92GT3qhVkebZiNmNlZwckaU/c
# CkVunJnU5T6T2qkploysUXwdlQ+XsY4fQlACNciZeffmT2E4siNQ/4H1uPB4xca6
# 8Sgmg2VH7OF+EWwuBihY1pbe7g+sOJg9w9isRduBnLGrLbOrewGIJBNbiVzFlz5W
# 30RdvfLoUUak5qTlMT/6yl98r6fkkDmfPX653iYmpA/H/Ah+17ZJXB2XNigkqBdD
# Cp8OxtFceKQdZOqNiADJRzT3Gore4lBkPnULKwct/5U0B/tUiBdZ2YDJW8EObUMY
# zFE7giE5mCnyFSmfBmjKu8yS8zJm9NooYEjunTcodop/FDb96c3sh8376ZLamTii
# /p5WSwfo4a6DXPUTx0aiCkqpeCdPncRgwKc5TvqyKLKxQHbfjt6UZrcL6iYbe6O6
# ltBcdvfdzL41TNjS678QqiGuYkADVa/nhig3ano4msx/Tf5e0O8eMoK9bDbVS9KF
# QuONtOcut1YhnAHJp4oYN2Nimtr0t8j07iOOfc4X3+WwdbMCfR+toDM4wWVJ3u/O
# 8Phy8hinfndMXdP9Q4eeFAiJ1zuD/XkpaKoDe0gHcEvp3zMEXmHiEOdv4hFeWTQB
# ivU3oM/j2uVcHU4CSxra3B54vfLc1gudJ2yLPvhwPKoIRbJ/kbc=
# =36NA
# -----END PGP SIGNATURE-----
# gpg: Signature made Wed 25 Jan 2023 02:32:34 GMT
# gpg:                using RSA key F9B7ABDBBCACDF95BE76CBD07DEF8106AAFC390E
# gpg: Good signature from "John Snow (John Huston) <jsnow@redhat.com>" [full]
# Primary key fingerprint: FAEB 9711 A12C F475 812F  18F2 88A9 064D 1835 61EB
#      Subkey fingerprint: F9B7 ABDB BCAC DF95 BE76  CBD0 7DEF 8106 AAFC 390E

* tag 'python-pull-request' of https://gitlab.com/jsnow/qemu:
  python/qemu/machine: use socketpair() for QMP by default
  python/qmp/legacy: make QEMUMonitorProtocol accept a socket
  python/qmp/protocol: add open_with_socket()
  python/qmp: increase read buffer size
  python/machine: Fix AF_UNIX path too long on macOS
  python: QEMUMachine: enable qmp accept timeout by default
  Fix some typos

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2023-02-01 16:15:55 +00:00
commit 026817fb69
8 changed files with 64 additions and 28 deletions

View File

@ -68,7 +68,7 @@ class ConsoleSocket(socket.socket):
"""Kick off a thread to drain the socket."""
# Configure socket to not block and timeout.
# This allows our drain thread to not block
# on recieve and exit smoothly.
# on receive and exit smoothly.
socket.socket.setblocking(self, False)
socket.socket.settimeout(self, 1)
drain_thread = threading.Thread(target=self._drain_fn)

View File

@ -131,7 +131,7 @@ class QEMUMachine:
drain_console: bool = False,
console_log: Optional[str] = None,
log_dir: Optional[str] = None,
qmp_timer: Optional[float] = None):
qmp_timer: Optional[float] = 30):
'''
Initialize a QEMUMachine
@ -157,18 +157,14 @@ class QEMUMachine:
self._wrapper = wrapper
self._qmp_timer = qmp_timer
self._name = name or f"qemu-{os.getpid()}-{id(self):02x}"
self._name = name or f"{id(self):x}"
self._sock_pair: Optional[Tuple[socket.socket, socket.socket]] = None
self._temp_dir: Optional[str] = None
self._base_temp_dir = base_temp_dir
self._sock_dir = sock_dir
self._log_dir = log_dir
if monitor_address is not None:
self._monitor_address = monitor_address
else:
self._monitor_address = os.path.join(
self.sock_dir, f"{self._name}-monitor.sock"
)
self._monitor_address = monitor_address
self._console_log_path = console_log
if self._console_log_path:
@ -192,7 +188,7 @@ class QEMUMachine:
self._console_set = False
self._console_device_type: Optional[str] = None
self._console_address = os.path.join(
self.sock_dir, f"{self._name}-console.sock"
self.sock_dir, f"{self._name}.con"
)
self._console_socket: Optional[socket.socket] = None
self._remove_files: List[str] = []
@ -303,7 +299,11 @@ class QEMUMachine:
args = ['-display', 'none', '-vga', 'none']
if self._qmp_set:
if isinstance(self._monitor_address, tuple):
if self._sock_pair:
fd = self._sock_pair[0].fileno()
os.set_inheritable(fd, True)
moncdev = f"socket,id=mon,fd={fd}"
elif isinstance(self._monitor_address, tuple):
moncdev = "socket,id=mon,host={},port={}".format(
*self._monitor_address
)
@ -337,10 +337,17 @@ class QEMUMachine:
self._remove_files.append(self._console_address)
if self._qmp_set:
monitor_address = None
sock = None
if self._monitor_address is None:
self._sock_pair = socket.socketpair()
sock = self._sock_pair[1]
if isinstance(self._monitor_address, str):
self._remove_files.append(self._monitor_address)
monitor_address = self._monitor_address
self._qmp_connection = QEMUMonitorProtocol(
self._monitor_address,
address=monitor_address,
sock=sock,
server=True,
nickname=self._name
)
@ -360,6 +367,8 @@ class QEMUMachine:
))
def _post_launch(self) -> None:
if self._sock_pair:
self._sock_pair[0].close()
if self._qmp_connection:
self._qmp.accept(self._qmp_timer)

View File

@ -42,7 +42,7 @@ class QEMUQtestProtocol:
:raise socket.error: on socket connection errors
.. note::
No conection is estabalished by __init__(), this is done
No connection is established by __init__(), this is done
by the connect() or accept() methods.
"""
def __init__(self, address: SocketAddrT,

View File

@ -22,6 +22,7 @@ old interface.
#
import asyncio
import socket
from types import TracebackType
from typing import (
Any,
@ -69,22 +70,32 @@ class QEMUMonitorProtocol:
:param address: QEMU address, can be either a unix socket path (string)
or a tuple in the form ( address, port ) for a TCP
connection
connection or None
:param sock: a socket or None
:param server: Act as the socket server. (See 'accept')
:param nickname: Optional nickname used for logging.
"""
def __init__(self, address: SocketAddrT,
def __init__(self,
address: Optional[SocketAddrT] = None,
sock: Optional[socket.socket] = None,
server: bool = False,
nickname: Optional[str] = None):
assert address or sock
self._qmp = QMPClient(nickname)
self._aloop = asyncio.get_event_loop()
self._address = address
self._sock = sock
self._timeout: Optional[float] = None
if server:
self._sync(self._qmp.start_server(self._address))
if sock:
assert self._sock is not None
self._sync(self._qmp.open_with_socket(self._sock))
else:
assert self._address is not None
self._sync(self._qmp.start_server(self._address))
_T = TypeVar('_T')
@ -139,6 +150,7 @@ class QEMUMonitorProtocol:
:return: QMP greeting dict, or None if negotiate is false
:raise ConnectError: on connection errors
"""
assert self._address is not None
self._qmp.await_greeting = negotiate
self._qmp.negotiate = negotiate

View File

@ -18,6 +18,7 @@ from asyncio import StreamReader, StreamWriter
from enum import Enum
from functools import wraps
import logging
import socket
from ssl import SSLContext
from typing import (
Any,
@ -296,6 +297,19 @@ class AsyncProtocol(Generic[T]):
await self.accept()
assert self.runstate == Runstate.RUNNING
@upper_half
@require(Runstate.IDLE)
async def open_with_socket(self, sock: socket.socket) -> None:
"""
Start connection with given socket.
:param sock: A socket.
:raise StateError: When the `Runstate` is not `IDLE`.
"""
self._reader, self._writer = await asyncio.open_connection(sock=sock)
self._set_state(Runstate.CONNECTING)
@upper_half
@require(Runstate.IDLE)
async def start_server(self, address: SocketAddrT,
@ -343,11 +357,12 @@ class AsyncProtocol(Generic[T]):
protocol-level failure occurs while establishing a new
session, the wrapped error may also be an `QMPError`.
"""
if self._accepted is None:
raise QMPError("Cannot call accept() before start_server().")
await self._session_guard(
self._do_accept(),
'Failed to establish connection')
if not self._reader:
if self._accepted is None:
raise QMPError("Cannot call accept() before start_server().")
await self._session_guard(
self._do_accept(),
'Failed to establish connection')
await self._session_guard(
self._establish_session(),
'Failed to establish session')
@ -812,7 +827,7 @@ class AsyncProtocol(Generic[T]):
@bottom_half
async def _bh_close_stream(self, error_pathway: bool = False) -> None:
# NB: Closing the writer also implcitly closes the reader.
# NB: Closing the writer also implicitly closes the reader.
if not self._writer:
return

View File

@ -197,8 +197,8 @@ class QMPClient(AsyncProtocol[Message], Events):
#: Logger object used for debugging messages.
logger = logging.getLogger(__name__)
# Read buffer limit; large enough to accept query-qmp-schema
_limit = (256 * 1024)
# Read buffer limit; 10MB like libvirt default
_limit = (10 * 1024 * 1024)
# Type alias for pending execute() result items
_PendingT = Union[Message, ExecInterruptedError]

View File

@ -71,7 +71,7 @@ def format_json(msg: str) -> str:
due to an decoding error then a simple string manipulation is done to
achieve a single line JSON string.
Converting into single line is more asthetically pleasing when looking
Converting into single line is more aesthetically pleasing when looking
along with error messages.
Eg:
@ -91,7 +91,7 @@ def format_json(msg: str) -> str:
[1, true, 3]: QMP message is not a JSON object.
The single line mode is more asthetically pleasing.
The single line mode is more aesthetically pleasing.
:param msg:
The message to formatted into single line.
@ -498,7 +498,7 @@ class EditorWidget(urwid.Filler):
class HistoryBox(urwid.ListBox):
"""
This widget is modelled using the ListBox widget, contains the list of
all messages both QMP messages and log messsages to be shown in the TUI.
all messages both QMP messages and log messages to be shown in the TUI.
The messages are urwid.Text widgets. On every append of a message, the
focus is shifted to the last appended message.

View File

@ -306,7 +306,7 @@ class QemuSystemTest(QemuBaseTest):
self.cancel('no support for user networking')
def _new_vm(self, name, *args):
self._sd = tempfile.TemporaryDirectory(prefix="avo_qemu_sock_")
self._sd = tempfile.TemporaryDirectory(prefix="qemu_")
vm = QEMUMachine(self.qemu_bin, base_temp_dir=self.workdir,
sock_dir=self._sd.name, log_dir=self.logdir)
self.log.debug('QEMUMachine "%s" created', name)