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.
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEE+ber27ys35W+dsvQfe+BBqr8OQ4FAmHrSt4ACgkQfe+BBqr8
 OQ4BLg/5AdhcWsAtKq+xZj/nz2DNAbvXmrGJRuVln1yofrj22w2MYUTGfpQ0m8JO
 Ezu+LYtSTPQAaQw54WByaliD5h2ucYl5W8H13cDc8NPZkbsX+dD7G99u4XkSIY4I
 sSCYDMKi4j/P+4YR2MN1Iol4362fWbi3O3rsRX6PqNymIAaaklDrH3QZCGMiBMjl
 2OAcgba31uguyXnMuM3WY8XAnnRsib3wZ/a+a3WWcEsEm1HAUC0pb8VmrRzH5Rv9
 CpR3EBYoVc3of96jd/qLjucnoUL0+K2RVN2qPeie3+o7yEM2VYj5o+cG2H8pEG5p
 Fk/J7kqs0XWBOeX3A3IlGqKEXFPGDjEJZpcjwd8+IhgA2Y/MByTqRr1EvrOSO+bg
 q3njEg5DsORQS/xgZrnAidk5fdgLj7Cv39LfsxMnv77RBnlLubEAet7pT1XtprAv
 DI7STKknVpPu0VtYI8ALVjVhpeCkIt95DXACMtPZiSJ5X1NdoY5qubV1y8/vsExI
 RMDMepcS2A75Un2DA1bkStHTPN2PSUfM15fmUCebxbHp53FlJCh44gxAAsfj9j41
 xUmwSz1c81bCU4m+jsMBdNrbtkpPz/gX/3ZS8KqGoZmWN0wDkh3vEYFj5Y/310HY
 xmzug6o+tR7OD3bBGxZ73k9rn86X3+1PsYxOZjvYM0wiJIisPk4=
 =D2kj
 -----END PGP SIGNATURE-----

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) <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:
  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 <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2022-01-22 12:03:22 +00:00
commit aeb0ae95b7
24 changed files with 151 additions and 90 deletions

View File

@ -68,6 +68,8 @@ $(QEMU_VENV_DIR) $(QEMU_VENV_DIR)/bin/activate: setup.cfg
echo "ACTIVATE $(QEMU_VENV_DIR)"; \ echo "ACTIVATE $(QEMU_VENV_DIR)"; \
. $(QEMU_VENV_DIR)/bin/activate; \ . $(QEMU_VENV_DIR)/bin/activate; \
echo "INSTALL qemu[devel] $(QEMU_VENV_DIR)"; \ echo "INSTALL qemu[devel] $(QEMU_VENV_DIR)"; \
pip install --disable-pip-version-check \
"setuptools<60.0.0" 1>/dev/null; \
make develop 1>/dev/null; \ make develop 1>/dev/null; \
) )
@touch $(QEMU_VENV_DIR) @touch $(QEMU_VENV_DIR)

View File

@ -59,7 +59,7 @@ Package installation also normally provides executable console scripts,
so that tools like ``qmp-shell`` are always available via $PATH. To so that tools like ``qmp-shell`` are always available via $PATH. To
invoke them without installation, you can invoke e.g.: invoke them without installation, you can invoke e.g.:
``> PYTHONPATH=~/src/qemu/python python3 -m qemu.qmp.qmp_shell`` ``> PYTHONPATH=~/src/qemu/python python3 -m qemu.aqmp.qmp_shell``
The mappings between console script name and python module path can be The mappings between console script name and python module path can be
found in ``setup.cfg``. found in ``setup.cfg``.

View File

@ -1,5 +1,5 @@
[run] [run]
test_runner = runner test_runner = nrunner
[simpletests] [simpletests]
# Don't show stdout/stderr in the test *summary* # Don't show stdout/stderr in the test *summary*

View File

