diff options
author | Peter Maydell <peter.maydell@linaro.org> | 2022-02-03 15:42:28 +0000 |
---|---|---|
committer | Peter Maydell <peter.maydell@linaro.org> | 2022-02-03 15:42:28 +0000 |
commit | 31f59af395922b7f40799e75db6e15ff52d8f94a (patch) | |
tree | 3dad1f53308b49c39d898334ff59d3cea58d3855 /python/qemu | |
parent | 8f3e5ce773c62bb5c4a847f3a9a5c98bbb3b359f (diff) | |
parent | b0b662bb2b340d63529672b5bdae596a6243c4d0 (diff) | |
download | qemu-31f59af395922b7f40799e75db6e15ff52d8f94a.zip qemu-31f59af395922b7f40799e75db6e15ff52d8f94a.tar.gz qemu-31f59af395922b7f40799e75db6e15ff52d8f94a.tar.bz2 |
Merge remote-tracking branch 'remotes/jsnow-gitlab/tags/python-pull-request' into staging
Python patches
Peter: I expect this to address the iotest 040,041 failures you observed
on NetBSD. If it doesn't, let me know.
# gpg: Signature made Thu 03 Feb 2022 01:59:32 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
* remotes/jsnow-gitlab/tags/python-pull-request:
python/aqmp: add socket bind step to legacy.py
python: upgrade mypy to 0.780
python/machine: raise VMLaunchFailure exception from launch()
python/aqmp: Fix negotiation with pre-"oob" QEMU
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'python/qemu')
-rw-r--r-- | python/qemu/aqmp/legacy.py | 3 | ||||
-rw-r--r-- | python/qemu/aqmp/protocol.py | 41 | ||||
-rw-r--r-- | python/qemu/aqmp/qmp_client.py | 4 | ||||
-rw-r--r-- | python/qemu/machine/machine.py | 45 |
4 files changed, 82 insertions, 11 deletions
diff --git a/python/qemu/aqmp/legacy.py b/python/qemu/aqmp/legacy.py index 0890f95..6baa5f3 100644 --- a/python/qemu/aqmp/legacy.py +++ b/python/qemu/aqmp/legacy.py @@ -56,6 +56,9 @@ class QEMUMonitorProtocol(qemu.qmp.QEMUMonitorProtocol): self._address = address self._timeout: Optional[float] = None + if server: + self._aqmp._bind_hack(address) # pylint: disable=protected-access + _T = TypeVar('_T') def _sync( diff --git a/python/qemu/aqmp/protocol.py b/python/qemu/aqmp/protocol.py index 50e973c..33358f5 100644 --- a/python/qemu/aqmp/protocol.py +++ b/python/qemu/aqmp/protocol.py @@ -15,6 +15,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, @@ -238,6 +239,9 @@ class AsyncProtocol(Generic[T]): self._runstate = Runstate.IDLE self._runstate_changed: Optional[asyncio.Event] = None + # Workaround for bind() + self._sock: Optional[socket.socket] = None + def __repr__(self) -> str: cls_name = type(self).__name__ tokens = [] @@ -427,6 +431,34 @@ class AsyncProtocol(Generic[T]): else: await self._do_connect(address, ssl) + def _bind_hack(self, address: Union[str, Tuple[str, int]]) -> None: + """ + Used to create a socket in advance of accept(). + + This is a workaround to ensure that we can guarantee timing of + precisely when a socket exists to avoid a connection attempt + bouncing off of nothing. + + Python 3.7+ adds a feature to separate the server creation and + listening phases instead, and should be used instead of this + hack. + """ + if isinstance(address, tuple): + family = socket.AF_INET + else: + family = socket.AF_UNIX + + sock = socket.socket(family, socket.SOCK_STREAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + + try: + sock.bind(address) + except: + sock.close() + raise + + self._sock = sock + @upper_half async def _do_accept(self, address: SocketAddrT, ssl: Optional[SSLContext] = None) -> None: @@ -464,24 +496,27 @@ class AsyncProtocol(Generic[T]): if isinstance(address, tuple): coro = asyncio.start_server( _client_connected_cb, - host=address[0], - port=address[1], + host=None if self._sock else address[0], + port=None if self._sock else address[1], ssl=ssl, backlog=1, limit=self._limit, + sock=self._sock, ) else: coro = asyncio.start_unix_server( _client_connected_cb, - path=address, + path=None if self._sock else address, ssl=ssl, backlog=1, limit=self._limit, + sock=self._sock, ) server = await coro # Starts listening await connected.wait() # Waits for the callback to fire (and finish) assert server is None + self._sock = None self.logger.debug("Connection accepted.") diff --git a/python/qemu/aqmp/qmp_client.py b/python/qemu/aqmp/qmp_client.py index f1a845c..90a8737 100644 --- a/python/qemu/aqmp/qmp_client.py +++ b/python/qemu/aqmp/qmp_client.py @@ -292,9 +292,9 @@ class QMPClient(AsyncProtocol[Message], Events): """ self.logger.debug("Negotiating capabilities ...") - arguments: Dict[str, List[str]] = {'enable': []} + arguments: Dict[str, List[str]] = {} if self._greeting and 'oob' in self._greeting.QMP.capabilities: - arguments['enable'].append('oob') + arguments.setdefault('enable', []).append('oob') msg = self.make_execute_msg('qmp_capabilities', arguments=arguments) # It's not safe to use execute() here, because the reader/writers diff --git a/python/qemu/machine/machine.py b/python/qemu/machine/machine.py index 67ab06c..a5972fa 100644 --- a/python/qemu/machine/machine.py +++ b/python/qemu/machine/machine.py @@ -74,6 +74,35 @@ class QEMUMachineAddDeviceError(QEMUMachineError): """ +class VMLaunchFailure(QEMUMachineError): + """ + Exception raised when a VM launch was attempted, but failed. + """ + def __init__(self, exitcode: Optional[int], + command: str, output: Optional[str]): + super().__init__(exitcode, command, output) + self.exitcode = exitcode + self.command = command + self.output = output + + def __str__(self) -> str: + ret = '' + if self.__cause__ is not None: + name = type(self.__cause__).__name__ + reason = str(self.__cause__) + if reason: + ret += f"{name}: {reason}" + else: + ret += f"{name}" + ret += '\n' + + if self.exitcode is not None: + ret += f"\tExit code: {self.exitcode}\n" + ret += f"\tCommand: {self.command}\n" + ret += f"\tOutput: {self.output}\n" + return ret + + class AbnormalShutdown(QEMUMachineError): """ Exception raised when a graceful shutdown was requested, but not performed. @@ -397,7 +426,7 @@ class QEMUMachine: try: self._launch() - except: + except BaseException as exc: # We may have launched the process but it may # have exited before we could connect via QMP. # Assume the VM didn't launch or is exiting. @@ -408,11 +437,15 @@ class QEMUMachine: else: self._post_shutdown() - LOG.debug('Error launching VM') - if self._qemu_full_args: - LOG.debug('Command: %r', ' '.join(self._qemu_full_args)) - if self._iolog: - LOG.debug('Output: %r', self._iolog) + if isinstance(exc, Exception): + raise VMLaunchFailure( + exitcode=self.exitcode(), + command=' '.join(self._qemu_full_args), + output=self._iolog + ) from exc + + # Don't wrap 'BaseException'; doing so would downgrade + # that exception. However, we still want to clean up. raise def _launch(self) -> None: |