Skip to content

Commit

Permalink
Merge tag 'python-pull-request' of https://gitlab.com/jsnow/qemu into…
Browse files Browse the repository at this point in the history
… staging

Python Pullreq

Python PR:

- Use socketpair for all machine.py connections
- Support Python 3.12
- Switch iotests over to using raise-on-error QMP command interface
  (Thank you very much, Vladimir!)

# -----BEGIN PGP SIGNATURE-----
#
# iQIzBAABCAAdFiEE+ber27ys35W+dsvQfe+BBqr8OQ4FAmUpldkACgkQfe+BBqr8
# OQ4NtRAAnkEmXsECAxQ2ewvf3yK8PTFm4Oq5nqMIw+KB94ATrsGzk3z1rLvatSl3
# 6VLsV2+FWoOEyKrsfu5DIfbuo4d3TZTU7N2DIZpVpvO166K+fXbzp8skAg+n3BMC
# tWkSOcnsT6+8aqyxxyASdHvbbE7pvPw8OA3oIIstsYeZ5/HHpOWXNj1kjCsnL0lW
# 7y5h6UUKGmnCPdixyk042+AvKkT7GAKVjFnjUF5JHv0iR2KpQ+O9H7OEalqQT5w5
# eab4oMGuIYhzYe+MNpyybAB3Xd2pxhcppk+sl4dCE8qmMn7KRoTNw1iu+qhsNQfQ
# JILZoCPtYMhpef4X0ulH8PFBMweBptqOjo4lpz9QIdMWTf86IE0yIT9DCy3aSjpp
# ywwxhFKJS43gz4WHkEJlrY9PHwLsULaV/Cz6HKJAU6h9aFtcNdT4pkCOERnZ8X4C
# yHlNReTG5Dz1sYzKJ/k9LTjAaVDasumR8/yadaUCwalj5zexQ27qlIM6oc5wdIRQ
# up1VHi7odF5KHb6GeqdniuuEF6NBCYRAV5nz+dbd6exfKOaxYRrr48yh9SUm8QS6
# JCvMMFFAZCIrI/nkRVajbLi9L5O3fg5abtlzSzh9o4iyf8Rf/1gtKNxZRK1NZIjQ
# cTYBJXpMulNx7bM2CPNsPWGqCTAjAcu10svqTA8luGj4fqdTNyU=
# =02Bd
# -----END PGP SIGNATURE-----
# gpg: Signature made Fri 13 Oct 2023 15:09:13 EDT
# 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

* tag 'python-pull-request' of https://gitlab.com/jsnow/qemu: (25 commits)
  python: use vm.cmd() instead of vm.qmp() where appropriate
  scripts: add python_qmp_updater.py
  tests/vm/basevm.py: use cmd() instead of qmp()
  iotests.py: pause_job(): drop return value
  iotests: drop some extra ** in qmp() call
  iotests: drop some extra semicolons
  iotests: refactor some common qmp result checks into generic pattern
  iotests: add some missed checks of qmp result
  iotests: QemuStorageDaemon: add cmd() method like in QEMUMachine.
  python/machine.py: upgrade vm.cmd() method
  python/qemu: rename command() to cmd()
  python: rename QEMUMonitorProtocol.cmd() to cmd_raw()
  scripts/cpu-x86-uarch-abi.py: use .command() instead of .cmd()
  qmp_shell.py: _fill_completion() use .command() instead of .cmd()
  python/qemu/qmp/legacy: cmd(): drop cmd_id unused argument
  Python: Enable python3.12 support
  configure: fix error message to say Python 3.8
  python/qmp: remove Server.wait_closed() call for Python 3.12
  Python/iotests: Add type hint for nbd module
  python/machine: remove unused sock_dir argument
  ...

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
  • Loading branch information
stefanhaRH committed Oct 16, 2023
2 parents 34a0454 + b6aed19 commit a662b9d
Show file tree
Hide file tree
Showing 82 changed files with 1,484 additions and 1,735 deletions.
5 changes: 3 additions & 2 deletions configure
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,8 @@ first_python=
if test -z "${PYTHON}"; then
# A bare 'python' is traditionally python 2.x, but some distros
# have it as python 3.x, so check in both places.
for binary in python3 python python3.11 python3.10 python3.9 python3.8; do
for binary in python3 python python3.12 python3.11 \
python3.10 python3.9 python3.8; do
if has "$binary"; then
python=$(command -v "$binary")
if check_py_version "$python"; then
Expand Down Expand Up @@ -948,7 +949,7 @@ then
# If first_python is set, there was a binary somewhere even though
# it was not suitable. Use it for the error message.
if test -n "$first_python"; then
error_exit "Cannot use '$first_python', Python >= 3.7 is required." \
error_exit "Cannot use '$first_python', Python >= 3.8 is required." \
"Use --python=/path/to/python to specify a supported Python."
else
error_exit "Python not found. Use --python=/path/to/python"
Expand Down
10 changes: 5 additions & 5 deletions docs/devel/testing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1014,8 +1014,8 @@ class. Here's a simple usage example:
"""
def test_qmp_human_info_version(self):
self.vm.launch()
res = self.vm.command('human-monitor-command',
command_line='info version')
res = self.vm.cmd('human-monitor-command',
command_line='info version')
self.assertRegexpMatches(res, r'^(\d+\.\d+\.\d)')
To execute your test, run:
Expand Down Expand Up @@ -1065,15 +1065,15 @@ and hypothetical example follows:
first_machine.launch()
second_machine.launch()
first_res = first_machine.command(
first_res = first_machine.cmd(
'human-monitor-command',
command_line='info version')
second_res = second_machine.command(
second_res = second_machine.cmd(
'human-monitor-command',
command_line='info version')
third_res = self.get_vm(name='third_machine').command(
third_res = self.get_vm(name='third_machine').cmd(
'human-monitor-command',
command_line='info version')
Expand Down
29 changes: 21 additions & 8 deletions python/qemu/machine/console_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,32 @@ class ConsoleSocket(socket.socket):
"""
ConsoleSocket represents a socket attached to a char device.
Optionally (if drain==True), drains the socket and places the bytes
into an in memory buffer for later processing.
Optionally a file path can be passed in and we will also
dump the characters to this file for debugging purposes.
:param address: An AF_UNIX path or address.
:param sock_fd: Optionally, an existing socket file descriptor.
One of address or sock_fd must be specified.
:param file: Optionally, a filename to log to.
:param drain: Optionally, drains the socket and places the bytes
into an in memory buffer for later processing.
"""
def __init__(self, address: str, file: Optional[str] = None,
def __init__(self,
address: Optional[str] = None,
sock_fd: Optional[int] = None,
file: Optional[str] = None,
drain: bool = False):
if address is None and sock_fd is None:
raise ValueError("one of 'address' or 'sock_fd' must be specified")
if address is not None and sock_fd is not None:
raise ValueError("can't specify both 'address' and 'sock_fd'")

self._recv_timeout_sec = 300.0
self._sleep_time = 0.5
self._buffer: Deque[int] = deque()
socket.socket.__init__(self, socket.AF_UNIX, socket.SOCK_STREAM)
self.connect(address)
if address is not None:
socket.socket.__init__(self, socket.AF_UNIX, socket.SOCK_STREAM)
self.connect(address)
else:
assert sock_fd is not None
socket.socket.__init__(self, fileno=sock_fd)
self._logfile = None
if file:
# pylint: disable=consider-using-with
Expand Down
78 changes: 49 additions & 29 deletions python/qemu/machine/machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ def __init__(self,
name: Optional[str] = None,
base_temp_dir: str = "/var/tmp",
monitor_address: Optional[SocketAddrT] = None,
sock_dir: Optional[str] = None,
drain_console: bool = False,
console_log: Optional[str] = None,
log_dir: Optional[str] = None,
Expand All @@ -141,7 +140,6 @@ def __init__(self,
@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 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
@param log_dir: where to create and keep log files
Expand All @@ -159,9 +157,10 @@ def __init__(self,

self._name = name or f"{id(self):x}"
self._sock_pair: Optional[Tuple[socket.socket, socket.socket]] = None
self._cons_sock_pair: Optional[
Tuple[socket.socket, socket.socket]] = None
self._temp_dir: Optional[str] = None
self._base_temp_dir = base_temp_dir
self._sock_dir = sock_dir
self._log_dir = log_dir

self._monitor_address = monitor_address
Expand All @@ -187,9 +186,6 @@ def __init__(self,
self._console_index = 0
self._console_set = False
self._console_device_type: Optional[str] = None
self._console_address = os.path.join(
self.sock_dir, f"{self._name}.con"
)
self._console_socket: Optional[socket.socket] = None
self._console_file: Optional[socket.SocketIO] = None
self._remove_files: List[str] = []
Expand Down Expand Up @@ -301,9 +297,7 @@ def _base_args(self) -> List[str]:

if self._qmp_set:
if self._sock_pair:
fd = self._sock_pair[0].fileno()
os.set_inheritable(fd, True)
moncdev = f"socket,id=mon,fd={fd}"
moncdev = f"socket,id=mon,fd={self._sock_pair[0].fileno()}"
elif isinstance(self._monitor_address, tuple):
moncdev = "socket,id=mon,host={},port={}".format(
*self._monitor_address
Expand All @@ -318,8 +312,9 @@ def _base_args(self) -> List[str]:
for _ in range(self._console_index):
args.extend(['-serial', 'null'])
if self._console_set:
chardev = ('socket,id=console,path=%s,server=on,wait=off' %
self._console_address)
assert self._cons_sock_pair is not None
fd = self._cons_sock_pair[0].fileno()
chardev = f"socket,id=console,fd={fd}"
args.extend(['-chardev', chardev])
if self._console_device_type is None:
args.extend(['-serial', 'chardev:console'])
Expand All @@ -334,12 +329,10 @@ def args(self) -> List[str]:
return self._args

def _pre_launch(self) -> None:
if self._console_set:
self._remove_files.append(self._console_address)

if self._qmp_set:
if self._monitor_address is None:
self._sock_pair = socket.socketpair()
os.set_inheritable(self._sock_pair[0].fileno(), True)
sock = self._sock_pair[1]
if isinstance(self._monitor_address, str):
self._remove_files.append(self._monitor_address)
Expand All @@ -353,6 +346,10 @@ def _pre_launch(self) -> None:
nickname=self._name
)

if self._console_set:
self._cons_sock_pair = socket.socketpair()
os.set_inheritable(self._cons_sock_pair[0].fileno(), True)

# NOTE: Make sure any opened resources are *definitely* freed in
# _post_shutdown()!
# pylint: disable=consider-using-with
Expand All @@ -370,6 +367,9 @@ def _pre_launch(self) -> None:
def _post_launch(self) -> None:
if self._sock_pair:
self._sock_pair[0].close()
if self._cons_sock_pair:
self._cons_sock_pair[0].close()

if self._qmp_connection:
if self._sock_pair:
self._qmp.connect()
Expand Down Expand Up @@ -397,6 +397,11 @@ def _post_shutdown(self) -> None:
finally:
assert self._qmp_connection is None

if self._sock_pair:
self._sock_pair[0].close()
self._sock_pair[1].close()
self._sock_pair = None

self._close_qemu_log_file()

self._load_io_log()
Expand Down Expand Up @@ -520,6 +525,11 @@ def _early_cleanup(self) -> None:
self._console_socket.close()
self._console_socket = None

if self._cons_sock_pair:
self._cons_sock_pair[0].close()
self._cons_sock_pair[1].close()
self._cons_sock_pair = None

def _hard_shutdown(self) -> None:
"""
Perform early cleanup, kill the VM, and wait for it to terminate.
Expand Down Expand Up @@ -692,21 +702,31 @@ def qmp(self, cmd: str,
conv_keys = True

qmp_args = self._qmp_args(conv_keys, args)
ret = self._qmp.cmd(cmd, args=qmp_args)
ret = self._qmp.cmd_raw(cmd, args=qmp_args)
if cmd == 'quit' and 'error' not in ret and 'return' in ret:
self._quit_issued = True
return ret

def command(self, cmd: str,
conv_keys: bool = True,
**args: Any) -> QMPReturnValue:
def cmd(self, cmd: str,
args_dict: Optional[Dict[str, object]] = None,
conv_keys: Optional[bool] = None,
**args: Any) -> QMPReturnValue:
"""
Invoke a QMP command.
On success return the response dict.
On failure raise an exception.
"""
if args_dict is not None:
assert not args
assert conv_keys is None
args = args_dict
conv_keys = False

if conv_keys is None:
conv_keys = True

qmp_args = self._qmp_args(conv_keys, args)
ret = self._qmp.command(cmd, **qmp_args)
ret = self._qmp.cmd(cmd, **qmp_args)
if cmd == 'quit':
self._quit_issued = True
return ret
Expand Down Expand Up @@ -881,10 +901,19 @@ def console_socket(self) -> socket.socket:
"""
if self._console_socket is None:
LOG.debug("Opening console socket")
if not self._console_set:
raise QEMUMachineError(
"Attempt to access console socket with no connection")
assert self._cons_sock_pair is not None
# os.dup() is used here for sock_fd because otherwise we'd
# have two rich python socket objects that would each try to
# close the same underlying fd when either one gets garbage
# collected.
self._console_socket = console_socket.ConsoleSocket(
self._console_address,
sock_fd=os.dup(self._cons_sock_pair[1].fileno()),
file=self._console_log_path,
drain=self._drain_console)
self._cons_sock_pair[1].close()
return self._console_socket

@property
Expand All @@ -909,15 +938,6 @@ def temp_dir(self) -> str:
dir=self._base_temp_dir)
return self._temp_dir

@property
def sock_dir(self) -> str:
"""
Returns the directory used for sockfiles by this machine.
"""
if self._sock_dir:
return self._sock_dir
return self.temp_dir

@property
def log_dir(self) -> str:
"""
Expand Down

0 comments on commit a662b9d

Please sign in to comment.