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----- 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:
commit
026817fb69
|
@ -68,7 +68,7 @@ class ConsoleSocket(socket.socket):
|
||||||
"""Kick off a thread to drain the socket."""
|
"""Kick off a thread to drain the socket."""
|
||||||
# Configure socket to not block and timeout.
|
# Configure socket to not block and timeout.
|
||||||
# This allows our drain thread to not block
|
# 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.setblocking(self, False)
|
||||||
socket.socket.settimeout(self, 1)
|
socket.socket.settimeout(self, 1)
|
||||||
drain_thread = threading.Thread(target=self._drain_fn)
|
drain_thread = threading.Thread(target=self._drain_fn)
|
||||||
|
|
|
@ -131,7 +131,7 @@ class QEMUMachine:
|
||||||
drain_console: bool = False,
|
drain_console: bool = False,
|
||||||
console_log: Optional[str] = None,
|
console_log: Optional[str] = None,
|
||||||
log_dir: Optional[str] = None,
|
log_dir: Optional[str] = None,
|
||||||
qmp_timer: Optional[float] = None):
|
qmp_timer: Optional[float] = 30):
|
||||||
'''
|
'''
|
||||||
Initialize a QEMUMachine
|
Initialize a QEMUMachine
|
||||||
|
|
||||||
|
@ -157,18 +157,14 @@ class QEMUMachine:
|
||||||
self._wrapper = wrapper
|
self._wrapper = wrapper
|
||||||
self._qmp_timer = qmp_timer
|
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._temp_dir: Optional[str] = None
|
||||||
self._base_temp_dir = base_temp_dir
|
self._base_temp_dir = base_temp_dir
|
||||||
self._sock_dir = sock_dir
|
self._sock_dir = sock_dir
|
||||||
self._log_dir = log_dir
|
self._log_dir = log_dir
|
||||||
|
|
||||||
if monitor_address is not None:
|
self._monitor_address = monitor_address
|
||||||
self._monitor_address = monitor_address
|
|
||||||
else:
|
|
||||||
self._monitor_address = os.path.join(
|
|
||||||
self.sock_dir, f"{self._name}-monitor.sock"
|
|
||||||
)
|
|
||||||
|
|
||||||
self._console_log_path = console_log
|
self._console_log_path = console_log
|
||||||
if self._console_log_path:
|
if self._console_log_path:
|
||||||
|
@ -192,7 +188,7 @@ class QEMUMachine:
|
||||||
self._console_set = False
|
self._console_set = False
|
||||||
self._console_device_type: Optional[str] = None
|
self._console_device_type: Optional[str] = None
|
||||||
self._console_address = os.path.join(
|
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._console_socket: Optional[socket.socket] = None
|
||||||
self._remove_files: List[str] = []
|
self._remove_files: List[str] = []
|
||||||
|
@ -303,7 +299,11 @@ class QEMUMachine:
|
||||||
args = ['-display', 'none', '-vga', 'none']
|
args = ['-display', 'none', '-vga', 'none']
|
||||||
|
|
||||||
if self._qmp_set:
|
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(
|
moncdev = "socket,id=mon,host={},port={}".format(
|
||||||
*self._monitor_address
|
*self._monitor_address
|
||||||
)
|
)
|
||||||
|
@ -337,10 +337,17 @@ class QEMUMachine:
|
||||||
self._remove_files.append(self._console_address)
|
self._remove_files.append(self._console_address)
|
||||||
|
|
||||||
if self._qmp_set:
|
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):
|
if isinstance(self._monitor_address, str):
|
||||||
self._remove_files.append(self._monitor_address)
|
self._remove_files.append(self._monitor_address)
|
||||||
|
monitor_address = self._monitor_address
|
||||||
self._qmp_connection = QEMUMonitorProtocol(
|
self._qmp_connection = QEMUMonitorProtocol(
|
||||||
self._monitor_address,
|
address=monitor_address,
|
||||||
|
sock=sock,
|
||||||
server=True,
|
server=True,
|
||||||
nickname=self._name
|
nickname=self._name
|
||||||
)
|
)
|
||||||
|
@ -360,6 +367,8 @@ class QEMUMachine:
|
||||||
))
|
))
|
||||||
|
|
||||||
def _post_launch(self) -> None:
|
def _post_launch(self) -> None:
|
||||||
|
if self._sock_pair:
|
||||||
|
self._sock_pair[0].close()
|
||||||
if self._qmp_connection:
|
if self._qmp_connection:
|
||||||
self._qmp.accept(self._qmp_timer)
|
self._qmp.accept(self._qmp_timer)
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ class QEMUQtestProtocol:
|
||||||
:raise socket.error: on socket connection errors
|
:raise socket.error: on socket connection errors
|
||||||
|
|
||||||
.. note::
|
.. 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.
|
by the connect() or accept() methods.
|
||||||
"""
|
"""
|
||||||
def __init__(self, address: SocketAddrT,
|
def __init__(self, address: SocketAddrT,
|
||||||
|
|
|
@ -22,6 +22,7 @@ old interface.
|
||||||
#
|
#
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import socket
|
||||||
from types import TracebackType
|
from types import TracebackType
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
|
@ -69,22 +70,32 @@ class QEMUMonitorProtocol:
|
||||||
|
|
||||||
:param address: QEMU address, can be either a unix socket path (string)
|
:param address: QEMU address, can be either a unix socket path (string)
|
||||||
or a tuple in the form ( address, port ) for a TCP
|
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 server: Act as the socket server. (See 'accept')
|
||||||
:param nickname: Optional nickname used for logging.
|
: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,
|
server: bool = False,
|
||||||
nickname: Optional[str] = None):
|
nickname: Optional[str] = None):
|
||||||
|
|
||||||
|
assert address or sock
|
||||||
self._qmp = QMPClient(nickname)
|
self._qmp = QMPClient(nickname)
|
||||||
self._aloop = asyncio.get_event_loop()
|
self._aloop = asyncio.get_event_loop()
|
||||||
self._address = address
|
self._address = address
|
||||||
|
self._sock = sock
|
||||||
self._timeout: Optional[float] = None
|
self._timeout: Optional[float] = None
|
||||||
|
|
||||||
if server:
|
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')
|
_T = TypeVar('_T')
|
||||||
|
|
||||||
|
@ -139,6 +150,7 @@ class QEMUMonitorProtocol:
|
||||||
:return: QMP greeting dict, or None if negotiate is false
|
:return: QMP greeting dict, or None if negotiate is false
|
||||||
:raise ConnectError: on connection errors
|
:raise ConnectError: on connection errors
|
||||||
"""
|
"""
|
||||||
|
assert self._address is not None
|
||||||
self._qmp.await_greeting = negotiate
|
self._qmp.await_greeting = negotiate
|
||||||
self._qmp.negotiate = negotiate
|
self._qmp.negotiate = negotiate
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ from asyncio import StreamReader, StreamWriter
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
import logging
|
import logging
|
||||||
|
import socket
|
||||||
from ssl import SSLContext
|
from ssl import SSLContext
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
|
@ -296,6 +297,19 @@ class AsyncProtocol(Generic[T]):
|
||||||
await self.accept()
|
await self.accept()
|
||||||
assert self.runstate == Runstate.RUNNING
|
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
|
@upper_half
|
||||||
@require(Runstate.IDLE)
|
@require(Runstate.IDLE)
|
||||||
async def start_server(self, address: SocketAddrT,
|
async def start_server(self, address: SocketAddrT,
|
||||||
|
@ -343,11 +357,12 @@ class AsyncProtocol(Generic[T]):
|
||||||
protocol-level failure occurs while establishing a new
|
protocol-level failure occurs while establishing a new
|
||||||
session, the wrapped error may also be an `QMPError`.
|
session, the wrapped error may also be an `QMPError`.
|
||||||
"""
|
"""
|
||||||
if self._accepted is None:
|
if not self._reader:
|
||||||
raise QMPError("Cannot call accept() before start_server().")
|
if self._accepted is None:
|
||||||
await self._session_guard(
|
raise QMPError("Cannot call accept() before start_server().")
|
||||||
self._do_accept(),
|
await self._session_guard(
|
||||||
'Failed to establish connection')
|
self._do_accept(),
|
||||||
|
'Failed to establish connection')
|
||||||
await self._session_guard(
|
await self._session_guard(
|
||||||
self._establish_session(),
|
self._establish_session(),
|
||||||
'Failed to establish session')
|
'Failed to establish session')
|
||||||
|
@ -812,7 +827,7 @@ class AsyncProtocol(Generic[T]):
|
||||||
|
|
||||||
@bottom_half
|
@bottom_half
|
||||||
async def _bh_close_stream(self, error_pathway: bool = False) -> None:
|
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:
|
if not self._writer:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -197,8 +197,8 @@ class QMPClient(AsyncProtocol[Message], Events):
|
||||||
#: Logger object used for debugging messages.
|
#: Logger object used for debugging messages.
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Read buffer limit; large enough to accept query-qmp-schema
|
# Read buffer limit; 10MB like libvirt default
|
||||||
_limit = (256 * 1024)
|
_limit = (10 * 1024 * 1024)
|
||||||
|
|
||||||
# Type alias for pending execute() result items
|
# Type alias for pending execute() result items
|
||||||
_PendingT = Union[Message, ExecInterruptedError]
|
_PendingT = Union[Message, ExecInterruptedError]
|
||||||
|
|
|
@ -71,7 +71,7 @@ def format_json(msg: str) -> str:
|
||||||
due to an decoding error then a simple string manipulation is done to
|
due to an decoding error then a simple string manipulation is done to
|
||||||
achieve a single line JSON string.
|
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.
|
along with error messages.
|
||||||
|
|
||||||
Eg:
|
Eg:
|
||||||
|
@ -91,7 +91,7 @@ def format_json(msg: str) -> str:
|
||||||
|
|
||||||
[1, true, 3]: QMP message is not a JSON object.
|
[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:
|
:param msg:
|
||||||
The message to formatted into single line.
|
The message to formatted into single line.
|
||||||
|
@ -498,7 +498,7 @@ class EditorWidget(urwid.Filler):
|
||||||
class HistoryBox(urwid.ListBox):
|
class HistoryBox(urwid.ListBox):
|
||||||
"""
|
"""
|
||||||
This widget is modelled using the ListBox widget, contains the list of
|
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
|
The messages are urwid.Text widgets. On every append of a message, the
|
||||||
focus is shifted to the last appended message.
|
focus is shifted to the last appended message.
|
||||||
|
|
|
@ -306,7 +306,7 @@ class QemuSystemTest(QemuBaseTest):
|
||||||
self.cancel('no support for user networking')
|
self.cancel('no support for user networking')
|
||||||
|
|
||||||
def _new_vm(self, name, *args):
|
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,
|
vm = QEMUMachine(self.qemu_bin, base_temp_dir=self.workdir,
|
||||||
sock_dir=self._sd.name, log_dir=self.logdir)
|
sock_dir=self._sd.name, log_dir=self.logdir)
|
||||||
self.log.debug('QEMUMachine "%s" created', name)
|
self.log.debug('QEMUMachine "%s" created', name)
|
||||||
|
|
Loading…
Reference in New Issue