Pull request

-----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEE+ber27ys35W+dsvQfe+BBqr8OQ4FAmFl/8cACgkQfe+BBqr8
 OQ7I8A/+K1vF5/TYME+9jPGJFrVVrT4TgMbzQlwxy6WOPXdO1prGmBR5cu9EqyfM
 PcQ4meYqKMGIWOYcxVYpDcsTkzT94nc4VuMQx0b3jw9W6PIKd4zZd0Kt10FqepAE
 akr8dzcgsClVvSwBHo9/9fXT5WWLGdeCttR42Lpv25ggMKSLuiL51w+/TmxJ8zuU
 pUn7tq2AjlQugqkZm+qtSq18Lu/0trmtPH5FyMCs0xxAPYP/h0QwaT6DizYIsLdz
 xZDH7ds/VsnQ41l5d+xG2/uh55ZTSzGQrCyVGoDbSI4jR8mWh3nj5uj1FQLWfKHT
 z7k/0oQZpp+9yFJ7M+yvPtW9L/narWuxW33qgGKT+CpicmEz1ZcEt9erWVitwbB9
 diP2z3AZzZlbxSOf+QKzl4TgYaKmFXcR6tCleitdvkwbUvOeSu0DyqmgMikdiRQw
 04BN3deuPjcZPWB007vQ/hQXdt2rQdv9E+E0qLszDyFBIVE2WIR4BNRl4AmsaSgz
 Fj8JrAERtdlaTW/iO34wboob3k+6e8de+USbqEMj1SgQBbsYRy5SGW0WaOe9r3lV
 D0ywZ8nB9G2Bb4iAvY38S80c04bAkgYwZuA4fpVjT0NpdLB0IcOBxLTuGwFrgfOF
 CBZ0e69pIMouWvRS56WPFUd6rXFCLtI0nXqiHxR/wunprB0/krU=
 =Yxqp
 -----END PGP SIGNATURE-----

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>
This commit is contained in:
Richard Henderson 2021-10-12 16:08:33 -07:00
commit ee26ce674a
15 changed files with 86 additions and 217 deletions

View File

@ -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__ = (

View File

@ -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

View File

@ -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):
"""

View File

@ -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()

View File

@ -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))]
)

View File

@ -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))
self._qmp.send_fd_scm(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)
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

View File

@ -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")

View File

@ -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.
Send a file descriptor to the remote via SCM_RIGHTS.
"""
if self.__sock.family != socket.AF_UNIX:
raise RuntimeError("Can't use SCM_RIGHTS on non-AF_UNIX socket.")
@return The file descriptor number.
"""
return self.__sock.fileno()
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))]
)

View File

@ -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)

View File

@ -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) \

View File

@ -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',

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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}