Skip to content

Commit aeb0ae9

Browse files
committed
Merge remote-tracking branch 'remotes/jsnow-gitlab/tags/python-pull-request' into staging
Python patches A few fixes to the Python CI tests, a few fixes to the (async) QMP library, and a set of patches that begin to shift us towards using the new qmp lib. # gpg: Signature made Sat 22 Jan 2022 00:07:58 GMT # gpg: using RSA key F9B7ABDBBCACDF95BE76CBD07DEF8106AAFC390E # gpg: Good signature from "John Snow (John Huston) <[email protected]>" [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: scripts/render-block-graph: switch to AQMP scripts/cpu-x86-uarch-abi: switch to AQMP scripts/cpu-x86-uarch-abi: fix CLI parsing python: move qmp-shell under the AQMP package python: move qmp utilities to python/qemu/utils python/qmp: switch qmp-shell to AQMP python/qmp: switch qom tools to AQMP python/qmp: switch qemu-ga-client to AQMP python/qemu-ga-client: don't use deprecated CLI syntax in usage comment python/aqmp: rename AQMPError to QMPError python/aqmp: add SocketAddrT to package root python/aqmp: copy type definitions from qmp python/aqmp: handle asyncio.TimeoutError on execute() python/aqmp: add __del__ method to legacy interface python/aqmp: fix docstring typo python: use avocado's "new" runner python: pin setuptools below v60.0.0 Signed-off-by: Peter Maydell <[email protected]>
2 parents 5e9d14f + 0590860 commit aeb0ae9

24 files changed

+151
-90
lines changed

python/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ $(QEMU_VENV_DIR) $(QEMU_VENV_DIR)/bin/activate: setup.cfg
6868
echo "ACTIVATE $(QEMU_VENV_DIR)"; \
6969
. $(QEMU_VENV_DIR)/bin/activate; \
7070
echo "INSTALL qemu[devel] $(QEMU_VENV_DIR)"; \
71+
pip install --disable-pip-version-check \
72+
"setuptools<60.0.0" 1>/dev/null; \
7173
make develop 1>/dev/null; \
7274
)
7375
@touch $(QEMU_VENV_DIR)

python/README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ Package installation also normally provides executable console scripts,
5959
so that tools like ``qmp-shell`` are always available via $PATH. To
6060
invoke them without installation, you can invoke e.g.:
6161

62-
``> PYTHONPATH=~/src/qemu/python python3 -m qemu.qmp.qmp_shell``
62+
``> PYTHONPATH=~/src/qemu/python python3 -m qemu.aqmp.qmp_shell``
6363

6464
The mappings between console script name and python module path can be
6565
found in ``setup.cfg``.

python/avocado.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[run]
2-
test_runner = runner
2+
test_runner = nrunner
33

44
[simpletests]
55
# Don't show stdout/stderr in the test *summary*

python/qemu/aqmp/__init__.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
QEMU Guest Agent, and the QEMU Storage Daemon.
77
88
`QMPClient` provides the main functionality of this package. All errors
9-
raised by this library dervive from `AQMPError`, see `aqmp.error` for
9+
raised by this library derive from `QMPError`, see `aqmp.error` for
1010
additional detail. See `aqmp.events` for an in-depth tutorial on
1111
managing QMP events.
1212
"""
@@ -23,10 +23,15 @@
2323

2424
import logging
2525

26-
from .error import AQMPError
26+
from .error import QMPError
2727
from .events import EventListener
2828
from .message import Message
29-
from .protocol import ConnectError, Runstate, StateError
29+
from .protocol import (
30+
ConnectError,
31+
Runstate,
32+
SocketAddrT,
33+
StateError,
34+
)
3035
from .qmp_client import ExecInterruptedError, ExecuteError, QMPClient
3136

3237

@@ -43,9 +48,12 @@
4348
'Runstate',
4449

4550
# Exceptions, most generic to most explicit
46-
'AQMPError',
51+
'QMPError',
4752
'StateError',
4853
'ConnectError',
4954
'ExecuteError',
5055
'ExecInterruptedError',
56+
57+
# Type aliases
58+
'SocketAddrT',
5159
)

python/qemu/aqmp/error.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
"""
2-
AQMP Error Classes
2+
QMP Error Classes
33
44
This package seeks to provide semantic error classes that are intended
55
to be used directly by clients when they would like to handle particular
66
semantic failures (e.g. "failed to connect") without needing to know the
77
enumeration of possible reasons for that failure.
88
9-
AQMPError serves as the ancestor for all exceptions raised by this
9+
QMPError serves as the ancestor for all exceptions raised by this
1010
package, and is suitable for use in handling semantic errors from this
1111
library. In most cases, individual public methods will attempt to catch
1212
and re-encapsulate various exceptions to provide a semantic
1313
error-handling interface.
1414
15-
.. admonition:: AQMP Exception Hierarchy Reference
15+
.. admonition:: QMP Exception Hierarchy Reference
1616
1717
| `Exception`
18-
| +-- `AQMPError`
18+
| +-- `QMPError`
1919
| +-- `ConnectError`
2020
| +-- `StateError`
2121
| +-- `ExecInterruptedError`
@@ -31,11 +31,11 @@
3131
"""
3232

3333

34-
class AQMPError(Exception):
34+
class QMPError(Exception):
3535
"""Abstract error class for all errors originating from this package."""
3636

3737

38-
class ProtocolError(AQMPError):
38+
class ProtocolError(QMPError):
3939
"""
4040
Abstract error class for protocol failures.
4141