@ -6,7 +6,7 @@ asynchronously with QMP protocol servers, as implemented by QEMU, the
QEMU Guest Agent, and the QEMU Storage Daemon. QEMU Guest Agent, and the QEMU Storage Daemon.
`QMPClient` provides the main functionality of this package. All errors `QMPClient` provides the main functionality of this package. All errors
raised by this library dervive from `AQMPError`, see `aqmp.error` for raised by this library derive from `QMPError`, see `aqmp.error` for
additional detail. See `aqmp.events` for an in-depth tutorial on additional detail. See `aqmp.events` for an in-depth tutorial on
managing QMP events. managing QMP events.
""" """
@ -23,10 +23,15 @@ managing QMP events.
import logging import logging
from .error import AQMPError from .error import QMPError
from .events import EventListener from .events import EventListener
from .message import Message from .message import Message
from .protocol import ConnectError, Runstate, StateError from .protocol import (
ConnectError,
Runstate,
SocketAddrT,
StateError,
)
from .qmp_client import ExecInterruptedError, ExecuteError, QMPClient from .qmp_client import ExecInterruptedError, ExecuteError, QMPClient
@ -43,9 +48,12 @@ __all__ = (
'Runstate', 'Runstate',
# Exceptions, most generic to most explicit # Exceptions, most generic to most explicit
'AQMPError', 'QMPError',
'StateError', 'StateError',
'ConnectError', 'ConnectError',
'ExecuteError', 'ExecuteError',
'ExecInterruptedError', 'ExecInterruptedError',
# Type aliases
'SocketAddrT',
) )

View File

@ -1,21 +1,21 @@
""" """
AQMP Error Classes QMP Error Classes
This package seeks to provide semantic error classes that are intended This package seeks to provide semantic error classes that are intended
to be used directly by clients when they would like to handle particular to be used directly by clients when they would like to handle particular
semantic failures (e.g. "failed to connect") without needing to know the semantic failures (e.g. "failed to connect") without needing to know the
enumeration of possible reasons for that failure. enumeration of possible reasons for that failure.
AQMPError serves as the ancestor for all exceptions raised by this QMPError serves as the ancestor for all exceptions raised by this
package, and is suitable for use in handling semantic errors from this package, and is suitable for use in handling semantic errors from this
library. In most cases, individual public methods will attempt to catch library. In most cases, individual public methods will attempt to catch
and re-encapsulate various exceptions to provide a semantic and re-encapsulate various exceptions to provide a semantic
error-handling interface. error-handling interface.
.. admonition:: AQMP Exception Hierarchy Reference .. admonition:: QMP Exception Hierarchy Reference
| `Exception` | `Exception`
| +-- `AQMPError` | +-- `QMPError`
| +-- `ConnectError` | +-- `ConnectError`
| +-- `StateError` | +-- `StateError`
| +-- `ExecInterruptedError` | +-- `ExecInterruptedError`
@ -31,11 +31,11 @@ error-handling interface.
""" """
class AQMPError(Exception): class QMPError(Exception):
"""Abstract error class for all errors originating from this package.""" """Abstract error class for all errors originating from this package."""
class ProtocolError(AQMPError): class ProtocolError(QMPError):
""" """
Abstract error class for protocol failures. Abstract error class for protocol failures.

View File

@ -443,7 +443,7 @@ from typing import (
Union, Union,
) )
from .error import AQMPError from .error import QMPError
from .message import Message from .message import Message
@ -451,7 +451,7 @@ EventNames = Union[str, Iterable[str], None]
EventFilter = Callable[[Message], bool] EventFilter = Callable[[Message], bool]
class ListenerError(AQMPError): class ListenerError(QMPError):
""" """
Generic error class for `EventListener`-related problems. Generic error class for `EventListener`-related problems.
""" """

View File

@ -6,7 +6,9 @@ This class pretends to be qemu.qmp.QEMUMonitorProtocol.
import asyncio import asyncio
from typing import ( from typing import (
Any,
Awaitable, Awaitable,
Dict,
List, List,
Optional, Optional,
TypeVar, TypeVar,
@ -14,11 +16,32 @@ from typing import (
) )
import qemu.qmp import qemu.qmp
from qemu.qmp import QMPMessage, QMPReturnValue, SocketAddrT
from .error import QMPError
from .protocol import Runstate, SocketAddrT
from .qmp_client import QMPClient from .qmp_client import QMPClient
# (Temporarily) Re-export QMPBadPortError
QMPBadPortError = qemu.qmp.QMPBadPortError
#: QMPMessage is an entire QMP message of any kind.
QMPMessage = Dict[str, Any]
#: QMPReturnValue is the 'return' value of a command.
QMPReturnValue = object
#: QMPObject is any object in a QMP message.
QMPObject = Dict[str, object]
# QMPMessage can be outgoing commands or incoming events/returns.
# QMPReturnValue is usually a dict/json object, but due to QAPI's
# 'returns-whitelist', it can actually be anything.
#
# {'return': {}} is a QMPMessage,
# {} is the QMPReturnValue.
# pylint: disable=missing-docstring # pylint: disable=missing-docstring
@ -136,3 +159,19 @@ class QEMUMonitorProtocol(qemu.qmp.QEMUMonitorProtocol):
def send_fd_scm(self, fd: int) -> None: def send_fd_scm(self, fd: int) -> None:
self._aqmp.send_fd_scm(fd) self._aqmp.send_fd_scm(fd)
def __del__(self) -> None:
if self._aqmp.runstate == Runstate.IDLE:
return
if not self._aloop.is_running():
self.close()
else:
# Garbage collection ran while the event loop was running.
# Nothing we can do about it now, but if we don't raise our
# own error, the user will be treated to a lot of traceback
# they might not understand.
raise QMPError(
"QEMUMonitorProtocol.close()"
" was not called before object was garbage collected"
)

View File

@ -29,7 +29,7 @@ from typing import (
cast, cast,
) )
from .error import AQMPError from .error import QMPError
from .util import ( from .util import (
bottom_half, bottom_half,
create_task, create_task,
@ -46,6 +46,10 @@ T = TypeVar('T')
_U = TypeVar('_U') _U = TypeVar('_U')
_TaskFN = Callable[[], Awaitable[None]] # aka ``async def func() -> None`` _TaskFN = Callable[[], Awaitable[None]] # aka ``async def func() -> None``
InternetAddrT = Tuple[str, int]
UnixAddrT = str
SocketAddrT = Union[UnixAddrT, InternetAddrT]
class Runstate(Enum): class Runstate(Enum):
"""Protocol session runstate.""" """Protocol session runstate."""
@ -61,7 +65,7 @@ class Runstate(Enum):
DISCONNECTING = 3 DISCONNECTING = 3
class ConnectError(AQMPError): class ConnectError(QMPError):
""" """
Raised when the initial connection process has failed. Raised when the initial connection process has failed.
@ -86,7 +90,7 @@ class ConnectError(AQMPError):
return f"{self.error_message}: {cause}" return f"{self.error_message}: {cause}"
class StateError(AQMPError): class StateError(QMPError):
""" """
An API command (connect, execute, etc) was issued at an inappropriate time. An API command (connect, execute, etc) was issued at an inappropriate time.
@ -257,7 +261,7 @@ class AsyncProtocol(Generic[T]):
@upper_half @upper_half
@require(Runstate.IDLE) @require(Runstate.IDLE)
async def accept(self, address: Union[str, Tuple[str, int]], async def accept(self, address: SocketAddrT,
ssl: Optional[SSLContext] = None) -> None: ssl: Optional[SSLContext] = None) -> None:
""" """
Accept a connection and begin processing message queues. Accept a connection and begin processing message queues.
@ -275,7 +279,7 @@ class AsyncProtocol(Generic[T]):
@upper_half @upper_half
@require(Runstate.IDLE) @require(Runstate.IDLE)
async def connect(self, address: Union[str, Tuple[str, int]], async def connect(self, address: SocketAddrT,
ssl: Optional[SSLContext] = None) -> None: ssl: Optional[SSLContext] = None) -> None:
""" """
Connect to the server and begin processing message queues. Connect to the server and begin processing message queues.
@ -337,7 +341,7 @@ class AsyncProtocol(Generic[T]):
@upper_half @upper_half
async def _new_session(self, async def _new_session(self,
address: Union[str, Tuple[str, int]], address: SocketAddrT,
ssl: Optional[SSLContext] = None, ssl: Optional[SSLContext] = None,
accept: bool = False) -> None: accept: bool = False) -> None:
""" """
@ -359,7 +363,7 @@ class AsyncProtocol(Generic[T]):
This exception will wrap a more concrete one. In most cases, This exception will wrap a more concrete one. In most cases,
the wrapped exception will be `OSError` or `EOFError`. If a the wrapped exception will be `OSError` or `EOFError`. If a
protocol-level failure occurs while establishing a new protocol-level failure occurs while establishing a new
session, the wrapped error may also be an `AQMPError`. session, the wrapped error may also be an `QMPError`.
""" """
assert self.runstate == Runstate.IDLE assert self.runstate == Runstate.IDLE
@ -397,7 +401,7 @@ class AsyncProtocol(Generic[T]):
@upper_half @upper_half
async def _establish_connection( async def _establish_connection(
self, self,
address: Union[str, Tuple[str, int]], address: SocketAddrT,
ssl: Optional[SSLContext] = None, ssl: Optional[SSLContext] = None,
accept: bool = False accept: bool = False
) -> None: ) -> None:
@ -424,7 +428,7 @@ class AsyncProtocol(Generic[T]):
await self._do_connect(address, ssl) await self._do_connect(address, ssl)
@upper_half @upper_half
async def _do_accept(self, address: Union[str, Tuple[str, int]], async def _do_accept(self, address: SocketAddrT,
ssl: Optional[SSLContext] = None) -> None: ssl: Optional[SSLContext] = None) -> None:
""" """
Acting as the transport server, accept a single connection. Acting as the transport server, accept a single connection.
@ -482,7 +486,7 @@ class AsyncProtocol(Generic[T]):
self.logger.debug("Connection accepted.") self.logger.debug("Connection accepted.")
@upper_half @upper_half
async def _do_connect(self, address: Union[str, Tuple[str, int]], async def _do_connect(self, address: SocketAddrT,
ssl: Optional[SSLContext] = None) -> None: ssl: Optional[SSLContext] = None) -> None:
""" """
Acting as the transport client, initiate a connection to a server. Acting as the transport client, initiate a connection to a server.

View File

@ -20,7 +20,7 @@ from typing import (
cast, cast,
) )
from .error import AQMPError, ProtocolError from .error import ProtocolError, QMPError
from .events import Events from .events import Events
from .message import Message from .message import Message
from .models import ErrorResponse, Greeting from .models import ErrorResponse, Greeting
@ -66,7 +66,7 @@ class NegotiationError(_WrappedProtocolError):
""" """
class ExecuteError(AQMPError): class ExecuteError(QMPError):
""" """
Exception raised by `QMPClient.execute()` on RPC failure. Exception raised by `QMPClient.execute()` on RPC failure.
@ -87,7 +87,7 @@ class ExecuteError(AQMPError):
self.error_class: str = error_response.error.class_ self.error_class: str = error_response.error.class_
class ExecInterruptedError(AQMPError): class ExecInterruptedError(QMPError):
""" """
Exception raised by `execute()` (et al) when an RPC is interrupted. Exception raised by `execute()` (et al) when an RPC is interrupted.
@ -435,7 +435,11 @@ class QMPClient(AsyncProtocol[Message], Events):
msg_id = msg['id'] msg_id = msg['id']
self._pending[msg_id] = asyncio.Queue(maxsize=1) self._pending[msg_id] = asyncio.Queue(maxsize=1)
await self._outgoing.put(msg) try:
await self._outgoing.put(msg)
except:
del self._pending[msg_id]
raise
return msg_id return msg_id
@ -452,9 +456,9 @@ class QMPClient(AsyncProtocol[Message], Events):
was lost, or some other problem. was lost, or some other problem.
""" """
queue = self._pending[msg_id] queue = self._pending[msg_id]
result = await queue.get()
try: try:
result = await queue.get()
if isinstance(result, ExecInterruptedError): if isinstance(result, ExecInterruptedError):
raise result raise result
return result return result
@ -637,7 +641,7 @@ class QMPClient(AsyncProtocol[Message], Events):
sock = self._writer.transport.get_extra_info('socket') sock = self._writer.transport.get_extra_info('socket')
if sock.family != socket.AF_UNIX: if sock.family != socket.AF_UNIX:
raise AQMPError("Sending file descriptors requires a UNIX socket.") raise QMPError("Sending file descriptors requires a UNIX socket.")
if not hasattr(sock, 'sendmsg'): if not hasattr(sock, 'sendmsg'):
# We need to void the warranty sticker. # We need to void the warranty sticker.

View File

@ -95,8 +95,13 @@ from typing import (
Sequence, Sequence,
) )
from qemu import qmp from qemu.aqmp import ConnectError, QMPError, SocketAddrT
from qemu.qmp import QMPMessage from qemu.aqmp.legacy import (
QEMUMonitorProtocol,
QMPBadPortError,
QMPMessage,
QMPObject,
)
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -125,7 +130,7 @@ class QMPCompleter:
return None return None
class QMPShellError(qmp.QMPError): class QMPShellError(QMPError):
""" """
QMP Shell Base error class. QMP Shell Base error class.
""" """
@ -153,7 +158,7 @@ class FuzzyJSON(ast.NodeTransformer):
return node return node
class QMPShell(qmp.QEMUMonitorProtocol): class QMPShell(QEMUMonitorProtocol):
""" """
QMPShell provides a basic readline-based QMP shell. QMPShell provides a basic readline-based QMP shell.
@ -161,7 +166,7 @@ class QMPShell(qmp.QEMUMonitorProtocol):
:param pretty: Pretty-print QMP messages. :param pretty: Pretty-print QMP messages.
:param verbose: Echo outgoing QMP messages to console. :param verbose: Echo outgoing QMP messages to console.
""" """
def __init__(self, address: qmp.SocketAddrT, def __init__(self, address: SocketAddrT,
pretty: bool = False, verbose: bool = False): pretty: bool = False, verbose: bool = False):
super().__init__(address) super().__init__(address)
self._greeting: Optional[QMPMessage] = None self._greeting: Optional[QMPMessage] = None
@ -237,7 +242,7 @@ class QMPShell(qmp.QEMUMonitorProtocol):
def _cli_expr(self, def _cli_expr(self,
tokens: Sequence[str], tokens: Sequence[str],
parent: qmp.QMPObject) -> None: parent: QMPObject) -> None:
for arg in tokens: for arg in tokens:
(key, sep, val) = arg.partition('=') (key, sep, val) = arg.partition('=')
if sep != '=': if sep != '=':
@ -403,7 +408,7 @@ class HMPShell(QMPShell):
:param pretty: Pretty-print QMP messages. :param pretty: Pretty-print QMP messages.
:param verbose: Echo outgoing QMP messages to console. :param verbose: Echo outgoing QMP messages to console.
""" """
def __init__(self, address: qmp.SocketAddrT, def __init__(self, address: SocketAddrT,
pretty: bool = False, verbose: bool = False): pretty: bool = False, verbose: bool = False):
super().__init__(address, pretty, verbose) super().__init__(address, pretty, verbose)
self._cpu_index = 0 self._cpu_index = 0
@ -512,19 +517,17 @@ def main() -> None:
try: try:
address = shell_class.parse_address(args.qmp_server) address = shell_class.parse_address(args.qmp_server)
except qmp.QMPBadPortError: except QMPBadPortError:
parser.error(f"Bad port number: {args.qmp_server}") parser.error(f"Bad port number: {args.qmp_server}")
return # pycharm doesn't know error() is noreturn return # pycharm doesn't know error() is noreturn
with shell_class(address, args.pretty, args.verbose) as qemu: with shell_class(address, args.pretty, args.verbose) as qemu:
try: try:
qemu.connect(negotiate=not args.skip_negotiation) qemu.connect(negotiate=not args.skip_negotiation)
except qmp.QMPConnectError: except ConnectError as err:
die("Didn't get QMP greeting message") if isinstance(err.exc, OSError):
except qmp.QMPCapabilitiesError: die(f"Couldn't connect to {args.qmp_server}: {err!s}")
die("Couldn't negotiate capabilities") die(str(err))
except OSError as err:
die(f"Couldn't connect to {args.qmp_server}: {err!s}")
for _ in qemu.repl(): for _ in qemu.repl():
pass pass

