aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2017-09-16 14:36:16 +0100
committerPeter Maydell <peter.maydell@linaro.org>2017-09-16 14:36:16 +0100
commit5ee53d1593dfc071275b13b1228c70bb88f4aaee (patch)
tree1e58882bef9497fe270f52417759fad69884162a
parentd8782a113a5f21a030b35fc35f0871b937685227 (diff)
parentb92a0011b1220aff549ae82c6104014d25e339ef (diff)
Merge remote-tracking branch 'remotes/ehabkost/tags/python-next-pull-request' into staging
Python queue, 2017-09-15 # gpg: Signature made Sat 16 Sep 2017 00:14:01 BST # gpg: using RSA key 0x2807936F984DC5A6 # gpg: Good signature from "Eduardo Habkost <ehabkost@redhat.com>" # Primary key fingerprint: 5A32 2FD5 ABC4 D3DB ACCF D1AA 2807 936F 984D C5A6 * remotes/ehabkost/tags/python-next-pull-request: qemu.py: include debug information on launch error qemu.py: improve message on negative exit code qemu.py: use os.path.null instead of /dev/null qemu.py: avoid writing to stdout/stderr qemu.py: fix is_running() return before first launch() qtest.py: Few pylint/style fixes qmp.py: Avoid overriding a builtin object qmp.py: Avoid "has_key" usage qmp.py: Use object-based class for QEMUMonitorProtocol qmp.py: Couple of pylint/style fixes qemu.py: Use custom exceptions rather than Exception qemu.py: Simplify QMP key-conversion qemu.py: Use iteritems rather than keys() qemu|qtest: Avoid dangerous arguments qemu.py: Pylint/style fixes Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
-rw-r--r--scripts/qemu.py145
-rwxr-xr-xscripts/qmp/qmp-shell4
-rw-r--r--scripts/qmp/qmp.py49
-rw-r--r--scripts/qtest.py13
4 files changed, 151 insertions, 60 deletions
diff --git a/scripts/qemu.py b/scripts/qemu.py
index 4d8ee10943..8c67595ec8 100644
--- a/scripts/qemu.py
+++ b/scripts/qemu.py
@@ -13,13 +13,35 @@
#
import errno
-import string
+import logging
import os
import sys
import subprocess
import qmp.qmp
+LOG = logging.getLogger(__name__)
+
+
+class QEMUMachineError(Exception):
+ """
+ Exception called when an error in QEMUMachine happens.
+ """
+
+
+class MonitorResponseError(qmp.qmp.QMPError):
+ '''
+ Represents erroneous QMP monitor reply
+ '''
+ def __init__(self, reply):
+ try:
+ desc = reply["error"]["desc"]
+ except KeyError:
+ desc = reply
+ super(MonitorResponseError, self).__init__(desc)
+ self.reply = reply
+
+
class QEMUMachine(object):
'''A QEMU VM
@@ -30,8 +52,26 @@ class QEMUMachine(object):
# vm is guaranteed to be shut down here
'''
- def __init__(self, binary, args=[], wrapper=[], name=None, test_dir="/var/tmp",
- monitor_address=None, socket_scm_helper=None, debug=False):
+ def __init__(self, binary, args=None, wrapper=None, name=None,
+ test_dir="/var/tmp", monitor_address=None,
+ socket_scm_helper=None, debug=False):
+ '''
+ Initialize a QEMUMachine
+
+ @param binary: path to the qemu binary
+ @param args: list of extra arguments
+ @param wrapper: list of arguments used as prefix to qemu binary
+ @param name: prefix for socket and log file names (default: qemu-PID)
+ @param test_dir: where to create socket and log file
+ @param monitor_address: address for QMP monitor
+ @param socket_scm_helper: helper program, required for send_fd_scm()"
+ @param debug: enable debug mode
+ @note: Qemu process is not started until launch() is used.
+ '''
+ if args is None:
+ args = []
+ if wrapper is None:
+ wrapper = []
if name is None:
name = "qemu-%d" % os.getpid()
if monitor_address is None:
@@ -40,12 +80,14 @@ class QEMUMachine(object):
self._qemu_log_path = os.path.join(test_dir, name + ".log")
self._popen = None
self._binary = binary
- self._args = list(args) # Force copy args in case we modify them
+ 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._debug = debug
+ self._qmp = None
+ self._qemu_full_args = None
def __enter__(self):
return self
@@ -76,18 +118,21 @@ class QEMUMachine(object):
# In iotest.py, the qmp should always use unix socket.
assert self._qmp.is_scm_available()
if self._socket_scm_helper is None:
- print >>sys.stderr, "No path to socket_scm_helper set"
- return -1
- if os.path.exists(self._socket_scm_helper) == False:
- print >>sys.stderr, "%s does not exist" % self._socket_scm_helper
- return -1
+ raise QEMUMachineError("No path to socket_scm_helper set")
+ if not os.path.exists(self._socket_scm_helper):
+ raise QEMUMachineError("%s does not exist" %
+ self._socket_scm_helper)
fd_param = ["%s" % self._socket_scm_helper,
"%d" % self._qmp.get_sock_fd(),
"%s" % fd_file_path]
- devnull = open('/dev/null', 'rb')
- p = subprocess.Popen(fd_param, stdin=devnull, stdout=sys.stdout,
- stderr=sys.stderr)
- return p.wait()
+ devnull = open(os.path.devnull, 'rb')
+ proc = subprocess.Popen(fd_param, stdin=devnull, stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+ output = proc.communicate()[0]
+ if output:
+ LOG.debug(output)
+
+ return proc.returncode
@staticmethod
def _remove_if_exists(path):
@@ -100,7 +145,7 @@ class QEMUMachine(object):
raise
def is_running(self):
- return self._popen and (self._popen.returncode is None)
+ return self._popen is not None and self._popen.returncode is None
def exitcode(self):
if self._popen is None:
@@ -113,8 +158,8 @@ class QEMUMachine(object):
return self._popen.pid
def _load_io_log(self):
- with open(self._qemu_log_path, "r") as fh:
- self._iolog = fh.read()
+ with open(self._qemu_log_path, "r") as iolog:
+ self._iolog = iolog.read()
def _base_args(self):
if isinstance(self._monitor_address, tuple):
@@ -128,7 +173,8 @@ class QEMUMachine(object):
'-display', 'none', '-vga', 'none']
def _pre_launch(self):
- self._qmp = qmp.qmp.QEMUMonitorProtocol(self._monitor_address, server=True,
+ self._qmp = qmp.qmp.QEMUMonitorProtocol(self._monitor_address,
+ server=True,
debug=self._debug)
def _post_launch(self):
@@ -141,13 +187,19 @@ class QEMUMachine(object):
def launch(self):
'''Launch the VM and establish a QMP connection'''
- devnull = open('/dev/null', 'rb')
+ self._iolog = None
+ self._qemu_full_args = None
+ devnull = open(os.path.devnull, 'rb')
qemulog = open(self._qemu_log_path, 'wb')
try:
self._pre_launch()
- args = self._wrapper + [self._binary] + self._base_args() + self._args
- self._popen = subprocess.Popen(args, stdin=devnull, stdout=qemulog,
- stderr=subprocess.STDOUT, shell=False)
+ self._qemu_full_args = self._wrapper + [self._binary] +
+ self._base_args() + self._args
+ self._popen = subprocess.Popen(self._qemu_full_args,
+ stdin=devnull,
+ stdout=qemulog,
+ stderr=subprocess.STDOUT,
+ shell=False)
self._post_launch()
except:
if self.is_running():
@@ -155,6 +207,12 @@ class QEMUMachine(object):
self._popen.wait()
self._load_io_log()
self._post_shutdown()
+
+ LOG.debug('Error launching VM')
+ if self._qemu_full_args:
+ LOG.debug('Command: %r', ' '.join(self._qemu_full_args))
+ if self._iolog:
+ LOG.debug('Output: %r', self._iolog)
raise
def shutdown(self):
@@ -165,31 +223,42 @@ class QEMUMachine(object):
self._qmp.close()
except:
self._popen.kill()
+ self._popen.wait()
- exitcode = self._popen.wait()
- if exitcode < 0:
- sys.stderr.write('qemu received signal %i: %s\n' % (-exitcode, ' '.join(self._args)))
self._load_io_log()
self._post_shutdown()
- underscore_to_dash = string.maketrans('_', '-')
+ exitcode = self.exitcode()
+ if exitcode is not None and exitcode < 0:
+ msg = 'qemu received signal %i: %s'
+ if self._qemu_full_args:
+ command = ' '.join(self._qemu_full_args)
+ else:
+ command = ''
+ LOG.warn(msg, exitcode, command)
+
def qmp(self, cmd, conv_keys=True, **args):
- '''Invoke a QMP command and return the result dict'''
+ '''Invoke a QMP command and return the response dict'''
qmp_args = dict()
- for k in args.keys():
+ for key, value in args.iteritems():
if conv_keys:
- qmp_args[k.translate(self.underscore_to_dash)] = args[k]
+ qmp_args[key.replace('_', '-')] = value
else:
- qmp_args[k] = args[k]
+ qmp_args[key] = value
return self._qmp.cmd(cmd, args=qmp_args)
def command(self, cmd, conv_keys=True, **args):
+ '''
+ Invoke a QMP command.
+ On success return the response dict.
+ On failure raise an exception.
+ '''
reply = self.qmp(cmd, conv_keys, **args)
if reply is None:
- raise Exception("Monitor is closed")
+ raise qmp.qmp.QMPError("Monitor is closed")
if "error" in reply:
- raise Exception(reply["error"]["desc"])
+ raise MonitorResponseError(reply)
return reply["return"]
def get_qmp_event(self, wait=False):
@@ -207,7 +276,15 @@ class QEMUMachine(object):
return events
def event_wait(self, name, timeout=60.0, match=None):
- # Test if 'match' is a recursive subset of 'event'
+ '''
+ Wait for specified timeout on named event in QMP; optionally filter
+ results by match.
+
+ The 'match' is checked to be a recursive subset of the 'event'; skips
+ branch processing on match's value None
+ {"foo": {"bar": 1}} matches {"foo": None}
+ {"foo": {"bar": 1}} does not matches {"foo": {"baz": None}}
+ '''
def event_match(event, match=None):
if match is None:
return True
@@ -240,4 +317,8 @@ class QEMUMachine(object):
return None
def get_log(self):
+ '''
+ After self.shutdown or failed qemu execution, this returns the output
+ of the qemu process.
+ '''
return self._iolog
diff --git a/scripts/qmp/qmp-shell b/scripts/qmp/qmp-shell
index 860ffb27f2..be449de621 100755
--- a/scripts/qmp/qmp-shell
+++ b/scripts/qmp/qmp-shell
@@ -106,7 +106,7 @@ class FuzzyJSON(ast.NodeTransformer):
# _execute_cmd()). Let's design a better one.
class QMPShell(qmp.QEMUMonitorProtocol):
def __init__(self, address, pretty=False):
- qmp.QEMUMonitorProtocol.__init__(self, self.__get_address(address))
+ super(QMPShell, self).__init__(self.__get_address(address))
self._greeting = None
self._completer = None
self._pretty = pretty
@@ -281,7 +281,7 @@ class QMPShell(qmp.QEMUMonitorProtocol):
return True
def connect(self, negotiate):
- self._greeting = qmp.QEMUMonitorProtocol.connect(self, negotiate)
+ self._greeting = super(QMPShell, self).connect(negotiate)
self.__completer_setup()
def show_banner(self, msg='Welcome to the QMP low-level shell!'):
diff --git a/scripts/qmp/qmp.py b/scripts/qmp/qmp.py
index 62d3651967..ef12e8a1a0 100644
--- a/scripts/qmp/qmp.py
+++ b/scripts/qmp/qmp.py
@@ -13,19 +13,30 @@ import errno
import socket
import sys
+
class QMPError(Exception):
pass
+
class QMPConnectError(QMPError):
pass
+
class QMPCapabilitiesError(QMPError):
pass
+
class QMPTimeoutError(QMPError):
pass
-class QEMUMonitorProtocol:
+
+class QEMUMonitorProtocol(object):
+
+ #: Socket's error class
+ error = socket.error
+ #: Socket's timeout
+ timeout = socket.timeout
+
def __init__(self, address, server=False, debug=False):
"""
Create a QEMUMonitorProtocol class.
@@ -42,6 +53,7 @@ class QEMUMonitorProtocol:
self.__address = address
self._debug = debug
self.__sock = self.__get_sock()
+ self.__sockfile = None
if server:
self.__sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.__sock.bind(self.__address)
@@ -56,7 +68,7 @@ class QEMUMonitorProtocol:
def __negotiate_capabilities(self):
greeting = self.__json_read()
- if greeting is None or not greeting.has_key('QMP'):
+ if greeting is None or "QMP" not in greeting:
raise QMPConnectError
# Greeting seems ok, negotiate capabilities
resp = self.cmd('qmp_capabilities')
@@ -78,8 +90,6 @@ class QEMUMonitorProtocol:
continue
return resp
- error = socket.error
-
def __get_events(self, wait=False):
"""
Check for new events in the stream and cache them in __events.
@@ -89,8 +99,8 @@ class QEMUMonitorProtocol:
@raise QMPTimeoutError: If a timeout float is provided and the timeout
period elapses.
- @raise QMPConnectError: If wait is True but no events could be retrieved
- or if some other error occurred.
+ @raise QMPConnectError: If wait is True but no events could be
+ retrieved or if some other error occurred.
"""
# Check for new events regardless and pull them into the cache:
@@ -167,38 +177,41 @@ class QEMUMonitorProtocol:
print >>sys.stderr, "QMP:<<< %s" % resp
return resp
- def cmd(self, name, args=None, id=None):
+ def cmd(self, name, args=None, cmd_id=None):
"""
Build a QMP command and send it to the QMP Monitor.
@param name: command name (string)
@param args: command arguments (dict)
- @param id: command id (dict, list, string or int)
+ @param cmd_id: command id (dict, list, string or int)
"""
- qmp_cmd = { 'execute': name }
+ qmp_cmd = {'execute': name}
if args:
qmp_cmd['arguments'] = args
- if id:
- qmp_cmd['id'] = id
+ if cmd_id:
+ qmp_cmd['id'] = cmd_id
return self.cmd_obj(qmp_cmd)
def command(self, cmd, **kwds):
+ """
+ Build and send a QMP command to the monitor, report errors if any
+ """
ret = self.cmd(cmd, kwds)
- if ret.has_key('error'):
+ if "error" in ret:
raise Exception(ret['error']['desc'])
return ret['return']
def pull_event(self, wait=False):
"""
- Get and delete the first available QMP event.
+ Pulls a single event.
@param wait (bool): block until an event is available.
@param wait (float): If wait is a float, treat it as a timeout value.
@raise QMPTimeoutError: If a timeout float is provided and the timeout
period elapses.
- @raise QMPConnectError: If wait is True but no events could be retrieved
- or if some other error occurred.
+ @raise QMPConnectError: If wait is True but no events could be
+ retrieved or if some other error occurred.
@return The first available QMP event, or None.
"""
@@ -217,8 +230,8 @@ class QEMUMonitorProtocol:
@raise QMPTimeoutError: If a timeout float is provided and the timeout
period elapses.
- @raise QMPConnectError: If wait is True but no events could be retrieved
- or if some other error occurred.
+ @raise QMPConnectError: If wait is True but no events could be
+ retrieved or if some other error occurred.
@return The list of available QMP events.
"""
@@ -235,8 +248,6 @@ class QEMUMonitorProtocol:
self.__sock.close()
self.__sockfile.close()
- timeout = socket.timeout
-
def settimeout(self, timeout):
self.__sock.settimeout(timeout)
diff --git a/scripts/qtest.py b/scripts/qtest.py
index d5aecb5f49..df0daf26ca 100644
--- a/scripts/qtest.py
+++ b/scripts/qtest.py
@@ -11,14 +11,11 @@
# Based on qmp.py.
#
-import errno
import socket
-import string
import os
-import subprocess
-import qmp.qmp
import qemu
+
class QEMUQtestProtocol(object):
def __init__(self, address, server=False):
"""
@@ -79,12 +76,14 @@ class QEMUQtestProtocol(object):
class QEMUQtestMachine(qemu.QEMUMachine):
'''A QEMU VM'''
- def __init__(self, binary, args=[], name=None, test_dir="/var/tmp",
+ def __init__(self, binary, args=None, name=None, test_dir="/var/tmp",
socket_scm_helper=None):
if name is None:
name = "qemu-%d" % os.getpid()
- super(QEMUQtestMachine, self).__init__(binary, args, name=name, test_dir=test_dir,
- socket_scm_helper=socket_scm_helper)
+ super(QEMUQtestMachine,
+ self).__init__(binary, args, name=name, test_dir=test_dir,
+ socket_scm_helper=socket_scm_helper)
+ self._qtest = None
self._qtest_path = os.path.join(test_dir, name + "-qtest.sock")
def _base_args(self):