python/qemu/aqmp/events.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -443,15 +443,15 @@ def accept(self, event) -> bool:
443443
Union,
444444
)
445445

446-
from .error import AQMPError
446+
from .error import QMPError
447447
from .message import Message
448448

449449

450450
EventNames = Union[str, Iterable[str], None]
451451
EventFilter = Callable[[Message], bool]
452452

453453

454-
class ListenerError(AQMPError):
454+
class ListenerError(QMPError):
455455
"""
456456
Generic error class for `EventListener`-related problems.
457457
"""

python/qemu/aqmp/legacy.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,42 @@
66

77
import asyncio
88
from typing import (
9+
Any,
910
Awaitable,
11+
Dict,
1012
List,
1113
Optional,
1214
TypeVar,
1315
Union,
1416
)
1517

1618
import qemu.qmp
17-
from qemu.qmp import QMPMessage, QMPReturnValue, SocketAddrT
1819

20+
from .error import QMPError
21+
from .protocol import Runstate, SocketAddrT
1922
from .qmp_client import QMPClient
2023

2124

25+
# (Temporarily) Re-export QMPBadPortError
26+
QMPBadPortError = qemu.qmp.QMPBadPortError
27+
28+
#: QMPMessage is an entire QMP message of any kind.
29+
QMPMessage = Dict[str, Any]
30+
31+
#: QMPReturnValue is the 'return' value of a command.
32+
QMPReturnValue = object
33+
34+
#: QMPObject is any object in a QMP message.
35+
QMPObject = Dict[str, object]
36+
37+
# QMPMessage can be outgoing commands or incoming events/returns.
38+
# QMPReturnValue is usually a dict/json object, but due to QAPI's
39+
# 'returns-whitelist', it can actually be anything.
40+
#
41+
# {'return': {}} is a QMPMessage,
42+
# {} is the QMPReturnValue.
43+
44+
2245
# pylint: disable=missing-docstring
2346

2447

@@ -136,3 +159,19 @@ def settimeout(self, timeout: Optional[float]) -> None:
136159

137160
def send_fd_scm(self, fd: int) -> None:
138161
self._aqmp.send_fd_scm(fd)
162+
163+
def __del__(self) -> None:
164+
if self._aqmp.runstate == Runstate.IDLE:
165+
return
166+
167+
if not self._aloop.is_running():
168+
self.close()
169+
else:
170+
# Garbage collection ran while the event loop was running.
171+
# Nothing we can do about it now, but if we don't raise our
172+
# own error, the user will be treated to a lot of traceback
173+
# they might not understand.
174+
raise QMPError(
175+
"QEMUMonitorProtocol.close()"
176+
" was not called before object was garbage collected"
177+
)

python/qemu/aqmp/protocol.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
cast,
3030
)
3131

32-
from .error import AQMPError
32+
from .error import QMPError
3333
from .util import (
3434
bottom_half,
3535
create_task,
@@ -46,6 +46,10 @@
4646
_U = TypeVar('_U')
4747
_TaskFN = Callable[[], Awaitable[None]] # aka ``async def func() -> None``
4848

49+
InternetAddrT = Tuple[str, int]
50+
UnixAddrT = str
51+
SocketAddrT = Union[UnixAddrT, InternetAddrT]
52+
4953

5054
class Runstate(Enum):
5155
"""Protocol session runstate."""
@@ -61,7 +65,7 @@ class Runstate(Enum):
6165
DISCONNECTING = 3
6266

6367

64-
class ConnectError(AQMPError):
68+
class ConnectError(QMPError):
6569
"""
6670
Raised when the initial connection process has failed.
6771
@@ -86,7 +90,7 @@ def __str__(self) -> str:
8690
return f"{self.error_message}: {cause}"
8791

8892

89-
class StateError(AQMPError):
93+
class StateError(QMPError):
9094
"""
9195
An API command (connect, execute, etc) was issued at an inappropriate time.
9296
@@ -257,7 +261,7 @@ async def runstate_changed(self) -> Runstate:
257261

258262
@upper_half
259263
@require(Runstate.IDLE)
260-
async def accept(self, address: Union[str, Tuple[str, int]],
264+
async def accept(self, address: SocketAddrT,
261265
ssl: Optional[SSLContext] = None) -> None:
262266
"""
263267
Accept a connection and begin processing message queues.
@@ -275,7 +279,7 @@ async def accept(self, address: Union[str, Tuple[str, int]],
275279

276280
@upper_half
277281
@require(Runstate.IDLE)
278-
async def connect(self, address: Union[str, Tuple[str, int]],
282+
async def connect(self, address: SocketAddrT,
279283
ssl: Optional[SSLContext] = None) -> None:
280284
"""
281285
Connect to the server and begin processing message queues.
@@ -337,7 +341,7 @@ def _set_state(self, state: Runstate) -> None:
337341

338342
@upper_half
339343
async def _new_session(self,
340-
address: Union[str, Tuple[str, int]],
344+
address: SocketAddrT,
341345
ssl: Optional[SSLContext] = None,
342346
accept: bool = False) -> None:
343347
"""
@@ -359,7 +363,7 @@ async def _new_session(self,
359363
This exception will wrap a more concrete one. In most cases,
360364
the wrapped exception will be `OSError` or `EOFError`. If a
361365
protocol-level failure occurs while establishing a new
362-
session, the wrapped error may also be an `AQMPError`.
366+
session, the wrapped error may also be an `QMPError`.
363367
"""
364368
assert self.runstate == Runstate.IDLE
365369

@@ -397,7 +401,7 @@ async def _new_session(self,
397401
@upper_half
398402
async def _establish_connection(
399403
self,
400-
address: Union[str, Tuple[str, int]],
404+
address: SocketAddrT,
401405
ssl: Optional[SSLContext] = None,
402406
accept: bool = False
403407
) -> None:
@@ -424,7 +428,7 @@ async def _establish_connection(
424428
await self._do_connect(address, ssl)
425429

426430
@upper_half
427-
async def _do_accept(self, address: Union[str, Tuple[str, int]],
431+
async def _do_accept(self, address: SocketAddrT,
428432
ssl: Optional[SSLContext] = None) -> None:
429433
"""
430434
Acting as the transport server, accept a single connection.
@@ -482,7 +486,7 @@ async def _client_connected_cb(reader: asyncio.StreamReader,
482486
self.logger.debug("Connection accepted.")
483487

484488
@upper_half
485-
async def _do_connect(self, address: Union[str, Tuple[str, int]],
489+
async def _do_connect(self, address: SocketAddrT,
486490
ssl: Optional[SSLContext] = None) -> None:
487491
"""
488492
Acting as the transport client, initiate a connection to a server.

python/qemu/aqmp/qmp_client.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
cast,
2121
)
2222

23-
from .error import AQMPError, ProtocolError
23+
from .error import ProtocolError, QMPError
2424
from .events import Events
2525
from .message import Message
2626
from .models import ErrorResponse, Greeting
@@ -66,7 +66,7 @@ class NegotiationError(_WrappedProtocolError):
6666
"""
6767

6868

69-
class ExecuteError(AQMPError):
69+
class ExecuteError(QMPError):
7070
"""
7171
Exception raised by `QMPClient.execute()` on RPC failure.
7272
@@ -87,7 +87,7 @@ def __init__(self, error_response: ErrorResponse,
8787
self.error_class: str = error_response.error.class_
8888

8989

90-
class ExecInterruptedError(AQMPError):
90+
class ExecInterruptedError(QMPError):
9191
"""
9292
Exception raised by `execute()` (et al) when an RPC is interrupted.
9393
@@ -435,7 +435,11 @@ async def _issue(self, msg: Message) -> Union[None, str]:
435435
msg_id = msg['id']
436436

437437
self._pending[msg_id] = asyncio.Queue(maxsize=1)
438-
await self._outgoing.put(msg)
438+
try:
439+
await self._outgoing.put(msg)
440+
except:
441+
del self._pending[msg_id]
442+
raise
439443

440444
return msg_id
441445

@@ -452,9 +456,9 @@ async def _reply(self, msg_id: Union[str, None]) -> Message:
452456
was lost, or some other problem.
453457
"""
454458
queue = self._pending[msg_id]
455-
result = await queue.get()
456459

457460
try:
461+
result = await queue.get()
458462
if isinstance(result, ExecInterruptedError):
459463
raise result
460464
return result
@@ -637,7 +641,7 @@ def send_fd_scm(self, fd: int) -> None:
637641
sock = self._writer.transport.get_extra_info('socket')
638642

639643
if sock.family != socket.AF_UNIX:
640-
raise AQMPError("Sending file descriptors requires a UNIX socket.")
644+
raise QMPError("Sending file descriptors requires a UNIX socket.")
641645

642646
if not hasattr(sock, 'sendmsg'):
643647
# We need to void the warranty sticker.

0 commit comments

Comments
 (0)