View File

@ -5,7 +5,7 @@ Usage:
Start QEMU with: Start QEMU with:
# qemu [...] -chardev socket,path=/tmp/qga.sock,server,wait=off,id=qga0 \ # qemu [...] -chardev socket,path=/tmp/qga.sock,server=on,wait=off,id=qga0 \
-device virtio-serial \ -device virtio-serial \
-device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0 -device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0
@ -37,8 +37,8 @@ See also: https://wiki.qemu.org/Features/QAPI/GuestAgent
# the COPYING file in the top-level directory. # the COPYING file in the top-level directory.
import argparse import argparse
import asyncio
import base64 import base64
import errno
import os import os
import random import random
import sys import sys
@ -50,8 +50,8 @@ from typing import (
Sequence, Sequence,
) )
from qemu import qmp from qemu.aqmp import ConnectError, SocketAddrT
from qemu.qmp import SocketAddrT from qemu.aqmp.legacy import QEMUMonitorProtocol
# This script has not seen many patches or careful attention in quite # This script has not seen many patches or careful attention in quite
@ -61,7 +61,7 @@ from qemu.qmp import SocketAddrT
# pylint: disable=missing-docstring # pylint: disable=missing-docstring
class QemuGuestAgent(qmp.QEMUMonitorProtocol): class QemuGuestAgent(QEMUMonitorProtocol):
def __getattr__(self, name: str) -> Callable[..., Any]: def __getattr__(self, name: str) -> Callable[..., Any]:
def wrapper(**kwds: object) -> object: def wrapper(**kwds: object) -> object:
return self.command('guest-' + name.replace('_', '-'), **kwds) return self.command('guest-' + name.replace('_', '-'), **kwds)
@ -149,7 +149,7 @@ class QemuGuestAgentClient:
self.qga.settimeout(timeout) self.qga.settimeout(timeout)
try: try:
self.qga.ping() self.qga.ping()
except TimeoutError: except asyncio.TimeoutError:
return False return False
return True return True
@ -172,7 +172,7 @@ class QemuGuestAgentClient:
try: try:
getattr(self.qga, 'suspend' + '_' + mode)() getattr(self.qga, 'suspend' + '_' + mode)()
# On error exception will raise # On error exception will raise
except TimeoutError: except asyncio.TimeoutError:
# On success command will timed out # On success command will timed out
return return
@ -182,7 +182,7 @@ class QemuGuestAgentClient:
try: try:
self.qga.shutdown(mode=mode) self.qga.shutdown(mode=mode)
except TimeoutError: except asyncio.TimeoutError:
pass pass
@ -277,7 +277,7 @@ commands = [m.replace('_cmd_', '') for m in dir() if '_cmd_' in m]
def send_command(address: str, cmd: str, args: Sequence[str]) -> None: def send_command(address: str, cmd: str, args: Sequence[str]) -> None:
if not os.path.exists(address): if not os.path.exists(address):
print('%s not found' % address) print(f"'{address}' not found. (Is QEMU running?)")
sys.exit(1) sys.exit(1)
if cmd not in commands: if cmd not in commands:
@ -287,10 +287,10 @@ def send_command(address: str, cmd: str, args: Sequence[str]) -> None:
try: try:
client = QemuGuestAgentClient(address) client = QemuGuestAgentClient(address)
except OSError as err: except ConnectError as err:
print(err) print(err)
if err.errno == errno.ECONNREFUSED: if isinstance(err.exc, ConnectionError):
print('Hint: qemu is not running?') print('(Is QEMU running?)')
sys.exit(1) sys.exit(1)
if cmd == 'fsfreeze' and args[0] == 'freeze': if cmd == 'fsfreeze' and args[0] == 'freeze':

View File

@ -32,7 +32,8 @@ QOM commands:
import argparse import argparse
from . import QMPResponseError from qemu.aqmp import ExecuteError
from .qom_common import QOMCommand from .qom_common import QOMCommand
@ -233,7 +234,7 @@ class QOMTree(QOMCommand):
rsp = self.qmp.command('qom-get', path=path, rsp = self.qmp.command('qom-get', path=path,
property=item.name) property=item.name)
print(f" {item.name}: {rsp} ({item.type})") print(f" {item.name}: {rsp} ({item.type})")
except QMPResponseError as err: except ExecuteError as err:
print(f" {item.name}: <EXCEPTION: {err!s}> ({item.type})") print(f" {item.name}: <EXCEPTION: {err!s}> ({item.type})")
print('') print('')
for item in items: for item in items:

View File

@ -27,7 +27,8 @@ from typing import (
TypeVar, TypeVar,
) )
from . import QEMUMonitorProtocol, QMPError from qemu.aqmp import QMPError
from qemu.aqmp.legacy import QEMUMonitorProtocol
class ObjectPropertyInfo: class ObjectPropertyInfo:

View File

@ -48,7 +48,8 @@ from typing import (
import fuse import fuse
from fuse import FUSE, FuseOSError, Operations from fuse import FUSE, FuseOSError, Operations
from . import QMPResponseError from qemu.aqmp import ExecuteError
from .qom_common import QOMCommand from .qom_common import QOMCommand
@ -99,7 +100,7 @@ class QOMFuse(QOMCommand, Operations):
try: try:
self.qom_list(path) self.qom_list(path)
return True return True
except QMPResponseError: except ExecuteError:
return False return False
def is_property(self, path: str) -> bool: def is_property(self, path: str) -> bool:
@ -112,7 +113,7 @@ class QOMFuse(QOMCommand, Operations):
if item.name == prop: if item.name == prop:
return True return True
return False return False
except QMPResponseError: except ExecuteError:
return False return False
def is_link(self, path: str) -> bool: def is_link(self, path: str) -> bool:
@ -125,7 +126,7 @@ class QOMFuse(QOMCommand, Operations):
if item.name == prop and item.link: if item.name == prop and item.link:
return True return True
return False return False
except QMPResponseError: except ExecuteError:
return False return False
def read(self, path: str, size: int, offset: int, fh: IO[bytes]) -> bytes: def read(self, path: str, size: int, offset: int, fh: IO[bytes]) -> bytes:
@ -138,7 +139,7 @@ class QOMFuse(QOMCommand, Operations):
try: try:
data = str(self.qmp.command('qom-get', path=path, property=prop)) data = str(self.qmp.command('qom-get', path=path, property=prop))
data += '\n' # make values shell friendly data += '\n' # make values shell friendly
except QMPResponseError as err: except ExecuteError as err:
raise FuseOSError(EPERM) from err raise FuseOSError(EPERM) from err
if offset > len(data): if offset > len(data):

View File

@ -60,14 +60,14 @@ tui =
[options.entry_points] [options.entry_points]
console_scripts = console_scripts =
qom = qemu.qmp.qom:main qom = qemu.utils.qom:main
qom-set = qemu.qmp.qom:QOMSet.entry_point qom-set = qemu.utils.qom:QOMSet.entry_point
qom-get = qemu.qmp.qom:QOMGet.entry_point qom-get = qemu.utils.qom:QOMGet.entry_point
qom-list = qemu.qmp.qom:QOMList.entry_point qom-list = qemu.utils.qom:QOMList.entry_point
qom-tree = qemu.qmp.qom:QOMTree.entry_point qom-tree = qemu.utils.qom:QOMTree.entry_point
qom-fuse = qemu.qmp.qom_fuse:QOMFuse.entry_point [fuse] qom-fuse = qemu.utils.qom_fuse:QOMFuse.entry_point [fuse]
qemu-ga-client = qemu.qmp.qemu_ga_client:main qemu-ga-client = qemu.utils.qemu_ga_client:main
qmp-shell = qemu.qmp.qmp_shell:main qmp-shell = qemu.aqmp.qmp_shell:main
aqmp-tui = qemu.aqmp.aqmp_tui:main [tui] aqmp-tui = qemu.aqmp.aqmp_tui:main [tui]
[flake8] [flake8]
@ -80,7 +80,7 @@ python_version = 3.6
warn_unused_configs = True warn_unused_configs = True
namespace_packages = True namespace_packages = True
[mypy-qemu.qmp.qom_fuse] [mypy-qemu.utils.qom_fuse]
# fusepy has no type stubs: # fusepy has no type stubs:
allow_subclassing_any = True allow_subclassing_any = True
@ -163,6 +163,7 @@ deps =
.[devel] .[devel]
.[fuse] # Workaround to trigger tox venv rebuild .[fuse] # Workaround to trigger tox venv rebuild
.[tui] # Workaround to trigger tox venv rebuild .[tui] # Workaround to trigger tox venv rebuild
setuptools < 60 # Workaround, please see commit msg.
commands = commands =
make check make check

View File

@ -6,10 +6,10 @@
# compatibility levels for each CPU model. # compatibility levels for each CPU model.
# #
from qemu import qmp from qemu.aqmp.legacy import QEMUMonitorProtocol
import sys import sys
if len(sys.argv) != 1: if len(sys.argv) != 2:
print("syntax: %s QMP-SOCK\n\n" % __file__ + print("syntax: %s QMP-SOCK\n\n" % __file__ +
"Where QMP-SOCK points to a QEMU process such as\n\n" + "Where QMP-SOCK points to a QEMU process such as\n\n" +
" # qemu-system-x86_64 -qmp unix:/tmp/qmp,server,nowait " + " # qemu-system-x86_64 -qmp unix:/tmp/qmp,server,nowait " +
@ -66,8 +66,7 @@ levels = [
sock = sys.argv[1] sock = sys.argv[1]
cmd = sys.argv[2] shell = QEMUMonitorProtocol(sock)
shell = qmp.QEMUMonitorProtocol(sock)
shell.connect() shell.connect()
models = shell.cmd("query-cpu-definitions") models = shell.cmd("query-cpu-definitions")

View File

@ -4,7 +4,7 @@ import os
import sys import sys
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
from qemu.qmp import qemu_ga_client from qemu.utils import qemu_ga_client
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -4,7 +4,7 @@ import os
import sys import sys
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
from qemu.qmp import qmp_shell from qemu.aqmp import qmp_shell
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -4,7 +4,7 @@ import os
import sys import sys
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
from qemu.qmp.qom_fuse import QOMFuse from qemu.utils.qom_fuse import QOMFuse
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -4,7 +4,7 @@ import os
import sys import sys
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
from qemu.qmp.qom import QOMGet from qemu.utils.qom import QOMGet
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -4,7 +4,7 @@ import os
import sys import sys
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
from qemu.qmp.qom import QOMList from qemu.utils.qom import QOMList
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -4,7 +4,7 @@ import os
import sys import sys
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
from qemu.qmp.qom import QOMSet from qemu.utils.qom import QOMSet
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -4,7 +4,7 @@ import os
import sys import sys
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python')) sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
from qemu.qmp.qom import QOMTree from qemu.utils.qom import QOMTree
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -25,10 +25,8 @@ import json
from graphviz import Digraph from graphviz import Digraph
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'python')) sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'python'))
from qemu.qmp import ( from qemu.aqmp import QMPError
QEMUMonitorProtocol, from qemu.aqmp.legacy import QEMUMonitorProtocol
QMPResponseError,
)
def perm(arr): def perm(arr):
@ -104,7 +102,7 @@ class LibvirtGuest():
reply = json.loads(subprocess.check_output(ar)) reply = json.loads(subprocess.check_output(ar))
if 'error' in reply: if 'error' in reply:
raise QMPResponseError(reply) raise QMPError(reply)
return reply['return'] return reply['return']