mirror of https://github.com/xemu-project/xemu.git
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:
commit
aeb0ae95b7
|
@ -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)
|
||||||
|
|
|
@ -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``.
|
||||||
|
|
|
@ -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*
|
||||||
|
|
|
@ -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',
|
||||||
)
|
)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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"
|
||||||
|
)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
|
@ -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':
|
|
@ -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:
|
|
@ -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:
|
|
@ -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):
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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__':
|
||||||
|
|
|
@ -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__':
|
||||||
|
|
|
@ -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__':
|
||||||
|
|
|
@ -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__':
|
||||||
|
|
|
@ -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__':
|
||||||
|
|
|
@ -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__':
|
||||||
|
|
|
@ -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__':
|
||||||
|
|
|
@ -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']
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue