* [PULL 00/21] Python patches
@ 2020-10-20 17:27 John Snow
2020-10-20 17:27 ` [PULL 01/21] MAINTAINERS: Add Python library stanza John Snow
` (20 more replies)
0 siblings, 21 replies; 24+ messages in thread
From: John Snow @ 2020-10-20 17:27 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Peter Maydell, Eduardo Habkost, qemu-block,
Max Reitz, Cleber Rosa, John Snow
The following changes since commit 4c41341af76cfc85b5a6c0f87de4838672ab9f89:
Merge remote-tracking branch 'remotes/aperard/tags/pull-xen-20201020' into staging (2020-10-20 11:20:36 +0100)
are available in the Git repository at:
https://gitlab.com/jsnow/qemu.git tags/python-pull-request
for you to fetch changes up to 5e44bef7cc532cd89ee46c78341266caa66f4437:
python/qemu/qmp.py: Fix settimeout operation (2020-10-20 09:37:57 -0400)
----------------------------------------------------------------
Pull request
----------------------------------------------------------------
John Snow (21):
MAINTAINERS: Add Python library stanza
python/qemu: use isort to lay out imports
python/machine.py: Fix monitor address typing
python/machine.py: reorder __init__
python/machine.py: Don't modify state in _base_args()
python/machine.py: Handle None events in events_wait
python/machine.py: use qmp.command
python/machine.py: Add _qmp access shim
python/machine.py: fix _popen access
python/qemu: make 'args' style arguments immutable
iotests.py: Adjust HMP kwargs typing
python/qemu: Add mypy type annotations
python/qemu/console_socket.py: Correct type of recv()
python/qemu/console_socket.py: fix typing of settimeout
python/qemu/console_socket.py: Clarify type of drain_thread
python/qemu/console_socket.py: Add type hint annotations
python/qemu/console_socket.py: avoid encoding to/from string
python/qemu/qmp.py: Preserve error context on re-raise
python: add mypy config
python/qemu/qmp.py: re-raise OSError when encountered
python/qemu/qmp.py: Fix settimeout operation
MAINTAINERS | 9 +-
python/mypy.ini | 4 +
python/qemu/.isort.cfg | 7 +
python/qemu/accel.py | 9 +-
python/qemu/console_socket.py | 54 +++---
python/qemu/machine.py | 306 +++++++++++++++++++++-------------
python/qemu/qmp.py | 89 ++++++----
python/qemu/qtest.py | 55 +++---
tests/qemu-iotests/iotests.py | 2 +-
9 files changed, 325 insertions(+), 210 deletions(-)
create mode 100644 python/mypy.ini
create mode 100644 python/qemu/.isort.cfg
--
2.26.2
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PULL 01/21] MAINTAINERS: Add Python library stanza
2020-10-20 17:27 [PULL 00/21] Python patches John Snow
@ 2020-10-20 17:27 ` John Snow
2020-10-20 17:27 ` [PULL 02/21] python/qemu: use isort to lay out imports John Snow
` (19 subsequent siblings)
20 siblings, 0 replies; 24+ messages in thread
From: John Snow @ 2020-10-20 17:27 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Peter Maydell, Eduardo Habkost, qemu-block,
Philippe Mathieu-Daudé,
Max Reitz, Alex Bennée, Cleber Rosa, John Snow
I'm proposing that I split the actual Python library off from the other
miscellaneous python scripts we have and declare it maintained. Add
myself as a maintainer of this folder, along with Cleber.
I will be actively working to add CI style guide checks, strict typing,
and an actual package infrastructure to this folder specifically which
differentiates it from loose, miscellaneous scripts which are generally
maintained by other individuals with subject matter expertise.
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Tested-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Reviewed-by: Alex Bennée <alex.bennee@linaro.org>
Acked-by: Cleber Rosa <crosa@redhat.com>
Acked-by: Eduardo Habkost <ehabkost@redhat.com>
---
MAINTAINERS | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index a7f0acf866..6a197bd358 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2373,11 +2373,18 @@ S: Maintained
F: include/sysemu/cryptodev*.h
F: backends/cryptodev*.c
+Python library
+M: John Snow <jsnow@redhat.com>
+M: Cleber Rosa <crosa@redhat.com>
+R: Eduardo Habkost <ehabkost@redhat.com>
+S: Maintained
+F: python/
+T: git https://gitlab.com/jsnow/qemu.git python
+
Python scripts
M: Eduardo Habkost <ehabkost@redhat.com>
M: Cleber Rosa <crosa@redhat.com>
S: Odd fixes
-F: python/qemu/*py
F: scripts/*.py
F: tests/*.py
--
2.26.2
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PULL 02/21] python/qemu: use isort to lay out imports
2020-10-20 17:27 [PULL 00/21] Python patches John Snow
2020-10-20 17:27 ` [PULL 01/21] MAINTAINERS: Add Python library stanza John Snow
@ 2020-10-20 17:27 ` John Snow
2020-10-20 17:27 ` [PULL 03/21] python/machine.py: Fix monitor address typing John Snow
` (18 subsequent siblings)
20 siblings, 0 replies; 24+ messages in thread
From: John Snow @ 2020-10-20 17:27 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Peter Maydell, Eduardo Habkost, qemu-block,
Philippe Mathieu-Daudé,
Max Reitz, Cleber Rosa, John Snow
Borrowed from the QAPI cleanup series, use the same configuration to
standardize the way we write and sort imports.
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Message-id: 20201006235817.3280413-2-jsnow@redhat.com
Signed-off-by: John Snow <jsnow@redhat.com>
---
python/qemu/.isort.cfg | 7 +++++++
python/qemu/accel.py | 1 +
python/qemu/console_socket.py | 2 +-
python/qemu/machine.py | 8 ++++----
python/qemu/qmp.py | 10 +++++-----
python/qemu/qtest.py | 2 +-
6 files changed, 19 insertions(+), 11 deletions(-)
create mode 100644 python/qemu/.isort.cfg
diff --git a/python/qemu/.isort.cfg b/python/qemu/.isort.cfg
new file mode 100644
index 0000000000..6d0fd6cc0d
--- /dev/null
+++ b/python/qemu/.isort.cfg
@@ -0,0 +1,7 @@
+[settings]
+force_grid_wrap=4
+force_sort_within_sections=True
+include_trailing_comma=True
+line_length=72
+lines_after_imports=2
+multi_line_output=3
\ No newline at end of file
diff --git a/python/qemu/accel.py b/python/qemu/accel.py
index 7fabe62920..3ec6bdcfdb 100644
--- a/python/qemu/accel.py
+++ b/python/qemu/accel.py
@@ -18,6 +18,7 @@
import os
import subprocess
+
LOG = logging.getLogger(__name__)
# Mapping host architecture to any additional architectures it can
diff --git a/python/qemu/console_socket.py b/python/qemu/console_socket.py
index 70869fbbdc..69f604c77f 100644
--- a/python/qemu/console_socket.py
+++ b/python/qemu/console_socket.py
@@ -13,9 +13,9 @@
# the COPYING file in the top-level directory.
#
+from collections import deque
import socket
import threading
-from collections import deque
import time
diff --git a/python/qemu/machine.py b/python/qemu/machine.py
index 82f3731fc3..bc83f399c1 100644
--- a/python/qemu/machine.py
+++ b/python/qemu/machine.py
@@ -20,15 +20,15 @@
import errno
import logging
import os
-import subprocess
import shutil
import signal
+import subprocess
import tempfile
-from typing import Optional, Type
from types import TracebackType
-from . import console_socket
+from typing import Optional, Type
+
+from . import console_socket, qmp
-from . import qmp
LOG = logging.getLogger(__name__)
diff --git a/python/qemu/qmp.py b/python/qemu/qmp.py
index 7935dababb..ddf8347ac1 100644
--- a/python/qemu/qmp.py
+++ b/python/qemu/qmp.py
@@ -7,21 +7,21 @@
# This work is licensed under the terms of the GNU GPL, version 2. See
# the COPYING file in the top-level directory.
-import json
import errno
-import socket
+import json
import logging
+import socket
+from types import TracebackType
from typing import (
Any,
- cast,
Dict,
Optional,
TextIO,
- Type,
Tuple,
+ Type,
Union,
+ cast,
)
-from types import TracebackType
# QMPMessage is a QMP Message of any kind.
diff --git a/python/qemu/qtest.py b/python/qemu/qtest.py
index 888c8bd2f6..7700c0b09b 100644
--- a/python/qemu/qtest.py
+++ b/python/qemu/qtest.py
@@ -17,8 +17,8 @@
# Based on qmp.py.
#
-import socket
import os
+import socket
from typing import Optional, TextIO
from .machine import QEMUMachine
--
2.26.2
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PULL 03/21] python/machine.py: Fix monitor address typing
2020-10-20 17:27 [PULL 00/21] Python patches John Snow
2020-10-20 17:27 ` [PULL 01/21] MAINTAINERS: Add Python library stanza John Snow
2020-10-20 17:27 ` [PULL 02/21] python/qemu: use isort to lay out imports John Snow
@ 2020-10-20 17:27 ` John Snow
2020-10-20 17:27 ` [PULL 04/21] python/machine.py: reorder __init__ John Snow
` (17 subsequent siblings)
20 siblings, 0 replies; 24+ messages in thread
From: John Snow @ 2020-10-20 17:27 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Peter Maydell, Eduardo Habkost, qemu-block,
Max Reitz, Cleber Rosa, John Snow
Prior to this, it's difficult for mypy to intuit what the concrete type
of the monitor address is; it has difficulty inferring the type across
two variables.
Create _monitor_address as a property that always returns a valid
address to simplify static type analysis.
To preserve our ability to clean up, use a simple boolean to indicate
whether or not we should try to clean up the sock file after execution.
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Message-id: 20201006235817.3280413-3-jsnow@redhat.com
Signed-off-by: John Snow <jsnow@redhat.com>
---
python/qemu/machine.py | 40 +++++++++++++++++++++++++---------------
1 file changed, 25 insertions(+), 15 deletions(-)
diff --git a/python/qemu/machine.py b/python/qemu/machine.py
index bc83f399c1..3017ec072d 100644
--- a/python/qemu/machine.py
+++ b/python/qemu/machine.py
@@ -28,6 +28,7 @@
from typing import Optional, Type
from . import console_socket, qmp
+from .qmp import SocketAddrT
LOG = logging.getLogger(__name__)
@@ -68,7 +69,8 @@ class QEMUMachine:
"""
def __init__(self, binary, args=None, wrapper=None, name=None,
- test_dir="/var/tmp", monitor_address=None,
+ test_dir="/var/tmp",
+ monitor_address: Optional[SocketAddrT] = None,
socket_scm_helper=None, sock_dir=None,
drain_console=False, console_log=None):
'''
@@ -95,8 +97,14 @@ def __init__(self, binary, args=None, wrapper=None, name=None,
if sock_dir is None:
sock_dir = test_dir
self._name = name
- self._monitor_address = monitor_address
- self._vm_monitor = None
+ if monitor_address is not None:
+ self._monitor_address = monitor_address
+ self._remove_monitor_sockfile = False
+ else:
+ self._monitor_address = os.path.join(
+ sock_dir, f"{name}-monitor.sock"
+ )
+ self._remove_monitor_sockfile = True
self._qemu_log_path = None
self._qemu_log_file = None
self._popen = None
@@ -241,15 +249,17 @@ def _load_io_log(self):
def _base_args(self):
args = ['-display', 'none', '-vga', 'none']
+
if self._qmp_set:
if isinstance(self._monitor_address, tuple):
- moncdev = "socket,id=mon,host=%s,port=%s" % (
- self._monitor_address[0],
- self._monitor_address[1])
+ moncdev = "socket,id=mon,host={},port={}".format(
+ *self._monitor_address
+ )
else:
- moncdev = 'socket,id=mon,path=%s' % self._vm_monitor
+ moncdev = f"socket,id=mon,path={self._monitor_address}"
args.extend(['-chardev', moncdev, '-mon',
'chardev=mon,mode=control'])
+
if self._machine is not None:
args.extend(['-machine', self._machine])
for _ in range(self._console_index):
@@ -274,14 +284,14 @@ def _pre_launch(self):
self._qemu_log_file = open(self._qemu_log_path, 'wb')
if self._qmp_set:
- if self._monitor_address is not None:
- self._vm_monitor = self._monitor_address
- else:
- self._vm_monitor = os.path.join(self._sock_dir,
- self._name + "-monitor.sock")
- self._remove_files.append(self._vm_monitor)
- self._qmp = qmp.QEMUMonitorProtocol(self._vm_monitor, server=True,
- nickname=self._name)
+ if self._remove_monitor_sockfile:
+ assert isinstance(self._monitor_address, str)
+ self._remove_files.append(self._monitor_address)
+ self._qmp = qmp.QEMUMonitorProtocol(
+ self._monitor_address,
+ server=True,
+ nickname=self._name
+ )
def _post_launch(self):
if self._qmp:
--
2.26.2
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PULL 04/21] python/machine.py: reorder __init__
2020-10-20 17:27 [PULL 00/21] Python patches John Snow
` (2 preceding siblings ...)
2020-10-20 17:27 ` [PULL 03/21] python/machine.py: Fix monitor address typing John Snow
@ 2020-10-20 17:27 ` John Snow
2020-10-20 17:27 ` [PULL 05/21] python/machine.py: Don't modify state in _base_args() John Snow
` (16 subsequent siblings)
20 siblings, 0 replies; 24+ messages in thread
From: John Snow @ 2020-10-20 17:27 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Peter Maydell, Eduardo Habkost, qemu-block,
Philippe Mathieu-Daudé,
Max Reitz, Cleber Rosa, John Snow
Put the init arg handling all at the top, and mostly in order (deviating
when one is dependent on another), and put what is effectively runtime
state declaration at the bottom.
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Message-id: 20201006235817.3280413-4-jsnow@redhat.com
Signed-off-by: John Snow <jsnow@redhat.com>
---
python/qemu/machine.py | 44 ++++++++++++++++++++++++------------------
1 file changed, 25 insertions(+), 19 deletions(-)
diff --git a/python/qemu/machine.py b/python/qemu/machine.py
index 3017ec072d..71fe58be05 100644
--- a/python/qemu/machine.py
+++ b/python/qemu/machine.py
@@ -84,42 +84,54 @@ def __init__(self, binary, args=None, wrapper=None, name=None,
@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 (overrides test_dir for sock)
- @param console_log: (optional) path to console log file
@param drain_console: (optional) True to drain console socket to buffer
+ @param console_log: (optional) path to console log file
@note: Qemu process is not started until launch() is used.
'''
+ # Direct user configuration
+
+ self._binary = binary
+
if args is None:
args = []
+ # Copy mutable input: we will be modifying our copy
+ self._args = list(args)
+
if wrapper is None:
wrapper = []
- if name is None:
- name = "qemu-%d" % os.getpid()
- if sock_dir is None:
- sock_dir = test_dir
- self._name = name
+ self._wrapper = wrapper
+
+ self._name = name or "qemu-%d" % os.getpid()
+ self._test_dir = test_dir
+ self._sock_dir = sock_dir or self._test_dir
+ self._socket_scm_helper = socket_scm_helper
+
if monitor_address is not None:
self._monitor_address = monitor_address
self._remove_monitor_sockfile = False
else:
self._monitor_address = os.path.join(
- sock_dir, f"{name}-monitor.sock"
+ self._sock_dir, f"{self._name}-monitor.sock"
)
self._remove_monitor_sockfile = True
+
+ self._console_log_path = console_log
+ if self._console_log_path:
+ # In order to log the console, buffering needs to be enabled.
+ self._drain_console = True
+ else:
+ self._drain_console = drain_console
+
+ # Runstate
self._qemu_log_path = None
self._qemu_log_file = None
self._popen = None
- self._binary = binary
- self._args = list(args) # Force copy args in case we modify them
- self._wrapper = wrapper
self._events = []
self._iolog = None
- self._socket_scm_helper = socket_scm_helper
self._qmp_set = True # Enable QMP monitor by default.
self._qmp = None
self._qemu_full_args = None
- self._test_dir = test_dir
self._temp_dir = None
- self._sock_dir = sock_dir
self._launched = False
self._machine = None
self._console_index = 0
@@ -129,12 +141,6 @@ def __init__(self, binary, args=None, wrapper=None, name=None,
self._console_socket = None
self._remove_files = []
self._user_killed = False
- self._console_log_path = console_log
- if self._console_log_path:
- # In order to log the console, buffering needs to be enabled.
- self._drain_console = True
- else:
- self._drain_console = drain_console
def __enter__(self):
return self
--
2.26.2
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PULL 05/21] python/machine.py: Don't modify state in _base_args()
2020-10-20 17:27 [PULL 00/21] Python patches John Snow
` (3 preceding siblings ...)
2020-10-20 17:27 ` [PULL 04/21] python/machine.py: reorder __init__ John Snow
@ 2020-10-20 17:27 ` John Snow
2020-10-20 17:27 ` [PULL 06/21] python/machine.py: Handle None events in events_wait John Snow
` (15 subsequent siblings)
20 siblings, 0 replies; 24+ messages in thread
From: John Snow @ 2020-10-20 17:27 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Peter Maydell, Eduardo Habkost, qemu-block,
Max Reitz, Cleber Rosa, John Snow
Don't append to the _remove_files list during _base_args; instead do so
during _launch. Rework _base_args as a @property to help facilitate
this impression.
This has the additional benefit of making the type of _console_address
easier to analyze statically.
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Message-id: 20201006235817.3280413-5-jsnow@redhat.com
Signed-off-by: John Snow <jsnow@redhat.com>
---
python/qemu/machine.py | 17 ++++++++++-------
python/qemu/qtest.py | 7 ++++---
2 files changed, 14 insertions(+), 10 deletions(-)
diff --git a/python/qemu/machine.py b/python/qemu/machine.py
index 71fe58be05..812ca7d349 100644
--- a/python/qemu/machine.py
+++ b/python/qemu/machine.py
@@ -25,7 +25,7 @@
import subprocess
import tempfile
from types import TracebackType
-from typing import Optional, Type
+from typing import List, Optional, Type
from . import console_socket, qmp
from .qmp import SocketAddrT
@@ -137,7 +137,9 @@ def __init__(self, binary, args=None, wrapper=None, name=None,
self._console_index = 0
self._console_set = False
self._console_device_type = None
- self._console_address = None
+ self._console_address = os.path.join(
+ self._sock_dir, f"{self._name}-console.sock"
+ )
self._console_socket = None
self._remove_files = []
self._user_killed = False
@@ -253,7 +255,8 @@ def _load_io_log(self):
with open(self._qemu_log_path, "r") as iolog:
self._iolog = iolog.read()
- def _base_args(self):
+ @property
+ def _base_args(self) -> List[str]:
args = ['-display', 'none', '-vga', 'none']
if self._qmp_set:
@@ -271,9 +274,6 @@ def _base_args(self):
for _ in range(self._console_index):
args.extend(['-serial', 'null'])
if self._console_set:
- self._console_address = os.path.join(self._sock_dir,
- self._name + "-console.sock")
- self._remove_files.append(self._console_address)
chardev = ('socket,id=console,path=%s,server,nowait' %
self._console_address)
args.extend(['-chardev', chardev])
@@ -289,6 +289,9 @@ def _pre_launch(self):
self._qemu_log_path = os.path.join(self._temp_dir, self._name + ".log")
self._qemu_log_file = open(self._qemu_log_path, 'wb')
+ if self._console_set:
+ self._remove_files.append(self._console_address)
+
if self._qmp_set:
if self._remove_monitor_sockfile:
assert isinstance(self._monitor_address, str)
@@ -374,7 +377,7 @@ def _launch(self):
devnull = open(os.path.devnull, 'rb')
self._pre_launch()
self._qemu_full_args = (self._wrapper + [self._binary] +
- self._base_args() + self._args)
+ self._base_args + self._args)
LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
self._popen = subprocess.Popen(self._qemu_full_args,
stdin=devnull,
diff --git a/python/qemu/qtest.py b/python/qemu/qtest.py
index 7700c0b09b..7fde2565a0 100644
--- a/python/qemu/qtest.py
+++ b/python/qemu/qtest.py
@@ -19,7 +19,7 @@
import os
import socket
-from typing import Optional, TextIO
+from typing import List, Optional, TextIO
from .machine import QEMUMachine
@@ -111,8 +111,9 @@ def __init__(self, binary, args=None, name=None, test_dir="/var/tmp",
self._qtest = None
self._qtest_path = os.path.join(sock_dir, name + "-qtest.sock")
- def _base_args(self):
- args = super()._base_args()
+ @property
+ def _base_args(self) -> List[str]:
+ args = super()._base_args
args.extend(['-qtest', 'unix:path=' + self._qtest_path,
'-accel', 'qtest'])
return args
--
2.26.2
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PULL 06/21] python/machine.py: Handle None events in events_wait
2020-10-20 17:27 [PULL 00/21] Python patches John Snow
` (4 preceding siblings ...)
2020-10-20 17:27 ` [PULL 05/21] python/machine.py: Don't modify state in _base_args() John Snow
@ 2020-10-20 17:27 ` John Snow
2020-10-20 17:27 ` [PULL 07/21] python/machine.py: use qmp.command John Snow
` (14 subsequent siblings)
20 siblings, 0 replies; 24+ messages in thread
From: John Snow @ 2020-10-20 17:27 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Peter Maydell, Eduardo Habkost, qemu-block,
Max Reitz, Cleber Rosa, John Snow
If the timeout is 0, we can get None back. Handle this explicitly.
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Message-id: 20201006235817.3280413-6-jsnow@redhat.com
Signed-off-by: John Snow <jsnow@redhat.com>
---
python/qemu/machine.py | 27 ++++++++++++++++++++-------
1 file changed, 20 insertions(+), 7 deletions(-)
diff --git a/python/qemu/machine.py b/python/qemu/machine.py
index 812ca7d349..aebfa09e9d 100644
--- a/python/qemu/machine.py
+++ b/python/qemu/machine.py
@@ -28,7 +28,7 @@
from typing import List, Optional, Type
from . import console_socket, qmp
-from .qmp import SocketAddrT
+from .qmp import QMPMessage, SocketAddrT
LOG = logging.getLogger(__name__)
@@ -604,13 +604,20 @@ def event_wait(self, name, timeout=60.0, match=None):
def events_wait(self, events, timeout=60.0):
"""
- events_wait waits for and returns a named event
- from QMP with a timeout.
+ events_wait waits for and returns a single named event from QMP.
+ In the case of multiple qualifying events, this function returns the
+ first one.
- events: a sequence of (name, match_criteria) tuples.
- The match criteria are optional and may be None.
- See event_match for details.
- timeout: QEMUMonitorProtocol.pull_event timeout parameter.
+ :param events: A sequence of (name, match_criteria) tuples.
+ The match criteria are optional and may be None.
+ See event_match for details.
+ :param timeout: Optional timeout, in seconds.
+ See QEMUMonitorProtocol.pull_event.
+
+ :raise QMPTimeoutError: If timeout was non-zero and no matching events
+ were found.
+ :return: A QMP event matching the filter criteria.
+ If timeout was 0 and no event matched, None.
"""
def _match(event):
for name, match in events:
@@ -618,6 +625,8 @@ def _match(event):
return True
return False
+ event: Optional[QMPMessage]
+
# Search cached events
for event in self._events:
if _match(event):
@@ -627,6 +636,10 @@ def _match(event):
# Poll for new events
while True:
event = self._qmp.pull_event(wait=timeout)
+ if event is None:
+ # NB: None is only returned when timeout is false-ish.
+ # Timeouts raise QMPTimeoutError instead!
+ break
if _match(event):
return event
self._events.append(event)
--
2.26.2
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PULL 07/21] python/machine.py: use qmp.command
2020-10-20 17:27 [PULL 00/21] Python patches John Snow
` (5 preceding siblings ...)
2020-10-20 17:27 ` [PULL 06/21] python/machine.py: Handle None events in events_wait John Snow
@ 2020-10-20 17:27 ` John Snow
2020-10-20 17:27 ` [PULL 08/21] python/machine.py: Add _qmp access shim John Snow
` (13 subsequent siblings)
20 siblings, 0 replies; 24+ messages in thread
From: John Snow @ 2020-10-20 17:27 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Peter Maydell, Eduardo Habkost, qemu-block,
Philippe Mathieu-Daudé,
Max Reitz, Cleber Rosa, John Snow
machine.py and qmp.py both do the same thing here; refactor machine.py
to use qmp.py's functionality more directly.
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Message-id: 20201006235817.3280413-7-jsnow@redhat.com
Signed-off-by: John Snow <jsnow@redhat.com>
---
python/qemu/machine.py | 32 ++++++++++++++++++++------------
1 file changed, 20 insertions(+), 12 deletions(-)
diff --git a/python/qemu/machine.py b/python/qemu/machine.py
index aebfa09e9d..d788e8aba8 100644
--- a/python/qemu/machine.py
+++ b/python/qemu/machine.py
@@ -25,7 +25,13 @@
import subprocess
import tempfile
from types import TracebackType
-from typing import List, Optional, Type
+from typing import (
+ Any,
+ Dict,
+ List,
+ Optional,
+ Type,
+)
from . import console_socket, qmp
from .qmp import QMPMessage, SocketAddrT
@@ -515,17 +521,23 @@ def set_qmp_monitor(self, enabled=True):
self._qmp_set = False
self._qmp = None
- def qmp(self, cmd, conv_keys=True, **args):
- """
- Invoke a QMP command and return the response dict
- """
+ @classmethod
+ def _qmp_args(cls, _conv_keys: bool = True, **args: Any) -> Dict[str, Any]:
qmp_args = dict()
for key, value in args.items():
- if conv_keys:
+ if _conv_keys:
qmp_args[key.replace('_', '-')] = value
else:
qmp_args[key] = value
+ return qmp_args
+ def qmp(self, cmd: str,
+ conv_keys: bool = True,
+ **args: Any) -> QMPMessage:
+ """
+ Invoke a QMP command and return the response dict
+ """
+ qmp_args = self._qmp_args(conv_keys, **args)
return self._qmp.cmd(cmd, args=qmp_args)
def command(self, cmd, conv_keys=True, **args):
@@ -534,12 +546,8 @@ def command(self, cmd, conv_keys=True, **args):
On success return the response dict.
On failure raise an exception.
"""
- reply = self.qmp(cmd, conv_keys, **args)
- if reply is None:
- raise qmp.QMPError("Monitor is closed")
- if "error" in reply:
- raise qmp.QMPResponseError(reply)
- return reply["return"]
+ qmp_args = self._qmp_args(conv_keys, **args)
+ return self._qmp.command(cmd, **qmp_args)
def get_qmp_event(self, wait=False):
"""
--
2.26.2
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PULL 08/21] python/machine.py: Add _qmp access shim
2020-10-20 17:27 [PULL 00/21] Python patches John Snow
` (6 preceding siblings ...)
2020-10-20 17:27 ` [PULL 07/21] python/machine.py: use qmp.command John Snow
@ 2020-10-20 17:27 ` John Snow
2020-10-20 17:27 ` [PULL 09/21] python/machine.py: fix _popen access John Snow
` (12 subsequent siblings)
20 siblings, 0 replies; 24+ messages in thread
From: John Snow @ 2020-10-20 17:27 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Peter Maydell, Eduardo Habkost, qemu-block,
Max Reitz, Cleber Rosa, John Snow
Like many other Optional[] types, it's not always a given that this
object will be set. Wrap it in a type-shim that raises a meaningful
error and will always return a concrete type.
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Message-id: 20201006235817.3280413-8-jsnow@redhat.com
Signed-off-by: John Snow <jsnow@redhat.com>
---
python/qemu/machine.py | 24 +++++++++++++-----------
1 file changed, 13 insertions(+), 11 deletions(-)
diff --git a/python/qemu/machine.py b/python/qemu/machine.py
index d788e8aba8..3e9cf09fd2 100644
--- a/python/qemu/machine.py
+++ b/python/qemu/machine.py
@@ -135,7 +135,7 @@ def __init__(self, binary, args=None, wrapper=None, name=None,
self._events = []
self._iolog = None
self._qmp_set = True # Enable QMP monitor by default.
- self._qmp = None
+ self._qmp_connection: Optional[qmp.QEMUMonitorProtocol] = None
self._qemu_full_args = None
self._temp_dir = None
self._launched = False
@@ -302,14 +302,14 @@ def _pre_launch(self):
if self._remove_monitor_sockfile:
assert isinstance(self._monitor_address, str)
self._remove_files.append(self._monitor_address)
- self._qmp = qmp.QEMUMonitorProtocol(
+ self._qmp_connection = qmp.QEMUMonitorProtocol(
self._monitor_address,
server=True,
nickname=self._name
)
def _post_launch(self):
- if self._qmp:
+ if self._qmp_connection:
self._qmp.accept()
def _post_shutdown(self):
@@ -320,9 +320,9 @@ def _post_shutdown(self):
# Comprehensive reset for the failed launch case:
self._early_cleanup()
- if self._qmp:
+ if self._qmp_connection:
self._qmp.close()
- self._qmp = None
+ self._qmp_connection = None
self._load_io_log()
@@ -434,7 +434,7 @@ def _soft_shutdown(self, timeout: Optional[int],
"""
self._early_cleanup()
- if self._qmp is not None:
+ if self._qmp_connection:
if not has_quit:
# Might raise ConnectionReset
self._qmp.cmd('quit')
@@ -515,11 +515,13 @@ def set_qmp_monitor(self, enabled=True):
line. Default is True.
@note: call this function before launch().
"""
- if enabled:
- self._qmp_set = True
- else:
- self._qmp_set = False
- self._qmp = None
+ self._qmp_set = enabled
+
+ @property
+ def _qmp(self) -> qmp.QEMUMonitorProtocol:
+ if self._qmp_connection is None:
+ raise QEMUMachineError("Attempt to access QMP with no connection")
+ return self._qmp_connection
@classmethod
def _qmp_args(cls, _conv_keys: bool = True, **args: Any) -> Dict[str, Any]:
--
2.26.2
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PULL 09/21] python/machine.py: fix _popen access
2020-10-20 17:27 [PULL 00/21] Python patches John Snow
` (7 preceding siblings ...)
2020-10-20 17:27 ` [PULL 08/21] python/machine.py: Add _qmp access shim John Snow
@ 2020-10-20 17:27 ` John Snow
2020-10-20 17:27 ` [PULL 10/21] python/qemu: make 'args' style arguments immutable John Snow
` (11 subsequent siblings)
20 siblings, 0 replies; 24+ messages in thread
From: John Snow @ 2020-10-20 17:27 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Peter Maydell, Eduardo Habkost, qemu-block,
Max Reitz, Cleber Rosa, John Snow
As always, Optional[T] causes problems with unchecked access. Add a
helper that asserts the pipe is present before we attempt to talk with
it.
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Message-id: 20201006235817.3280413-9-jsnow@redhat.com
Signed-off-by: John Snow <jsnow@redhat.com>
---
python/qemu/machine.py | 16 +++++++++++-----
1 file changed, 11 insertions(+), 5 deletions(-)
diff --git a/python/qemu/machine.py b/python/qemu/machine.py
index 3e9cf09fd2..4e762fcd52 100644
--- a/python/qemu/machine.py
+++ b/python/qemu/machine.py
@@ -131,7 +131,7 @@ def __init__(self, binary, args=None, wrapper=None, name=None,
# Runstate
self._qemu_log_path = None
self._qemu_log_file = None
- self._popen = None
+ self._popen: Optional['subprocess.Popen[bytes]'] = None
self._events = []
self._iolog = None
self._qmp_set = True # Enable QMP monitor by default.
@@ -244,6 +244,12 @@ def is_running(self):
"""Returns true if the VM is running."""
return self._popen is not None and self._popen.poll() is None
+ @property
+ def _subp(self) -> 'subprocess.Popen[bytes]':
+ if self._popen is None:
+ raise QEMUMachineError('Subprocess pipe not present')
+ return self._popen
+
def exitcode(self):
"""Returns the exit code if possible, or None."""
if self._popen is None:
@@ -254,7 +260,7 @@ def get_pid(self):
"""Returns the PID of the running process, or None."""
if not self.is_running():
return None
- return self._popen.pid
+ return self._subp.pid
def _load_io_log(self):
if self._qemu_log_path is not None:
@@ -415,8 +421,8 @@ def _hard_shutdown(self) -> None:
waiting for the QEMU process to terminate.
"""
self._early_cleanup()
- self._popen.kill()
- self._popen.wait(timeout=60)
+ self._subp.kill()
+ self._subp.wait(timeout=60)
def _soft_shutdown(self, timeout: Optional[int],
has_quit: bool = False) -> None:
@@ -440,7 +446,7 @@ def _soft_shutdown(self, timeout: Optional[int],
self._qmp.cmd('quit')
# May raise subprocess.TimeoutExpired
- self._popen.wait(timeout=timeout)
+ self._subp.wait(timeout=timeout)
def _do_shutdown(self, timeout: Optional[int],
has_quit: bool = False) -> None:
--
2.26.2
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PULL 10/21] python/qemu: make 'args' style arguments immutable
2020-10-20 17:27 [PULL 00/21] Python patches John Snow
` (8 preceding siblings ...)
2020-10-20 17:27 ` [PULL 09/21] python/machine.py: fix _popen access John Snow
@ 2020-10-20 17:27 ` John Snow
2020-10-20 17:27 ` [PULL 11/21] iotests.py: Adjust HMP kwargs typing John Snow
` (10 subsequent siblings)
20 siblings, 0 replies; 24+ messages in thread
From: John Snow @ 2020-10-20 17:27 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Peter Maydell, Eduardo Habkost, qemu-block,
Philippe Mathieu-Daudé,
Max Reitz, Cleber Rosa, John Snow
These arguments don't need to be mutable and aren't really used as
such. Clarify their types as immutable and adjust code to match where
necessary.
In general, It's probably best not to accept a user-defined mutable
object and store it as internal object state unless there's a strong
justification for doing so. Instead, try to use generic types as input
with empty tuples as the default, and coerce to list where necessary.
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Message-id: 20201006235817.3280413-10-jsnow@redhat.com
Signed-off-by: John Snow <jsnow@redhat.com>
---
python/qemu/machine.py | 30 +++++++++++++++++-------------
python/qemu/qtest.py | 22 +++++++++++++++++-----
2 files changed, 34 insertions(+), 18 deletions(-)
diff --git a/python/qemu/machine.py b/python/qemu/machine.py
index 4e762fcd52..e599cb7439 100644
--- a/python/qemu/machine.py
+++ b/python/qemu/machine.py
@@ -18,6 +18,7 @@
#
import errno
+from itertools import chain
import logging
import os
import shutil
@@ -30,6 +31,8 @@
Dict,
List,
Optional,
+ Sequence,
+ Tuple,
Type,
)
@@ -74,8 +77,12 @@ class QEMUMachine:
# vm is guaranteed to be shut down here
"""
- def __init__(self, binary, args=None, wrapper=None, name=None,
- test_dir="/var/tmp",
+ def __init__(self,
+ binary: str,
+ args: Sequence[str] = (),
+ wrapper: Sequence[str] = (),
+ name: Optional[str] = None,
+ test_dir: str = "/var/tmp",
monitor_address: Optional[SocketAddrT] = None,
socket_scm_helper=None, sock_dir=None,
drain_console=False, console_log=None):
@@ -97,14 +104,7 @@ def __init__(self, binary, args=None, wrapper=None, name=None,
# Direct user configuration
self._binary = binary
-
- if args is None:
- args = []
- # Copy mutable input: we will be modifying our copy
self._args = list(args)
-
- if wrapper is None:
- wrapper = []
self._wrapper = wrapper
self._name = name or "qemu-%d" % os.getpid()
@@ -136,7 +136,7 @@ def __init__(self, binary, args=None, wrapper=None, name=None,
self._iolog = None
self._qmp_set = True # Enable QMP monitor by default.
self._qmp_connection: Optional[qmp.QEMUMonitorProtocol] = None
- self._qemu_full_args = None
+ self._qemu_full_args: Tuple[str, ...] = ()
self._temp_dir = None
self._launched = False
self._machine = None
@@ -368,7 +368,7 @@ def launch(self):
raise QEMUMachineError('VM already launched')
self._iolog = None
- self._qemu_full_args = None
+ self._qemu_full_args = ()
try:
self._launch()
self._launched = True
@@ -388,8 +388,12 @@ def _launch(self):
"""
devnull = open(os.path.devnull, 'rb')
self._pre_launch()
- self._qemu_full_args = (self._wrapper + [self._binary] +
- self._base_args + self._args)
+ self._qemu_full_args = tuple(
+ chain(self._wrapper,
+ [self._binary],
+ self._base_args,
+ self._args)
+ )
LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
self._popen = subprocess.Popen(self._qemu_full_args,
stdin=devnull,
diff --git a/python/qemu/qtest.py b/python/qemu/qtest.py
index 7fde2565a0..675310d8df 100644
--- a/python/qemu/qtest.py
+++ b/python/qemu/qtest.py
@@ -19,7 +19,12 @@
import os
import socket
-from typing import List, Optional, TextIO
+from typing import (
+ List,
+ Optional,
+ Sequence,
+ TextIO,
+)
from .machine import QEMUMachine
@@ -99,8 +104,13 @@ class QEMUQtestMachine(QEMUMachine):
A QEMU VM, with a qtest socket available.
"""
- def __init__(self, binary, args=None, name=None, test_dir="/var/tmp",
- socket_scm_helper=None, sock_dir=None):
+ def __init__(self,
+ binary: str,
+ args: Sequence[str] = (),
+ name: Optional[str] = None,
+ test_dir: str = "/var/tmp",
+ socket_scm_helper: Optional[str] = None,
+ sock_dir: Optional[str] = None):
if name is None:
name = "qemu-%d" % os.getpid()
if sock_dir is None:
@@ -114,8 +124,10 @@ def __init__(self, binary, args=None, name=None, test_dir="/var/tmp",
@property
def _base_args(self) -> List[str]:
args = super()._base_args
- args.extend(['-qtest', 'unix:path=' + self._qtest_path,
- '-accel', 'qtest'])
+ args.extend([
+ '-qtest', f"unix:path={self._qtest_path}",
+ '-accel', 'qtest'
+ ])
return args
def _pre_launch(self):
--
2.26.2
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PULL 11/21] iotests.py: Adjust HMP kwargs typing
2020-10-20 17:27 [PULL 00/21] Python patches John Snow
` (9 preceding siblings ...)
2020-10-20 17:27 ` [PULL 10/21] python/qemu: make 'args' style arguments immutable John Snow
@ 2020-10-20 17:27 ` John Snow
2020-10-20 17:27 ` [PULL 12/21] python/qemu: Add mypy type annotations John Snow
` (9 subsequent siblings)
20 siblings, 0 replies; 24+ messages in thread
From: John Snow @ 2020-10-20 17:27 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Peter Maydell, Eduardo Habkost, qemu-block,
Max Reitz, Cleber Rosa, John Snow
mypy wants to ensure there's consistency between the kwargs arguments
types and any unspecified keyword arguments. In this case, conv_keys is
a bool, but the remaining keys are Any type. Mypy (correctly) infers the
**kwargs type to be **Dict[str, str], which is not compatible with
conv_keys: bool.
Because QMP typing is a little fraught right now anyway, re-type kwargs
to Dict[str, Any] which has the benefit of silencing this check right
now.
A future re-design might type these more aggressively, but this will
give us a baseline to work from with minimal disruption.
(Thanks Kevin Wolf for the debugging assist here)
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Message-id: 20201006235817.3280413-11-jsnow@redhat.com
Signed-off-by: John Snow <jsnow@redhat.com>
---
tests/qemu-iotests/iotests.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py
index f212cec446..63d2ace93c 100644
--- a/tests/qemu-iotests/iotests.py
+++ b/tests/qemu-iotests/iotests.py
@@ -605,7 +605,7 @@ def add_incoming(self, addr):
def hmp(self, command_line: str, use_log: bool = False) -> QMPMessage:
cmd = 'human-monitor-command'
- kwargs = {'command-line': command_line}
+ kwargs: Dict[str, Any] = {'command-line': command_line}
if use_log:
return self.qmp_log(cmd, **kwargs)
else:
--
2.26.2
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PULL 12/21] python/qemu: Add mypy type annotations
2020-10-20 17:27 [PULL 00/21] Python patches John Snow
` (10 preceding siblings ...)
2020-10-20 17:27 ` [PULL 11/21] iotests.py: Adjust HMP kwargs typing John Snow
@ 2020-10-20 17:27 ` John Snow
2020-10-20 17:27 ` [PULL 13/21] python/qemu/console_socket.py: Correct type of recv() John Snow
` (8 subsequent siblings)
20 siblings, 0 replies; 24+ messages in thread
From: John Snow @ 2020-10-20 17:27 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Peter Maydell, Eduardo Habkost, qemu-block,
Max Reitz, Cleber Rosa, John Snow
These should all be purely annotations with no changes in behavior at
all. You need to be in the python folder, but you should be able to
confirm that these annotations are correct (or at least self-consistent)
by running `mypy --strict qemu`.
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Message-id: 20201006235817.3280413-12-jsnow@redhat.com
Signed-off-by: John Snow <jsnow@redhat.com>
---
python/qemu/accel.py | 8 ++--
python/qemu/machine.py | 98 ++++++++++++++++++++++++------------------
python/qemu/qmp.py | 44 +++++++++++--------
python/qemu/qtest.py | 26 ++++++-----
4 files changed, 101 insertions(+), 75 deletions(-)
diff --git a/python/qemu/accel.py b/python/qemu/accel.py
index 3ec6bdcfdb..297933df2a 100644
--- a/python/qemu/accel.py
+++ b/python/qemu/accel.py
@@ -17,6 +17,7 @@
import logging
import os
import subprocess
+from typing import List, Optional
LOG = logging.getLogger(__name__)
@@ -30,7 +31,7 @@
}
-def list_accel(qemu_bin):
+def list_accel(qemu_bin: str) -> List[str]:
"""
List accelerators enabled in the QEMU binary.
@@ -50,7 +51,8 @@ def list_accel(qemu_bin):
return [acc.strip() for acc in out.splitlines()[1:]]
-def kvm_available(target_arch=None, qemu_bin=None):
+def kvm_available(target_arch: Optional[str] = None,
+ qemu_bin: Optional[str] = None) -> bool:
"""
Check if KVM is available using the following heuristic:
- Kernel module is present in the host;
@@ -73,7 +75,7 @@ def kvm_available(target_arch=None, qemu_bin=None):
return True
-def tcg_available(qemu_bin):
+def tcg_available(qemu_bin: str) -> bool:
"""
Check if TCG is available.
diff --git a/python/qemu/machine.py b/python/qemu/machine.py
index e599cb7439..6420f01bed 100644
--- a/python/qemu/machine.py
+++ b/python/qemu/machine.py
@@ -23,11 +23,13 @@
import os
import shutil
import signal
+import socket
import subprocess
import tempfile
from types import TracebackType
from typing import (
Any,
+ BinaryIO,
Dict,
List,
Optional,
@@ -37,7 +39,7 @@
)
from . import console_socket, qmp
-from .qmp import QMPMessage, SocketAddrT
+from .qmp import QMPMessage, QMPReturnValue, SocketAddrT
LOG = logging.getLogger(__name__)
@@ -67,7 +69,7 @@ class AbnormalShutdown(QEMUMachineError):
class QEMUMachine:
"""
- A QEMU VM
+ A QEMU VM.
Use this object as a context manager to ensure
the QEMU process terminates::
@@ -84,8 +86,10 @@ def __init__(self,
name: Optional[str] = None,
test_dir: str = "/var/tmp",
monitor_address: Optional[SocketAddrT] = None,
- socket_scm_helper=None, sock_dir=None,
- drain_console=False, console_log=None):
+ socket_scm_helper: Optional[str] = None,
+ sock_dir: Optional[str] = None,
+ drain_console: bool = False,
+ console_log: Optional[str] = None):
'''
Initialize a QEMUMachine
@@ -129,28 +133,28 @@ def __init__(self,
self._drain_console = drain_console
# Runstate
- self._qemu_log_path = None
- self._qemu_log_file = None
+ self._qemu_log_path: Optional[str] = None
+ self._qemu_log_file: Optional[BinaryIO] = None
self._popen: Optional['subprocess.Popen[bytes]'] = None
- self._events = []
- self._iolog = None
+ self._events: List[QMPMessage] = []
+ self._iolog: Optional[str] = None
self._qmp_set = True # Enable QMP monitor by default.
self._qmp_connection: Optional[qmp.QEMUMonitorProtocol] = None
self._qemu_full_args: Tuple[str, ...] = ()
- self._temp_dir = None
+ self._temp_dir: Optional[str] = None
self._launched = False
- self._machine = None
+ self._machine: Optional[str] = None
self._console_index = 0
self._console_set = False
- self._console_device_type = None
+ self._console_device_type: Optional[str] = None
self._console_address = os.path.join(
self._sock_dir, f"{self._name}-console.sock"
)
- self._console_socket = None
- self._remove_files = []
+ self._console_socket: Optional[socket.socket] = None
+ self._remove_files: List[str] = []
self._user_killed = False
- def __enter__(self):
+ def __enter__(self) -> 'QEMUMachine':
return self
def __exit__(self,
@@ -159,14 +163,15 @@ def __exit__(self,
exc_tb: Optional[TracebackType]) -> None:
self.shutdown()
- def add_monitor_null(self):
+ def add_monitor_null(self) -> None:
"""
This can be used to add an unused monitor instance.
"""
self._args.append('-monitor')
self._args.append('null')
- def add_fd(self, fd, fdset, opaque, opts=''):
+ def add_fd(self, fd: int, fdset: int,
+ opaque: str, opts: str = '') -> 'QEMUMachine':
"""
Pass a file descriptor to the VM
"""
@@ -185,7 +190,8 @@ def add_fd(self, fd, fdset, opaque, opts=''):
self._args.append(','.join(options))
return self
- def send_fd_scm(self, fd=None, file_path=None):
+ 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.
@@ -229,7 +235,7 @@ def send_fd_scm(self, fd=None, file_path=None):
return proc.returncode
@staticmethod
- def _remove_if_exists(path):
+ def _remove_if_exists(path: str) -> None:
"""
Remove file object at path if it exists
"""
@@ -240,7 +246,7 @@ def _remove_if_exists(path):
return
raise
- def is_running(self):
+ def is_running(self) -> bool:
"""Returns true if the VM is running."""
return self._popen is not None and self._popen.poll() is None
@@ -250,19 +256,19 @@ def _subp(self) -> 'subprocess.Popen[bytes]':
raise QEMUMachineError('Subprocess pipe not present')
return self._popen
- def exitcode(self):
+ def exitcode(self) -> Optional[int]:
"""Returns the exit code if possible, or None."""
if self._popen is None:
return None
return self._popen.poll()
- def get_pid(self):
+ def get_pid(self) -> Optional[int]:
"""Returns the PID of the running process, or None."""
if not self.is_running():
return None
return self._subp.pid
- def _load_io_log(self):
+ def _load_io_log(self) -> None:
if self._qemu_log_path is not None:
with open(self._qemu_log_path, "r") as iolog:
self._iolog = iolog.read()
@@ -296,7 +302,7 @@ def _base_args(self) -> List[str]:
args.extend(['-device', device])
return args
- def _pre_launch(self):
+ def _pre_launch(self) -> None:
self._temp_dir = tempfile.mkdtemp(dir=self._test_dir)
self._qemu_log_path = os.path.join(self._temp_dir, self._name + ".log")
self._qemu_log_file = open(self._qemu_log_path, 'wb')
@@ -314,11 +320,11 @@ def _pre_launch(self):
nickname=self._name
)
- def _post_launch(self):
+ def _post_launch(self) -> None:
if self._qmp_connection:
self._qmp.accept()
- def _post_shutdown(self):
+ def _post_shutdown(self) -> None:
"""
Called to cleanup the VM instance after the process has exited.
May also be called after a failed launch.
@@ -358,7 +364,7 @@ def _post_shutdown(self):
self._user_killed = False
self._launched = False
- def launch(self):
+ def launch(self) -> None:
"""
Launch the VM and make sure we cleanup and expose the
command line/output in case of exception
@@ -382,7 +388,7 @@ def launch(self):
LOG.debug('Output: %r', self._iolog)
raise
- def _launch(self):
+ def _launch(self) -> None:
"""
Launch the VM and establish a QMP connection
"""
@@ -501,7 +507,7 @@ def shutdown(self, has_quit: bool = False,
finally:
self._post_shutdown()
- def kill(self):
+ def kill(self) -> None:
"""
Terminate the VM forcefully, wait for it to exit, and perform cleanup.
"""
@@ -516,7 +522,7 @@ def wait(self, timeout: Optional[int] = 30) -> None:
"""
self.shutdown(has_quit=True, timeout=timeout)
- def set_qmp_monitor(self, enabled=True):
+ def set_qmp_monitor(self, enabled: bool = True) -> None:
"""
Set the QMP monitor.
@@ -552,7 +558,9 @@ def qmp(self, cmd: str,
qmp_args = self._qmp_args(conv_keys, **args)
return self._qmp.cmd(cmd, args=qmp_args)
- def command(self, cmd, conv_keys=True, **args):
+ def command(self, cmd: str,
+ conv_keys: bool = True,
+ **args: Any) -> QMPReturnValue:
"""
Invoke a QMP command.
On success return the response dict.
@@ -561,7 +569,7 @@ def command(self, cmd, conv_keys=True, **args):
qmp_args = self._qmp_args(conv_keys, **args)
return self._qmp.command(cmd, **qmp_args)
- def get_qmp_event(self, wait=False):
+ def get_qmp_event(self, wait: bool = False) -> Optional[QMPMessage]:
"""
Poll for one queued QMP events and return it
"""
@@ -569,7 +577,7 @@ def get_qmp_event(self, wait=False):
return self._events.pop(0)
return self._qmp.pull_event(wait=wait)
- def get_qmp_events(self, wait=False):
+ def get_qmp_events(self, wait: bool = False) -> List[QMPMessage]:
"""
Poll for queued QMP events and return a list of dicts
"""
@@ -580,7 +588,7 @@ def get_qmp_events(self, wait=False):
return events
@staticmethod
- def event_match(event, match=None):
+ def event_match(event: Any, match: Optional[Any]) -> bool:
"""
Check if an event matches optional match criteria.
@@ -610,9 +618,11 @@ def event_match(event, match=None):
return True
except TypeError:
# either match or event wasn't iterable (not a dict)
- return match == event
+ return bool(match == event)
- def event_wait(self, name, timeout=60.0, match=None):
+ def event_wait(self, name: str,
+ timeout: float = 60.0,
+ match: Optional[QMPMessage] = None) -> Optional[QMPMessage]:
"""
event_wait waits for and returns a named event from QMP with a timeout.
@@ -622,7 +632,9 @@ def event_wait(self, name, timeout=60.0, match=None):
"""
return self.events_wait([(name, match)], timeout)
- def events_wait(self, events, timeout=60.0):
+ def events_wait(self,
+ events: Sequence[Tuple[str, Any]],
+ timeout: float = 60.0) -> Optional[QMPMessage]:
"""
events_wait waits for and returns a single named event from QMP.
In the case of multiple qualifying events, this function returns the
@@ -639,7 +651,7 @@ def events_wait(self, events, timeout=60.0):
:return: A QMP event matching the filter criteria.
If timeout was 0 and no event matched, None.
"""
- def _match(event):
+ def _match(event: QMPMessage) -> bool:
for name, match in events:
if event['event'] == name and self.event_match(event, match):
return True
@@ -666,20 +678,20 @@ def _match(event):
return None
- def get_log(self):
+ def get_log(self) -> Optional[str]:
"""
After self.shutdown or failed qemu execution, this returns the output
of the qemu process.
"""
return self._iolog
- def add_args(self, *args):
+ def add_args(self, *args: str) -> None:
"""
Adds to the list of extra arguments to be given to the QEMU binary
"""
self._args.extend(args)
- def set_machine(self, machine_type):
+ def set_machine(self, machine_type: str) -> None:
"""
Sets the machine type
@@ -688,7 +700,9 @@ def set_machine(self, machine_type):
"""
self._machine = machine_type
- def set_console(self, device_type=None, console_index=0):
+ def set_console(self,
+ device_type: Optional[str] = None,
+ console_index: int = 0) -> None:
"""
Sets the device type for a console device
@@ -719,7 +733,7 @@ def set_console(self, device_type=None, console_index=0):
self._console_index = console_index
@property
- def console_socket(self):
+ def console_socket(self) -> socket.socket:
"""
Returns a socket connected to the console
"""
diff --git a/python/qemu/qmp.py b/python/qemu/qmp.py
index ddf8347ac1..9223307ed8 100644
--- a/python/qemu/qmp.py
+++ b/python/qemu/qmp.py
@@ -15,6 +15,7 @@
from typing import (
Any,
Dict,
+ List,
Optional,
TextIO,
Tuple,
@@ -90,7 +91,9 @@ class QEMUMonitorProtocol:
#: Logger object for debugging messages
logger = logging.getLogger('QMP')
- def __init__(self, address, server=False, nickname=None):
+ def __init__(self, address: SocketAddrT,
+ server: bool = False,
+ nickname: Optional[str] = None):
"""
Create a QEMUMonitorProtocol class.
@@ -102,7 +105,7 @@ def __init__(self, address, server=False, nickname=None):
@note No connection is established, this is done by the connect() or
accept() methods
"""
- self.__events = []
+ self.__events: List[QMPMessage] = []
self.__address = address
self.__sock = self.__get_sock()
self.__sockfile: Optional[TextIO] = None
@@ -114,14 +117,14 @@ def __init__(self, address, server=False, nickname=None):
self.__sock.bind(self.__address)
self.__sock.listen(1)
- def __get_sock(self):
+ def __get_sock(self) -> socket.socket:
if isinstance(self.__address, tuple):
family = socket.AF_INET
else:
family = socket.AF_UNIX
return socket.socket(family, socket.SOCK_STREAM)
- def __negotiate_capabilities(self):
+ def __negotiate_capabilities(self) -> QMPMessage:
greeting = self.__json_read()
if greeting is None or "QMP" not in greeting:
raise QMPConnectError
@@ -131,7 +134,7 @@ def __negotiate_capabilities(self):
return greeting
raise QMPCapabilitiesError
- def __json_read(self, only_event=False):
+ def __json_read(self, only_event: bool = False) -> Optional[QMPMessage]:
assert self.__sockfile is not None
while True:
data = self.__sockfile.readline()
@@ -148,7 +151,7 @@ def __json_read(self, only_event=False):
continue
return resp
- def __get_events(self, wait=False):
+ def __get_events(self, wait: Union[bool, float] = False) -> None:
"""
Check for new events in the stream and cache them in __events.
@@ -186,7 +189,7 @@ def __get_events(self, wait=False):
raise QMPConnectError("Error while reading from socket")
self.__sock.settimeout(None)
- def __enter__(self):
+ def __enter__(self) -> 'QEMUMonitorProtocol':
# Implement context manager enter function.
return self
@@ -199,7 +202,7 @@ def __exit__(self,
# Implement context manager exit function.
self.close()
- def connect(self, negotiate=True):
+ def connect(self, negotiate: bool = True) -> Optional[QMPMessage]:
"""
Connect to the QMP Monitor and perform capabilities negotiation.
@@ -214,7 +217,7 @@ def connect(self, negotiate=True):
return self.__negotiate_capabilities()
return None
- def accept(self, timeout=15.0):
+ def accept(self, timeout: float = 15.0) -> QMPMessage:
"""
Await connection from QMP Monitor and perform capabilities negotiation.
@@ -250,7 +253,9 @@ def cmd_obj(self, qmp_cmd: QMPMessage) -> QMPMessage:
self.logger.debug("<<< %s", resp)
return resp
- def cmd(self, name, args=None, cmd_id=None):
+ def cmd(self, name: str,
+ args: Optional[Dict[str, Any]] = None,
+ cmd_id: Optional[Any] = None) -> QMPMessage:
"""
Build a QMP command and send it to the QMP Monitor.
@@ -258,14 +263,14 @@ def cmd(self, name, args=None, cmd_id=None):
@param args: command arguments (dict)
@param cmd_id: command id (dict, list, string or int)
"""
- qmp_cmd = {'execute': name}
+ qmp_cmd: QMPMessage = {'execute': name}
if args:
qmp_cmd['arguments'] = args
if cmd_id:
qmp_cmd['id'] = cmd_id
return self.cmd_obj(qmp_cmd)
- def command(self, cmd, **kwds):
+ def command(self, cmd: str, **kwds: Any) -> QMPReturnValue:
"""
Build and send a QMP command to the monitor, report errors if any
"""
@@ -278,7 +283,8 @@ def command(self, cmd, **kwds):
)
return cast(QMPReturnValue, ret['return'])
- def pull_event(self, wait=False):
+ def pull_event(self,
+ wait: Union[bool, float] = False) -> Optional[QMPMessage]:
"""
Pulls a single event.
@@ -298,7 +304,7 @@ def pull_event(self, wait=False):
return self.__events.pop(0)
return None
- def get_events(self, wait=False):
+ def get_events(self, wait: bool = False) -> List[QMPMessage]:
"""
Get a list of available QMP events.
@@ -315,13 +321,13 @@ def get_events(self, wait=False):
self.__get_events(wait)
return self.__events
- def clear_events(self):
+ def clear_events(self) -> None:
"""
Clear current list of pending events.
"""
self.__events = []
- def close(self):
+ def close(self) -> None:
"""
Close the socket and socket file.
"""
@@ -330,7 +336,7 @@ def close(self):
if self.__sockfile:
self.__sockfile.close()
- def settimeout(self, timeout):
+ def settimeout(self, timeout: float) -> None:
"""
Set the socket timeout.
@@ -339,7 +345,7 @@ def settimeout(self, timeout):
"""
self.__sock.settimeout(timeout)
- def get_sock_fd(self):
+ def get_sock_fd(self) -> int:
"""
Get the socket file descriptor.
@@ -347,7 +353,7 @@ def get_sock_fd(self):
"""
return self.__sock.fileno()
- def is_scm_available(self):
+ def is_scm_available(self) -> bool:
"""
Check if the socket allows for SCM_RIGHTS.
diff --git a/python/qemu/qtest.py b/python/qemu/qtest.py
index 675310d8df..39a0cf62fe 100644
--- a/python/qemu/qtest.py
+++ b/python/qemu/qtest.py
@@ -27,6 +27,7 @@
)
from .machine import QEMUMachine
+from .qmp import SocketAddrT
class QEMUQtestProtocol:
@@ -43,7 +44,8 @@ class QEMUQtestProtocol:
No conection is estabalished by __init__(), this is done
by the connect() or accept() methods.
"""
- def __init__(self, address, server=False):
+ def __init__(self, address: SocketAddrT,
+ server: bool = False):
self._address = address
self._sock = self._get_sock()
self._sockfile: Optional[TextIO] = None
@@ -51,14 +53,14 @@ def __init__(self, address, server=False):
self._sock.bind(self._address)
self._sock.listen(1)
- def _get_sock(self):
+ def _get_sock(self) -> socket.socket:
if isinstance(self._address, tuple):
family = socket.AF_INET
else:
family = socket.AF_UNIX
return socket.socket(family, socket.SOCK_STREAM)
- def connect(self):
+ def connect(self) -> None:
"""
Connect to the qtest socket.
@@ -67,7 +69,7 @@ def connect(self):
self._sock.connect(self._address)
self._sockfile = self._sock.makefile(mode='r')
- def accept(self):
+ def accept(self) -> None:
"""
Await connection from QEMU.
@@ -76,7 +78,7 @@ def accept(self):
self._sock, _ = self._sock.accept()
self._sockfile = self._sock.makefile(mode='r')
- def cmd(self, qtest_cmd):
+ def cmd(self, qtest_cmd: str) -> str:
"""
Send a qtest command on the wire.
@@ -87,14 +89,16 @@ def cmd(self, qtest_cmd):
resp = self._sockfile.readline()
return resp
- def close(self):
- """Close this socket."""
+ def close(self) -> None:
+ """
+ Close this socket.
+ """
self._sock.close()
if self._sockfile:
self._sockfile.close()
self._sockfile = None
- def settimeout(self, timeout):
+ def settimeout(self, timeout: Optional[float]) -> None:
"""Set a timeout, in seconds."""
self._sock.settimeout(timeout)
@@ -118,7 +122,7 @@ def __init__(self,
super().__init__(binary, args, name=name, test_dir=test_dir,
socket_scm_helper=socket_scm_helper,
sock_dir=sock_dir)
- self._qtest = None
+ self._qtest: Optional[QEMUQtestProtocol] = None
self._qtest_path = os.path.join(sock_dir, name + "-qtest.sock")
@property
@@ -130,7 +134,7 @@ def _base_args(self) -> List[str]:
])
return args
- def _pre_launch(self):
+ def _pre_launch(self) -> None:
super()._pre_launch()
self._qtest = QEMUQtestProtocol(self._qtest_path, server=True)
@@ -139,7 +143,7 @@ def _post_launch(self) -> None:
super()._post_launch()
self._qtest.accept()
- def _post_shutdown(self):
+ def _post_shutdown(self) -> None:
super()._post_shutdown()
self._remove_if_exists(self._qtest_path)
--
2.26.2
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PULL 13/21] python/qemu/console_socket.py: Correct type of recv()
2020-10-20 17:27 [PULL 00/21] Python patches John Snow
` (11 preceding siblings ...)
2020-10-20 17:27 ` [PULL 12/21] python/qemu: Add mypy type annotations John Snow
@ 2020-10-20 17:27 ` John Snow
2020-10-20 17:27 ` [PULL 14/21] python/qemu/console_socket.py: fix typing of settimeout John Snow
` (7 subsequent siblings)
20 siblings, 0 replies; 24+ messages in thread
From: John Snow @ 2020-10-20 17:27 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Peter Maydell, Eduardo Habkost, qemu-block,
Max Reitz, Cleber Rosa, John Snow
The type and parameter names of recv() should match socket.socket().
OK, easy enough, but in the cases we don't pass straight through to the
real socket implementation, we probably can't accept such flags. OK, for
now, assert that we don't receive flags in such cases.
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Message-id: 20201006235817.3280413-13-jsnow@redhat.com
Signed-off-by: John Snow <jsnow@redhat.com>
---
python/qemu/console_socket.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/python/qemu/console_socket.py b/python/qemu/console_socket.py
index 69f604c77f..cb3400a038 100644
--- a/python/qemu/console_socket.py
+++ b/python/qemu/console_socket.py
@@ -92,13 +92,14 @@ def _drain_socket(self):
for c in string:
self._buffer.extend(c)
- def recv(self, bufsize=1):
+ def recv(self, bufsize: int = 1, flags: int = 0) -> bytes:
"""Return chars from in memory buffer.
Maintains the same API as socket.socket.recv.
"""
if self._drain_thread is None:
# Not buffering the socket, pass thru to socket.
- return socket.socket.recv(self, bufsize)
+ return socket.socket.recv(self, bufsize, flags)
+ assert not flags, "Cannot pass flags to recv() in drained mode"
start_time = time.time()
while len(self._buffer) < bufsize:
time.sleep(self._sleep_time)
--
2.26.2
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PULL 14/21] python/qemu/console_socket.py: fix typing of settimeout
2020-10-20 17:27 [PULL 00/21] Python patches John Snow
` (12 preceding siblings ...)
2020-10-20 17:27 ` [PULL 13/21] python/qemu/console_socket.py: Correct type of recv() John Snow
@ 2020-10-20 17:27 ` John Snow
2020-10-20 17:27 ` [PULL 15/21] python/qemu/console_socket.py: Clarify type of drain_thread John Snow
` (6 subsequent siblings)
20 siblings, 0 replies; 24+ messages in thread
From: John Snow @ 2020-10-20 17:27 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Peter Maydell, Eduardo Habkost, qemu-block,
Max Reitz, Cleber Rosa, John Snow
The types and names of the parameters must match the socket.socket interface.
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Message-id: 20201006235817.3280413-14-jsnow@redhat.com
Signed-off-by: John Snow <jsnow@redhat.com>
---
python/qemu/console_socket.py | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/python/qemu/console_socket.py b/python/qemu/console_socket.py
index cb3400a038..3945682506 100644
--- a/python/qemu/console_socket.py
+++ b/python/qemu/console_socket.py
@@ -17,6 +17,7 @@
import socket
import threading
import time
+from typing import Optional
class ConsoleSocket(socket.socket):
@@ -31,6 +32,7 @@ class ConsoleSocket(socket.socket):
"""
def __init__(self, address, file=None, drain=False):
self._recv_timeout_sec = 300
+ self._recv_timeout_sec = 300.0
self._sleep_time = 0.5
self._buffer = deque()
socket.socket.__init__(self, socket.AF_UNIX, socket.SOCK_STREAM)
@@ -120,11 +122,11 @@ def setblocking(self, value):
if self._drain_thread is None:
socket.socket.setblocking(self, value)
- def settimeout(self, seconds):
+ def settimeout(self, value: Optional[float]) -> None:
"""When not draining we pass thru to the socket,
since when draining we control the timeout.
"""
- if seconds is not None:
- self._recv_timeout_sec = seconds
+ if value is not None:
+ self._recv_timeout_sec = value
if self._drain_thread is None:
- socket.socket.settimeout(self, seconds)
+ socket.socket.settimeout(self, value)
--
2.26.2
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PULL 15/21] python/qemu/console_socket.py: Clarify type of drain_thread
2020-10-20 17:27 [PULL 00/21] Python patches John Snow
` (13 preceding siblings ...)
2020-10-20 17:27 ` [PULL 14/21] python/qemu/console_socket.py: fix typing of settimeout John Snow
@ 2020-10-20 17:27 ` John Snow
2020-10-20 17:27 ` [PULL 16/21] python/qemu/console_socket.py: Add type hint annotations John Snow
` (5 subsequent siblings)
20 siblings, 0 replies; 24+ messages in thread
From: John Snow @ 2020-10-20 17:27 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Peter Maydell, Eduardo Habkost, qemu-block,
Max Reitz, Cleber Rosa, John Snow
Mypy needs just a little help to guess the type here.
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Message-id: 20201006235817.3280413-15-jsnow@redhat.com
Signed-off-by: John Snow <jsnow@redhat.com>
---
python/qemu/console_socket.py | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/python/qemu/console_socket.py b/python/qemu/console_socket.py
index 3945682506..d4669c441d 100644
--- a/python/qemu/console_socket.py
+++ b/python/qemu/console_socket.py
@@ -41,10 +41,9 @@ def __init__(self, address, file=None, drain=False):
if file:
self._logfile = open(file, "w")
self._open = True
+ self._drain_thread = None
if drain:
self._drain_thread = self._thread_start()
- else:
- self._drain_thread = None
def _drain_fn(self):
"""Drains the socket and runs while the socket is open."""
--
2.26.2
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PULL 16/21] python/qemu/console_socket.py: Add type hint annotations
2020-10-20 17:27 [PULL 00/21] Python patches John Snow
` (14 preceding siblings ...)
2020-10-20 17:27 ` [PULL 15/21] python/qemu/console_socket.py: Clarify type of drain_thread John Snow
@ 2020-10-20 17:27 ` John Snow
2020-10-20 17:27 ` [PULL 17/21] python/qemu/console_socket.py: avoid encoding to/from string John Snow
` (4 subsequent siblings)
20 siblings, 0 replies; 24+ messages in thread
From: John Snow @ 2020-10-20 17:27 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Peter Maydell, Eduardo Habkost, qemu-block,
Max Reitz, Cleber Rosa, John Snow
Finish the typing of console_socket.py with annotations and no code
changes.
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Message-id: 20201006235817.3280413-16-jsnow@redhat.com
Signed-off-by: John Snow <jsnow@redhat.com>
---
python/qemu/console_socket.py | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/python/qemu/console_socket.py b/python/qemu/console_socket.py
index d4669c441d..57e6eee017 100644
--- a/python/qemu/console_socket.py
+++ b/python/qemu/console_socket.py
@@ -17,7 +17,7 @@
import socket
import threading
import time
-from typing import Optional
+from typing import Deque, Optional
class ConsoleSocket(socket.socket):
@@ -30,11 +30,11 @@ class ConsoleSocket(socket.socket):
Optionally a file path can be passed in and we will also
dump the characters to this file for debugging purposes.
"""
- def __init__(self, address, file=None, drain=False):
- self._recv_timeout_sec = 300
+ def __init__(self, address: str, file: Optional[str] = None,
+ drain: bool = False):
self._recv_timeout_sec = 300.0
self._sleep_time = 0.5
- self._buffer = deque()
+ self._buffer: Deque[str] = deque()
socket.socket.__init__(self, socket.AF_UNIX, socket.SOCK_STREAM)
self.connect(address)
self._logfile = None
@@ -45,7 +45,7 @@ def __init__(self, address, file=None, drain=False):
if drain:
self._drain_thread = self._thread_start()
- def _drain_fn(self):
+ def _drain_fn(self) -> None:
"""Drains the socket and runs while the socket is open."""
while self._open:
try:
@@ -56,7 +56,7 @@ def _drain_fn(self):
# self._open is set to False.
time.sleep(self._sleep_time)
- def _thread_start(self):
+ def _thread_start(self) -> threading.Thread:
"""Kick off a thread to drain the socket."""
# Configure socket to not block and timeout.
# This allows our drain thread to not block
@@ -68,7 +68,7 @@ def _thread_start(self):
drain_thread.start()
return drain_thread
- def close(self):
+ def close(self) -> None:
"""Close the base object and wait for the thread to terminate"""
if self._open:
self._open = False
@@ -80,7 +80,7 @@ def close(self):
self._logfile.close()
self._logfile = None
- def _drain_socket(self):
+ def _drain_socket(self) -> None:
"""process arriving characters into in memory _buffer"""
data = socket.socket.recv(self, 1)
# latin1 is needed since there are some chars
@@ -114,7 +114,7 @@ def recv(self, bufsize: int = 1, flags: int = 0) -> bytes:
# socket w/o our intervention.
return chars.encode("latin1")
- def setblocking(self, value):
+ def setblocking(self, value: bool) -> None:
"""When not draining we pass thru to the socket,
since when draining we control socket blocking.
"""
--
2.26.2
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PULL 17/21] python/qemu/console_socket.py: avoid encoding to/from string
2020-10-20 17:27 [PULL 00/21] Python patches John Snow
` (15 preceding siblings ...)
2020-10-20 17:27 ` [PULL 16/21] python/qemu/console_socket.py: Add type hint annotations John Snow
@ 2020-10-20 17:27 ` John Snow
2020-10-20 17:27 ` [PULL 18/21] python/qemu/qmp.py: Preserve error context on re-raise John Snow
` (3 subsequent siblings)
20 siblings, 0 replies; 24+ messages in thread
From: John Snow @ 2020-10-20 17:27 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Peter Maydell, Eduardo Habkost, qemu-block,
Max Reitz, Cleber Rosa, John Snow
We can work directly in bytes instead of translating back and forth to
string, which removes the question of which encodings to use.
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Message-id: 20201006235817.3280413-17-jsnow@redhat.com
Signed-off-by: John Snow <jsnow@redhat.com>
---
python/qemu/console_socket.py | 20 +++++---------------
1 file changed, 5 insertions(+), 15 deletions(-)
diff --git a/python/qemu/console_socket.py b/python/qemu/console_socket.py
index 57e6eee017..f060d79e06 100644
--- a/python/qemu/console_socket.py
+++ b/python/qemu/console_socket.py
@@ -34,12 +34,12 @@ def __init__(self, address: str, file: Optional[str] = None,
drain: bool = False):
self._recv_timeout_sec = 300.0
self._sleep_time = 0.5
- self._buffer: Deque[str] = deque()
+ self._buffer: Deque[int] = deque()
socket.socket.__init__(self, socket.AF_UNIX, socket.SOCK_STREAM)
self.connect(address)
self._logfile = None
if file:
- self._logfile = open(file, "w")
+ self._logfile = open(file, "bw")
self._open = True
self._drain_thread = None
if drain:
@@ -83,15 +83,10 @@ def close(self) -> None:
def _drain_socket(self) -> None:
"""process arriving characters into in memory _buffer"""
data = socket.socket.recv(self, 1)
- # latin1 is needed since there are some chars
- # we are receiving that cannot be encoded to utf-8
- # such as 0xe2, 0x80, 0xA6.
- string = data.decode("latin1")
if self._logfile:
- self._logfile.write("{}".format(string))
+ self._logfile.write(data)
self._logfile.flush()
- for c in string:
- self._buffer.extend(c)
+ self._buffer.extend(data)
def recv(self, bufsize: int = 1, flags: int = 0) -> bytes:
"""Return chars from in memory buffer.
@@ -107,12 +102,7 @@ def recv(self, bufsize: int = 1, flags: int = 0) -> bytes:
elapsed_sec = time.time() - start_time
if elapsed_sec > self._recv_timeout_sec:
raise socket.timeout
- chars = ''.join([self._buffer.popleft() for i in range(bufsize)])
- # We choose to use latin1 to remain consistent with
- # handle_read() and give back the same data as the user would
- # receive if they were reading directly from the
- # socket w/o our intervention.
- return chars.encode("latin1")
+ return bytes((self._buffer.popleft() for i in range(bufsize)))
def setblocking(self, value: bool) -> None:
"""When not draining we pass thru to the socket,
--
2.26.2
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PULL 18/21] python/qemu/qmp.py: Preserve error context on re-raise
2020-10-20 17:27 [PULL 00/21] Python patches John Snow
` (16 preceding siblings ...)
2020-10-20 17:27 ` [PULL 17/21] python/qemu/console_socket.py: avoid encoding to/from string John Snow
@ 2020-10-20 17:27 ` John Snow
2020-10-20 17:27 ` [PULL 19/21] python: add mypy config John Snow
` (2 subsequent siblings)
20 siblings, 0 replies; 24+ messages in thread
From: John Snow @ 2020-10-20 17:27 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Peter Maydell, Eduardo Habkost, qemu-block,
Philippe Mathieu-Daudé,
Max Reitz, Cleber Rosa, John Snow
Use the "from ..." phrasing when re-raising errors to preserve their
initial context, to help aid debugging when things go wrong.
This also silences a pylint 2.6.0+ error.
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Message-id: 20201006235817.3280413-18-jsnow@redhat.com
Signed-off-by: John Snow <jsnow@redhat.com>
---
python/qemu/qmp.py | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/python/qemu/qmp.py b/python/qemu/qmp.py
index 9223307ed8..d911999da1 100644
--- a/python/qemu/qmp.py
+++ b/python/qemu/qmp.py
@@ -181,10 +181,11 @@ def __get_events(self, wait: Union[bool, float] = False) -> None:
self.__sock.settimeout(wait)
try:
ret = self.__json_read(only_event=True)
- except socket.timeout:
- raise QMPTimeoutError("Timeout waiting for event")
- except:
- raise QMPConnectError("Error while reading from socket")
+ except socket.timeout as err:
+ raise QMPTimeoutError("Timeout waiting for event") from err
+ except Exception as err:
+ msg = "Error while reading from socket"
+ raise QMPConnectError(msg) from err
if ret is None:
raise QMPConnectError("Error while reading from socket")
self.__sock.settimeout(None)
--
2.26.2
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PULL 19/21] python: add mypy config
2020-10-20 17:27 [PULL 00/21] Python patches John Snow
` (17 preceding siblings ...)
2020-10-20 17:27 ` [PULL 18/21] python/qemu/qmp.py: Preserve error context on re-raise John Snow
@ 2020-10-20 17:27 ` John Snow
2020-10-20 17:27 ` [PULL 20/21] python/qemu/qmp.py: re-raise OSError when encountered John Snow
2020-10-20 17:27 ` [PULL 21/21] python/qemu/qmp.py: Fix settimeout operation John Snow
20 siblings, 0 replies; 24+ messages in thread
From: John Snow @ 2020-10-20 17:27 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Peter Maydell, Eduardo Habkost, qemu-block,
Max Reitz, Cleber Rosa, John Snow
Formalize the options used for checking the python library. You can run
mypy from the directory that mypy.ini is in by typing `mypy qemu/`.
Signed-off-by: John Snow <jsnow@redhat.com>
Message-id: 20201009175123.249009-2-jsnow@redhat.com
[Edit: Added newline; thanks Bin Meng --js]
Signed-off-by: John Snow <jsnow@redhat.com>
---
python/mypy.ini | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 python/mypy.ini
diff --git a/python/mypy.ini b/python/mypy.ini
new file mode 100644
index 0000000000..1a581c5f1e
--- /dev/null
+++ b/python/mypy.ini
@@ -0,0 +1,4 @@
+[mypy]
+strict = True
+python_version = 3.6
+warn_unused_configs = True
--
2.26.2
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PULL 20/21] python/qemu/qmp.py: re-raise OSError when encountered
2020-10-20 17:27 [PULL 00/21] Python patches John Snow
` (18 preceding siblings ...)
2020-10-20 17:27 ` [PULL 19/21] python: add mypy config John Snow
@ 2020-10-20 17:27 ` John Snow
2020-10-20 18:15 ` Nir Soffer
2020-10-20 17:27 ` [PULL 21/21] python/qemu/qmp.py: Fix settimeout operation John Snow
20 siblings, 1 reply; 24+ messages in thread
From: John Snow @ 2020-10-20 17:27 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Peter Maydell, Eduardo Habkost, qemu-block,
Philippe Mathieu-Daudé,
Max Reitz, Cleber Rosa, John Snow
Nested if conditions don't change when the exception block fires; we
need to explicitly re-raise the error if we didn't intend to capture and
suppress it.
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Message-id: 20201009175123.249009-3-jsnow@redhat.com
Signed-off-by: John Snow <jsnow@redhat.com>
---
python/qemu/qmp.py | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/python/qemu/qmp.py b/python/qemu/qmp.py
index d911999da1..4969e5741c 100644
--- a/python/qemu/qmp.py
+++ b/python/qemu/qmp.py
@@ -165,14 +165,15 @@ def __get_events(self, wait: Union[bool, float] = False) -> None:
"""
# Check for new events regardless and pull them into the cache:
- self.__sock.setblocking(False)
try:
+ self.__sock.setblocking(False)
self.__json_read()
except OSError as err:
- if err.errno == errno.EAGAIN:
- # No data available
- pass
- self.__sock.setblocking(True)
+ # EAGAIN: No data available; not critical
+ if err.errno != errno.EAGAIN:
+ raise
+ finally:
+ self.__sock.setblocking(True)
# Wait for new events, if needed.
# if wait is 0.0, this means "no wait" and is also implicitly false.
--
2.26.2
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PULL 21/21] python/qemu/qmp.py: Fix settimeout operation
2020-10-20 17:27 [PULL 00/21] Python patches John Snow
` (19 preceding siblings ...)
2020-10-20 17:27 ` [PULL 20/21] python/qemu/qmp.py: re-raise OSError when encountered John Snow
@ 2020-10-20 17:27 ` John Snow
20 siblings, 0 replies; 24+ messages in thread
From: John Snow @ 2020-10-20 17:27 UTC (permalink / raw)
To: qemu-devel
Cc: Kevin Wolf, Peter Maydell, Eduardo Habkost, qemu-block,
Philippe Mathieu-Daudé,
Max Reitz, Cleber Rosa, John Snow
We enabled callers to interface directly with settimeout, but this
reacts poorly with blocking/nonblocking operation; as they are using the
same internal mechanism.
1. Whenever we change the blocking mechanism temporarily, always set it
back to what it was afterwards.
2. Disallow callers from setting a timeout of "0", which means
Non-blocking mode. This is going to create more weird problems than
anybody wants, so just forbid it.
I opt not to coerce '0' to 'None' to maintain the principal of least
surprise in mirroring the semantics of Python's interface.
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Message-id: 20201009175123.249009-4-jsnow@redhat.com
Signed-off-by: John Snow <jsnow@redhat.com>
---
python/qemu/qmp.py | 23 +++++++++++++++++------
1 file changed, 17 insertions(+), 6 deletions(-)
diff --git a/python/qemu/qmp.py b/python/qemu/qmp.py
index 4969e5741c..f64517fb0a 100644
--- a/python/qemu/qmp.py
+++ b/python/qemu/qmp.py
@@ -164,16 +164,19 @@ def __get_events(self, wait: Union[bool, float] = False) -> None:
retrieved or if some other error occurred.
"""
+ # Current timeout and blocking status
+ current_timeout = self.__sock.gettimeout()
+
# Check for new events regardless and pull them into the cache:
try:
- self.__sock.setblocking(False)
+ self.__sock.settimeout(0) # i.e. setblocking(False)
self.__json_read()
except OSError as err:
# EAGAIN: No data available; not critical
if err.errno != errno.EAGAIN:
raise
finally:
- self.__sock.setblocking(True)
+ self.__sock.settimeout(current_timeout)
# Wait for new events, if needed.
# if wait is 0.0, this means "no wait" and is also implicitly false.
@@ -187,9 +190,11 @@ def __get_events(self, wait: Union[bool, float] = False) -> None:
except Exception as err:
msg = "Error while reading from socket"
raise QMPConnectError(msg) from err
+ finally:
+ self.__sock.settimeout(current_timeout)
+
if ret is None:
raise QMPConnectError("Error while reading from socket")
- self.__sock.settimeout(None)
def __enter__(self) -> 'QEMUMonitorProtocol':
# Implement context manager enter function.
@@ -219,7 +224,7 @@ def connect(self, negotiate: bool = True) -> Optional[QMPMessage]:
return self.__negotiate_capabilities()
return None
- def accept(self, timeout: float = 15.0) -> QMPMessage:
+ def accept(self, timeout: Optional[float] = 15.0) -> QMPMessage:
"""
Await connection from QMP Monitor and perform capabilities negotiation.
@@ -338,13 +343,19 @@ def close(self) -> None:
if self.__sockfile:
self.__sockfile.close()
- def settimeout(self, timeout: float) -> None:
+ def settimeout(self, timeout: Optional[float]) -> None:
"""
Set the socket timeout.
- @param timeout (float): timeout in seconds, or None.
+ @param timeout (float): timeout in seconds (non-zero), or None.
@note This is a wrap around socket.settimeout
+
+ @raise ValueError: if timeout was set to 0.
"""
+ if timeout == 0:
+ msg = "timeout cannot be 0; this engages non-blocking mode."
+ msg += " Use 'None' instead to disable timeouts."
+ raise ValueError(msg)
self.__sock.settimeout(timeout)
def get_sock_fd(self) -> int:
--
2.26.2
^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PULL 20/21] python/qemu/qmp.py: re-raise OSError when encountered
2020-10-20 17:27 ` [PULL 20/21] python/qemu/qmp.py: re-raise OSError when encountered John Snow
@ 2020-10-20 18:15 ` Nir Soffer
2020-10-20 19:06 ` John Snow
0 siblings, 1 reply; 24+ messages in thread
From: Nir Soffer @ 2020-10-20 18:15 UTC (permalink / raw)
To: John Snow
Cc: Kevin Wolf, Peter Maydell, Eduardo Habkost, qemu-block,
QEMU Developers, Max Reitz, Cleber Rosa,
Philippe Mathieu-Daudé
On Tue, Oct 20, 2020 at 8:52 PM John Snow <jsnow@redhat.com> wrote:
>
> Nested if conditions don't change when the exception block fires; we
> need to explicitly re-raise the error if we didn't intend to capture and
> suppress it.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
> Message-id: 20201009175123.249009-3-jsnow@redhat.com
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> python/qemu/qmp.py | 11 ++++++-----
> 1 file changed, 6 insertions(+), 5 deletions(-)
>
> diff --git a/python/qemu/qmp.py b/python/qemu/qmp.py
> index d911999da1..4969e5741c 100644
> --- a/python/qemu/qmp.py
> +++ b/python/qemu/qmp.py
> @@ -165,14 +165,15 @@ def __get_events(self, wait: Union[bool, float] = False) -> None:
> """
>
> # Check for new events regardless and pull them into the cache:
> - self.__sock.setblocking(False)
> try:
> + self.__sock.setblocking(False)
This change is not required. The idiom is:
do stuff
try:
something
finally:
undo stuff
If do stuff failed, there is no need to undo it.
socket.setblocking() should not fail with EAGAIN, so it
does not need to be inside the try block.
> self.__json_read()
> except OSError as err:
> - if err.errno == errno.EAGAIN:
> - # No data available
> - pass
> - self.__sock.setblocking(True)
> + # EAGAIN: No data available; not critical
> + if err.errno != errno.EAGAIN:
> + raise
In python 3 this can be simplified to:
try:
self.__json_read()
except BlockingIOError:
pass
https://docs.python.org/3.6/library/exceptions.html#BlockingIOError
> + finally:
> + self.__sock.setblocking(True)
>
> # Wait for new events, if needed.
> # if wait is 0.0, this means "no wait" and is also implicitly false.
> --
> 2.26.2
Nir
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PULL 20/21] python/qemu/qmp.py: re-raise OSError when encountered
2020-10-20 18:15 ` Nir Soffer
@ 2020-10-20 19:06 ` John Snow
0 siblings, 0 replies; 24+ messages in thread
From: John Snow @ 2020-10-20 19:06 UTC (permalink / raw)
To: Nir Soffer
Cc: Kevin Wolf, Peter Maydell, Eduardo Habkost, qemu-block,
QEMU Developers, Max Reitz, Cleber Rosa,
Philippe Mathieu-Daudé
On 10/20/20 2:15 PM, Nir Soffer wrote:
> On Tue, Oct 20, 2020 at 8:52 PM John Snow <jsnow@redhat.com> wrote:
>>
>> Nested if conditions don't change when the exception block fires; we
>> need to explicitly re-raise the error if we didn't intend to capture and
>> suppress it.
>>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
>> Message-id: 20201009175123.249009-3-jsnow@redhat.com
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>> python/qemu/qmp.py | 11 ++++++-----
>> 1 file changed, 6 insertions(+), 5 deletions(-)
>>
>> diff --git a/python/qemu/qmp.py b/python/qemu/qmp.py
>> index d911999da1..4969e5741c 100644
>> --- a/python/qemu/qmp.py
>> +++ b/python/qemu/qmp.py
>> @@ -165,14 +165,15 @@ def __get_events(self, wait: Union[bool, float] = False) -> None:
>> """
>>
>> # Check for new events regardless and pull them into the cache:
>> - self.__sock.setblocking(False)
>> try:
>> + self.__sock.setblocking(False)
>
> This change is not required. The idiom is:
>
> do stuff
> try:
> something
> finally:
> undo stuff
>
> If do stuff failed, there is no need to undo it.
>
> socket.setblocking() should not fail with EAGAIN, so it
> does not need to be inside the try block.
>
Squashing this change in, will send a new V2 cover letter.
>> self.__json_read()
>> except OSError as err:
>> - if err.errno == errno.EAGAIN:
>> - # No data available
>> - pass
>> - self.__sock.setblocking(True)
>> + # EAGAIN: No data available; not critical
>> + if err.errno != errno.EAGAIN:
>> + raise
>
> In python 3 this can be simplified to:
>
> try:
> self.__json_read()
> except BlockingIOError:
> pass
>
> https://docs.python.org/3.6/library/exceptions.html#BlockingIOError
>
I'm a lot less clear on this. We only check for EAGAIN, but that would
check for EAGAIN, EALREADY, EWOULDBLOCK and EINPROGRESS.
That's probably fine, really, but:
There is something worse lurking in the code here too, and I really
didn't want to get into it on this series, but we are making use of
undefined behavior (sockfile.readline() on a non-blocking socket) -- It
seems to work in practice so far, but it's begging to break.
For that reason (This code should never have worked anyway), I am
extremely reluctant to change the exception classes we catch here until
we fix the problem.
--js
>> + finally:
>> + self.__sock.setblocking(True)
>>
>> # Wait for new events, if needed.
>> # if wait is 0.0, this means "no wait" and is also implicitly false.
>> --
>> 2.26.2
>
> Nir
>
^ permalink raw reply [flat|nested] 24+ messages in thread
end of thread, other threads:[~2020-10-20 19:08 UTC | newest]
Thread overview: 24+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-10-20 17:27 [PULL 00/21] Python patches John Snow
2020-10-20 17:27 ` [PULL 01/21] MAINTAINERS: Add Python library stanza John Snow
2020-10-20 17:27 ` [PULL 02/21] python/qemu: use isort to lay out imports John Snow
2020-10-20 17:27 ` [PULL 03/21] python/machine.py: Fix monitor address typing John Snow
2020-10-20 17:27 ` [PULL 04/21] python/machine.py: reorder __init__ John Snow
2020-10-20 17:27 ` [PULL 05/21] python/machine.py: Don't modify state in _base_args() John Snow
2020-10-20 17:27 ` [PULL 06/21] python/machine.py: Handle None events in events_wait John Snow
2020-10-20 17:27 ` [PULL 07/21] python/machine.py: use qmp.command John Snow
2020-10-20 17:27 ` [PULL 08/21] python/machine.py: Add _qmp access shim John Snow
2020-10-20 17:27 ` [PULL 09/21] python/machine.py: fix _popen access John Snow
2020-10-20 17:27 ` [PULL 10/21] python/qemu: make 'args' style arguments immutable John Snow
2020-10-20 17:27 ` [PULL 11/21] iotests.py: Adjust HMP kwargs typing John Snow
2020-10-20 17:27 ` [PULL 12/21] python/qemu: Add mypy type annotations John Snow
2020-10-20 17:27 ` [PULL 13/21] python/qemu/console_socket.py: Correct type of recv() John Snow
2020-10-20 17:27 ` [PULL 14/21] python/qemu/console_socket.py: fix typing of settimeout John Snow
2020-10-20 17:27 ` [PULL 15/21] python/qemu/console_socket.py: Clarify type of drain_thread John Snow
2020-10-20 17:27 ` [PULL 16/21] python/qemu/console_socket.py: Add type hint annotations John Snow
2020-10-20 17:27 ` [PULL 17/21] python/qemu/console_socket.py: avoid encoding to/from string John Snow
2020-10-20 17:27 ` [PULL 18/21] python/qemu/qmp.py: Preserve error context on re-raise John Snow
2020-10-20 17:27 ` [PULL 19/21] python: add mypy config John Snow
2020-10-20 17:27 ` [PULL 20/21] python/qemu/qmp.py: re-raise OSError when encountered John Snow
2020-10-20 18:15 ` Nir Soffer
2020-10-20 19:06 ` John Snow
2020-10-20 17:27 ` [PULL 21/21] python/qemu/qmp.py: Fix settimeout operation John Snow
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).