aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Henderson <richard.henderson@linaro.org>2021-10-12 16:08:33 -0700
committerRichard Henderson <richard.henderson@linaro.org>2021-10-12 16:08:33 -0700
commitee26ce674a93c824713542cec3b6a9ca85459165 (patch)
treedfe2cd8dfc063e4e739a990b6754cbe20f73bf79
parent8be1d4ed9838f807c63695753b30865f6edc4a5c (diff)
parentc163c723ef92d0f629d015902396f2c67328b2e5 (diff)
downloadqemu-ee26ce674a93c824713542cec3b6a9ca85459165.zip
qemu-ee26ce674a93c824713542cec3b6a9ca85459165.tar.gz
qemu-ee26ce674a93c824713542cec3b6a9ca85459165.tar.bz2
Merge remote-tracking branch 'remotes/jsnow/tags/python-pull-request' into staging
Pull request # gpg: Signature made Tue 12 Oct 2021 02:36:07 PM PDT # gpg: using RSA key F9B7ABDBBCACDF95BE76CBD07DEF8106AAFC390E # gpg: Good signature from "John Snow (John Huston) <jsnow@redhat.com>" [full] * remotes/jsnow/tags/python-pull-request: python, iotests: remove socket_scm_helper python/qmp: add send_fd_scm directly to QEMUMonitorProtocol python/qmp: clear events on get_events() call python/aqmp: Disable logging messages by default python/aqmp: Reduce severity of EOFError-caused loop terminations python/aqmp: Add dict conversion method to Greeting object python/aqmp: add send_fd_scm python/aqmp: Return cleared events from EventListener.clear() python/aqmp: add .empty() method to EventListener python/aqmp: add greeting property to QMPClient Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
-rw-r--r--python/qemu/aqmp/__init__.py4
-rw-r--r--python/qemu/aqmp/events.py15
-rw-r--r--python/qemu/aqmp/models.py13
-rw-r--r--python/qemu/aqmp/protocol.py7
-rw-r--r--python/qemu/aqmp/qmp_client.py27
-rw-r--r--python/qemu/machine/machine.py48
-rw-r--r--python/qemu/machine/qtest.py2
-rw-r--r--python/qemu/qmp/__init__.py27
-rw-r--r--python/qemu/qmp/qmp_shell.py1
-rw-r--r--tests/Makefile.include1
-rw-r--r--tests/meson.build4
-rw-r--r--tests/qemu-iotests/iotests.py3
-rw-r--r--tests/qemu-iotests/meson.build5
-rw-r--r--tests/qemu-iotests/socket_scm_helper.c136
-rw-r--r--tests/qemu-iotests/testenv.py8
15 files changed, 85 insertions, 216 deletions
diff --git a/python/qemu/aqmp/__init__.py b/python/qemu/aqmp/__init__.py
index ab17829..d1b0e4d 100644
--- a/python/qemu/aqmp/__init__.py
+++ b/python/qemu/aqmp/__init__.py
@@ -21,6 +21,7 @@ managing QMP events.
# This work is licensed under the terms of the GNU GPL, version 2. See
# the COPYING file in the top-level directory.
+import logging
import warnings
from .error import AQMPError
@@ -41,6 +42,9 @@ Proceed with caution!
warnings.warn(_WMSG, FutureWarning)
+# Suppress logging unless an application engages it.
+logging.getLogger('qemu.aqmp').addHandler(logging.NullHandler())
+
# The order of these fields impact the Sphinx documentation order.
__all__ = (
diff --git a/python/qemu/aqmp/events.py b/python/qemu/aqmp/events.py
index fb81d21..5f7150c 100644
--- a/python/qemu/aqmp/events.py
+++ b/python/qemu/aqmp/events.py
@@ -556,7 +556,13 @@ class EventListener:
"""
return await self._queue.get()
- def clear(self) -> None:
+ def empty(self) -> bool:
+ """
+ Return `True` if there are no pending events.
+ """
+ return self._queue.empty()
+
+ def clear(self) -> List[Message]:
"""
Clear this listener of all pending events.
@@ -564,17 +570,22 @@ class EventListener:
pending FIFO queue synchronously. It can be also be used to
manually clear any pending events, if desired.
+ :return: The cleared events, if any.
+
.. warning::
Take care when discarding events. Cleared events will be
silently tossed on the floor. All events that were ever
accepted by this listener are visible in `history()`.
"""
+ events = []
while True:
try:
- self._queue.get_nowait()
+ events.append(self._queue.get_nowait())
except asyncio.QueueEmpty:
break
+ return events
+
def __aiter__(self) -> AsyncIterator[Message]:
return self
diff --git a/python/qemu/aqmp/models.py b/python/qemu/aqmp/models.py
index 24c9412..de87f87 100644
--- a/python/qemu/aqmp/models.py
+++ b/python/qemu/aqmp/models.py
@@ -8,8 +8,10 @@ data to make sure it conforms to spec.
# pylint: disable=too-few-public-methods
from collections import abc
+import copy
from typing import (
Any,
+ Dict,
Mapping,
Optional,
Sequence,
@@ -66,6 +68,17 @@ class Greeting(Model):
self._check_member('QMP', abc.Mapping, "JSON object")
self.QMP = QMPGreeting(self._raw['QMP'])
+ def _asdict(self) -> Dict[str, object]:
+ """
+ For compatibility with the iotests sync QMP wrapper.
+
+ The legacy QMP interface needs Greetings as a garden-variety Dict.
+
+ This interface is private in the hopes that it will be able to
+ be dropped again in the near-future. Caller beware!
+ """
+ return dict(copy.deepcopy(self._raw))
+
class QMPGreeting(Model):
"""
diff --git a/python/qemu/aqmp/protocol.py b/python/qemu/aqmp/protocol.py
index 32e7874..ae1df24 100644
--- a/python/qemu/aqmp/protocol.py
+++ b/python/qemu/aqmp/protocol.py
@@ -721,8 +721,11 @@ class AsyncProtocol(Generic[T]):
self.logger.debug("Task.%s: cancelled.", name)
return
except BaseException as err:
- self.logger.error("Task.%s: %s",
- name, exception_summary(err))
+ self.logger.log(
+ logging.INFO if isinstance(err, EOFError) else logging.ERROR,
+ "Task.%s: %s",
+ name, exception_summary(err)
+ )
self.logger.debug("Task.%s: failure:\n%s\n",
name, pretty_traceback())
self._schedule_disconnect()
diff --git a/python/qemu/aqmp/qmp_client.py b/python/qemu/aqmp/qmp_client.py
index 82e9dab..f987da0 100644
--- a/python/qemu/aqmp/qmp_client.py
+++ b/python/qemu/aqmp/qmp_client.py
@@ -9,6 +9,8 @@ accept an incoming connection from that server.
import asyncio
import logging
+import socket
+import struct
from typing import (
Dict,
List,
@@ -224,6 +226,11 @@ class QMPClient(AsyncProtocol[Message], Events):
'asyncio.Queue[QMPClient._PendingT]'
] = {}
+ @property
+ def greeting(self) -> Optional[Greeting]:
+ """The `Greeting` from the QMP server, if any."""
+ return self._greeting
+
@upper_half
async def _establish_session(self) -> None:
"""
@@ -619,3 +626,23 @@ class QMPClient(AsyncProtocol[Message], Events):
"""
msg = self.make_execute_msg(cmd, arguments, oob=oob)
return await self.execute_msg(msg)
+
+ @upper_half
+ @require(Runstate.RUNNING)
+ def send_fd_scm(self, fd: int) -> None:
+ """
+ Send a file descriptor to the remote via SCM_RIGHTS.
+ """
+ assert self._writer is not None
+ sock = self._writer.transport.get_extra_info('socket')
+
+ if sock.family != socket.AF_UNIX:
+ raise AQMPError("Sending file descriptors requires a UNIX socket.")
+
+ # Void the warranty sticker.
+ # Access to sendmsg in asyncio is scheduled for removal in Python 3.11.
+ sock = sock._sock # pylint: disable=protected-access
+ sock.sendmsg(
+ [b' '],
+ [(socket.SOL_SOCKET, socket.SCM_RIGHTS, struct.pack('@i', fd))]
+ )
diff --git a/python/qemu/machine/machine.py b/python/qemu/machine/machine.py
index 3413188..056d340 100644
--- a/python/qemu/machine/machine.py
+++ b/python/qemu/machine/machine.py
@@ -98,7 +98,6 @@ class QEMUMachine:
name: Optional[str] = None,
base_temp_dir: str = "/var/tmp",
monitor_address: Optional[SocketAddrT] = None,
- socket_scm_helper: Optional[str] = None,
sock_dir: Optional[str] = None,
drain_console: bool = False,
console_log: Optional[str] = None,
@@ -113,7 +112,6 @@ class QEMUMachine:
@param name: prefix for socket and log file names (default: qemu-PID)
@param base_temp_dir: default location where temp files are created
@param monitor_address: address for QMP monitor
- @param socket_scm_helper: helper program, required for send_fd_scm()
@param sock_dir: where to create socket (defaults to base_temp_dir)
@param drain_console: (optional) True to drain console socket to buffer
@param console_log: (optional) path to console log file
@@ -134,7 +132,6 @@ class QEMUMachine:
self._base_temp_dir = base_temp_dir
self._sock_dir = sock_dir or self._base_temp_dir
self._log_dir = log_dir
- self._socket_scm_helper = socket_scm_helper
if monitor_address is not None:
self._monitor_address = monitor_address
@@ -213,48 +210,22 @@ class QEMUMachine:
def send_fd_scm(self, fd: Optional[int] = None,
file_path: Optional[str] = None) -> int:
"""
- Send an fd or file_path to socket_scm_helper.
+ Send an fd or file_path to the remote via SCM_RIGHTS.
- Exactly one of fd and file_path must be given.
- If it is file_path, the helper will open that file and pass its own fd.
+ Exactly one of fd and file_path must be given. If it is
+ file_path, the file will be opened read-only and the new file
+ descriptor will be sent to the remote.
"""
- # In iotest.py, the qmp should always use unix socket.
- assert self._qmp.is_scm_available()
- if self._socket_scm_helper is None:
- raise QEMUMachineError("No path to socket_scm_helper set")
- if not os.path.exists(self._socket_scm_helper):
- raise QEMUMachineError("%s does not exist" %
- self._socket_scm_helper)
-
- # This did not exist before 3.4, but since then it is
- # mandatory for our purpose
- if hasattr(os, 'set_inheritable'):
- os.set_inheritable(self._qmp.get_sock_fd(), True)
- if fd is not None:
- os.set_inheritable(fd, True)
-
- fd_param = ["%s" % self._socket_scm_helper,
- "%d" % self._qmp.get_sock_fd()]
-
if file_path is not None:
assert fd is None
- fd_param.append(file_path)
+ with open(file_path, "rb") as passfile:
+ fd = passfile.fileno()
+ self._qmp.send_fd_scm(fd)
else:
assert fd is not None
- fd_param.append(str(fd))
-
- proc = subprocess.run(
- fd_param,
- stdin=subprocess.DEVNULL,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- check=False,
- close_fds=False,
- )
- if proc.stdout:
- LOG.debug(proc.stdout)
+ self._qmp.send_fd_scm(fd)
- return proc.returncode
+ return 0
@staticmethod
def _remove_if_exists(path: str) -> None:
@@ -631,7 +602,6 @@ class QEMUMachine:
events = self._qmp.get_events(wait=wait)
events.extend(self._events)
del self._events[:]
- self._qmp.clear_events()
return events
@staticmethod
diff --git a/python/qemu/machine/qtest.py b/python/qemu/machine/qtest.py
index 395cc8f..f2f9aaa 100644
--- a/python/qemu/machine/qtest.py
+++ b/python/qemu/machine/qtest.py
@@ -115,7 +115,6 @@ class QEMUQtestMachine(QEMUMachine):
wrapper: Sequence[str] = (),
name: Optional[str] = None,
base_temp_dir: str = "/var/tmp",
- socket_scm_helper: Optional[str] = None,
sock_dir: Optional[str] = None,
qmp_timer: Optional[float] = None):
# pylint: disable=too-many-arguments
@@ -126,7 +125,6 @@ class QEMUQtestMachine(QEMUMachine):
sock_dir = base_temp_dir
super().__init__(binary, args, wrapper=wrapper, name=name,
base_temp_dir=base_temp_dir,
- socket_scm_helper=socket_scm_helper,
sock_dir=sock_dir, qmp_timer=qmp_timer)
self._qtest: Optional[QEMUQtestProtocol] = None
self._qtest_path = os.path.join(sock_dir, name + "-qtest.sock")
diff --git a/python/qemu/qmp/__init__.py b/python/qemu/qmp/__init__.py
index 269516a..358c097 100644
--- a/python/qemu/qmp/__init__.py
+++ b/python/qemu/qmp/__init__.py
@@ -21,6 +21,7 @@ import errno
import json
import logging
import socket
+import struct
from types import TracebackType
from typing import (
Any,
@@ -361,7 +362,7 @@ class QEMUMonitorProtocol:
def get_events(self, wait: bool = False) -> List[QMPMessage]:
"""
- Get a list of available QMP events.
+ Get a list of available QMP events and clear all pending events.
@param wait (bool): block until an event is available.
@param wait (float): If wait is a float, treat it as a timeout value.
@@ -374,7 +375,9 @@ class QEMUMonitorProtocol:
@return The list of available QMP events.
"""
self.__get_events(wait)
- return self.__events
+ events = self.__events
+ self.__events = []
+ return events
def clear_events(self) -> None:
"""
@@ -406,18 +409,14 @@ class QEMUMonitorProtocol:
raise ValueError(msg)
self.__sock.settimeout(timeout)
- def get_sock_fd(self) -> int:
+ def send_fd_scm(self, fd: int) -> None:
"""
- Get the socket file descriptor.
-
- @return The file descriptor number.
+ Send a file descriptor to the remote via SCM_RIGHTS.
"""
- return self.__sock.fileno()
+ if self.__sock.family != socket.AF_UNIX:
+ raise RuntimeError("Can't use SCM_RIGHTS on non-AF_UNIX socket.")
- def is_scm_available(self) -> bool:
- """
- Check if the socket allows for SCM_RIGHTS.
-
- @return True if SCM_RIGHTS is available, otherwise False.
- """
- return self.__sock.family == socket.AF_UNIX
+ self.__sock.sendmsg(
+ [b' '],
+ [(socket.SOL_SOCKET, socket.SCM_RIGHTS, struct.pack('@i', fd))]
+ )
diff --git a/python/qemu/qmp/qmp_shell.py b/python/qemu/qmp/qmp_shell.py
index 337acfc..e7d7eb1 100644
--- a/python/qemu/qmp/qmp_shell.py
+++ b/python/qemu/qmp/qmp_shell.py
@@ -381,7 +381,6 @@ class QMPShell(qmp.QEMUMonitorProtocol):
if cmdline == '':
for event in self.get_events():
print(event)
- self.clear_events()
return True
return self._execute_cmd(cmdline)
diff --git a/tests/Makefile.include b/tests/Makefile.include
index 7426522..7bb8961 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -148,7 +148,6 @@ check-acceptance: check-venv $(TESTS_RESULTS_DIR) get-vm-images
check:
ifeq ($(CONFIG_TOOLS)$(CONFIG_POSIX),yy)
-QEMU_IOTESTS_HELPERS-$(CONFIG_LINUX) = tests/qemu-iotests/socket_scm_helper$(EXESUF)
check: check-block
export PYTHON
check-block: $(SRC_PATH)/tests/check-block.sh qemu-img$(EXESUF) \
diff --git a/tests/meson.build b/tests/meson.build
index 55a7b08..3f38827 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -67,10 +67,6 @@ if have_tools and 'CONFIG_VHOST_USER' in config_host and 'CONFIG_LINUX' in confi
dependencies: [qemuutil, vhost_user])
endif
-if have_system and 'CONFIG_POSIX' in config_host
- subdir('qemu-iotests')
-endif
-
test('decodetree', sh,
args: [ files('decode/check.sh'), config_host['PYTHON'], files('../scripts/decodetree.py') ],
workdir: meson.current_source_dir() / 'decode',
diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py
index b06ad76..e5fff6d 100644
--- a/tests/qemu-iotests/iotests.py
+++ b/tests/qemu-iotests/iotests.py
@@ -107,8 +107,6 @@ if os.environ.get('VALGRIND_QEMU') == "y" and \
qemu_valgrind = ['valgrind', valgrind_logfile, '--error-exitcode=99']
-socket_scm_helper = os.environ.get('SOCKET_SCM_HELPER', 'socket_scm_helper')
-
luks_default_secret_object = 'secret,id=keysec0,data=' + \
os.environ.get('IMGKEYSECRET', '')
luks_default_key_secret_opt = 'key-secret=keysec0'
@@ -598,7 +596,6 @@ class VM(qtest.QEMUQtestMachine):
super().__init__(qemu_prog, qemu_opts, wrapper=wrapper,
name=name,
base_temp_dir=test_dir,
- socket_scm_helper=socket_scm_helper,
sock_dir=sock_dir, qmp_timer=timer)
self._num_drives = 0
diff --git a/tests/qemu-iotests/meson.build b/tests/qemu-iotests/meson.build
deleted file mode 100644
index 67aed1e..0000000
--- a/tests/qemu-iotests/meson.build
+++ /dev/null
@@ -1,5 +0,0 @@
-if 'CONFIG_LINUX' in config_host
- socket_scm_helper = executable('socket_scm_helper', 'socket_scm_helper.c')
-else
- socket_scm_helper = []
-endif
diff --git a/tests/qemu-iotests/socket_scm_helper.c b/tests/qemu-iotests/socket_scm_helper.c
deleted file mode 100644
index eb76d31..0000000
--- a/tests/qemu-iotests/socket_scm_helper.c
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * SCM_RIGHTS with unix socket help program for test
- *
- * Copyright IBM, Inc. 2013
- *
- * Authors:
- * Wenchao Xia <xiawenc@linux.vnet.ibm.com>
- *
- * This work is licensed under the terms of the GNU LGPL, version 2 or later.
- * See the COPYING.LIB file in the top-level directory.
- */
-
-#include "qemu/osdep.h"
-#include <sys/socket.h>
-#include <sys/un.h>
-
-/* #define SOCKET_SCM_DEBUG */
-
-/*
- * @fd and @fd_to_send will not be checked for validation in this function,
- * a blank will be sent as iov data to notify qemu.
- */
-static int send_fd(int fd, int fd_to_send)
-{
- struct msghdr msg;
- struct iovec iov[1];
- int ret;
- char control[CMSG_SPACE(sizeof(int))];
- struct cmsghdr *cmsg;
-
- memset(&msg, 0, sizeof(msg));
- memset(control, 0, sizeof(control));
-
- /* Send a blank to notify qemu */
- iov[0].iov_base = (void *)" ";
- iov[0].iov_len = 1;
-
- msg.msg_iov = iov;
- msg.msg_iovlen = 1;
-
- msg.msg_control = control;
- msg.msg_controllen = sizeof(control);
-
- cmsg = CMSG_FIRSTHDR(&msg);
-
- cmsg->cmsg_len = CMSG_LEN(sizeof(int));
- cmsg->cmsg_level = SOL_SOCKET;
- cmsg->cmsg_type = SCM_RIGHTS;
- memcpy(CMSG_DATA(cmsg), &fd_to_send, sizeof(int));
-
- do {
- ret = sendmsg(fd, &msg, 0);
- } while (ret < 0 && errno == EINTR);
-
- if (ret < 0) {
- fprintf(stderr, "Failed to send msg, reason: %s\n", strerror(errno));
- }
-
- return ret;
-}
-
-/* Convert string to fd number. */
-static int get_fd_num(const char *fd_str, bool silent)
-{
- int sock;
- char *err;
-
- errno = 0;
- sock = strtol(fd_str, &err, 10);
- if (errno) {
- if (!silent) {
- fprintf(stderr, "Failed in strtol for socket fd, reason: %s\n",
- strerror(errno));
- }
- return -1;
- }
- if (!*fd_str || *err || sock < 0) {
- if (!silent) {
- fprintf(stderr, "bad numerical value for socket fd '%s'\n", fd_str);
- }
- return -1;
- }
-
- return sock;
-}
-
-/*
- * To make things simple, the caller needs to specify:
- * 1. socket fd.
- * 2. path of the file to be sent.
- */
-int main(int argc, char **argv, char **envp)
-{
- int sock, fd, ret;
-
-#ifdef SOCKET_SCM_DEBUG
- int i;
- for (i = 0; i < argc; i++) {
- fprintf(stderr, "Parameter %d: %s\n", i, argv[i]);
- }
-#endif
-
- if (argc != 3) {
- fprintf(stderr,
- "Usage: %s < socket-fd > < file-path >\n",
- argv[0]);
- return EXIT_FAILURE;
- }
-
-
- sock = get_fd_num(argv[1], false);
- if (sock < 0) {
- return EXIT_FAILURE;
- }
-
- fd = get_fd_num(argv[2], true);
- if (fd < 0) {
- /* Now only open a file in readonly mode for test purpose. If more
- precise control is needed, use python script in file operation, which
- is supposed to fork and exec this program. */
- fd = open(argv[2], O_RDONLY);
- if (fd < 0) {
- fprintf(stderr, "Failed to open file '%s'\n", argv[2]);
- return EXIT_FAILURE;
- }
- }
-
- ret = send_fd(sock, fd);
- if (ret < 0) {
- close(fd);
- return EXIT_FAILURE;
- }
-
- close(fd);
- return EXIT_SUCCESS;
-}
diff --git a/tests/qemu-iotests/testenv.py b/tests/qemu-iotests/testenv.py
index 99a57a6..c33454f 100644
--- a/tests/qemu-iotests/testenv.py
+++ b/tests/qemu-iotests/testenv.py
@@ -68,7 +68,7 @@ class TestEnv(ContextManager['TestEnv']):
env_variables = ['PYTHONPATH', 'TEST_DIR', 'SOCK_DIR', 'SAMPLE_IMG_DIR',
'OUTPUT_DIR', 'PYTHON', 'QEMU_PROG', 'QEMU_IMG_PROG',
'QEMU_IO_PROG', 'QEMU_NBD_PROG', 'QSD_PROG',
- 'SOCKET_SCM_HELPER', 'QEMU_OPTIONS', 'QEMU_IMG_OPTIONS',
+ 'QEMU_OPTIONS', 'QEMU_IMG_OPTIONS',
'QEMU_IO_OPTIONS', 'QEMU_IO_OPTIONS_NO_FMT',
'QEMU_NBD_OPTIONS', 'IMGOPTS', 'IMGFMT', 'IMGPROTO',
'AIOMODE', 'CACHEMODE', 'VALGRIND_QEMU',
@@ -140,7 +140,6 @@ class TestEnv(ContextManager['TestEnv']):
"""Init binary path variables:
PYTHON (for bash tests)
QEMU_PROG, QEMU_IMG_PROG, QEMU_IO_PROG, QEMU_NBD_PROG, QSD_PROG
- SOCKET_SCM_HELPER
"""
self.python = sys.executable
@@ -174,10 +173,6 @@ class TestEnv(ContextManager['TestEnv']):
if not isxfile(b):
sys.exit('Not executable: ' + b)
- helper_path = os.path.join(self.build_iotests, 'socket_scm_helper')
- if isxfile(helper_path):
- self.socket_scm_helper = helper_path # SOCKET_SCM_HELPER
-
def __init__(self, imgfmt: str, imgproto: str, aiomode: str,
cachemode: Optional[str] = None,
imgopts: Optional[str] = None,
@@ -303,7 +298,6 @@ IMGPROTO -- {IMGPROTO}
PLATFORM -- {platform}
TEST_DIR -- {TEST_DIR}
SOCK_DIR -- {SOCK_DIR}
-SOCKET_SCM_HELPER -- {SOCKET_SCM_HELPER}
GDB_OPTIONS -- {GDB_OPTIONS}
VALGRIND_QEMU -- {VALGRIND_QEMU}
PRINT_QEMU_OUTPUT -- {PRINT_QEMU}