aboutsummaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorDaniel P. Berrange <berrange@redhat.com>2016-07-20 14:23:10 +0100
committerAmit Shah <amit.shah@redhat.com>2016-07-22 13:23:24 +0530
commit66613974468fb6e1609fb3eabf55981b1ee436cf (patch)
treed810f59a5682890cbbbbc7b3e36b502b6aa98f66 /scripts
parent991e7c46504807bd89ba8debeccc5211e0b7f221 (diff)
scripts: refactor the VM class in iotests for reuse
The iotests module has a python class for controlling QEMU processes. Pull the generic functionality out of this file and create a scripts/qemu.py module containing a QEMUMachine class. Put the QTest integration support into a subclass QEMUQtestMachine. Signed-off-by: Daniel P. Berrange <berrange@redhat.com> Message-Id: <1469020993-29426-4-git-send-email-berrange@redhat.com> Signed-off-by: Amit Shah <amit.shah@redhat.com>
Diffstat (limited to 'scripts')
-rw-r--r--scripts/qemu.py202
-rw-r--r--scripts/qtest.py34
2 files changed, 236 insertions, 0 deletions
diff --git a/scripts/qemu.py b/scripts/qemu.py
new file mode 100644
index 0000000000..9cdad24949
--- /dev/null
+++ b/scripts/qemu.py
@@ -0,0 +1,202 @@
+# QEMU library
+#
+# Copyright (C) 2015-2016 Red Hat Inc.
+# Copyright (C) 2012 IBM Corp.
+#
+# Authors:
+# Fam Zheng <famz@redhat.com>
+#
+# This work is licensed under the terms of the GNU GPL, version 2. See
+# the COPYING file in the top-level directory.
+#
+# Based on qmp.py.
+#
+
+import errno
+import string
+import os
+import sys
+import subprocess
+import qmp.qmp
+
+
+class QEMUMachine(object):
+ '''A QEMU VM'''
+
+ def __init__(self, binary, args=[], wrapper=[], name=None, test_dir="/var/tmp",
+ monitor_address=None, debug=False):
+ if name is None:
+ name = "qemu-%d" % os.getpid()
+ if monitor_address is None:
+ monitor_address = os.path.join(test_dir, name + "-monitor.sock")
+ self._monitor_address = monitor_address
+ self._qemu_log_path = os.path.join(test_dir, name + ".log")
+ self._popen = None
+ self._binary = binary
+ self._args = args
+ self._wrapper = wrapper
+ self._events = []
+ self._iolog = None
+ self._debug = debug
+
+ # This can be used to add an unused monitor instance.
+ def add_monitor_telnet(self, ip, port):
+ args = 'tcp:%s:%d,server,nowait,telnet' % (ip, port)
+ self._args.append('-monitor')
+ self._args.append(args)
+
+ def add_fd(self, fd, fdset, opaque, opts=''):
+ '''Pass a file descriptor to the VM'''
+ options = ['fd=%d' % fd,
+ 'set=%d' % fdset,
+ 'opaque=%s' % opaque]
+ if opts:
+ options.append(opts)
+
+ self._args.append('-add-fd')
+ self._args.append(','.join(options))
+ return self
+
+ def send_fd_scm(self, fd_file_path):
+ # In iotest.py, the qmp should always use unix socket.
+ assert self._qmp.is_scm_available()
+ bin = socket_scm_helper
+ if os.path.exists(bin) == False:
+ print "Scm help program does not present, path '%s'." % bin
+ return -1
+ fd_param = ["%s" % bin,
+ "%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()
+
+ @staticmethod
+ def _remove_if_exists(path):
+ '''Remove file object at path if it exists'''
+ try:
+ os.remove(path)
+ except OSError as exception:
+ if exception.errno == errno.ENOENT:
+ return
+ raise
+
+ def get_pid(self):
+ if not self._popen:
+ return None
+ return self._popen.pid
+
+ def _load_io_log(self):
+ with open(self._qemu_log_path, "r") as fh:
+ self._iolog = fh.read()
+
+ def _base_args(self):
+ if isinstance(self._monitor_address, tuple):
+ moncdev = "socket,id=mon,host=%s,port=%s" % (
+ self._monitor_address[0],
+ self._monitor_address[1])
+ else:
+ moncdev = 'socket,id=mon,path=%s' % self._monitor_address
+ return ['-chardev', moncdev,
+ '-mon', 'chardev=mon,mode=control',
+ '-display', 'none', '-vga', 'none']
+
+ def _pre_launch(self):
+ self._qmp = qmp.qmp.QEMUMonitorProtocol(self._monitor_address, server=True,
+ debug=self._debug)
+
+ def _post_launch(self):
+ self._qmp.accept()
+
+ def _post_shutdown(self):
+ if not isinstance(self._monitor_address, tuple):
+ self._remove_if_exists(self._monitor_address)
+ self._remove_if_exists(self._qemu_log_path)
+
+ def launch(self):
+ '''Launch the VM and establish a QMP connection'''
+ devnull = open('/dev/null', '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._post_launch()
+ except:
+ if self._popen:
+ self._popen.kill()
+ self._load_io_log()
+ self._post_shutdown()
+ self._popen = None
+ raise
+
+ def shutdown(self):
+ '''Terminate the VM and clean up'''
+ if not self._popen is None:
+ try:
+ self._qmp.cmd('quit')
+ self._qmp.close()
+ except:
+ self._popen.kill()
+
+ 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()
+ self._popen = None
+
+ underscore_to_dash = string.maketrans('_', '-')
+ def qmp(self, cmd, conv_keys=True, **args):
+ '''Invoke a QMP command and return the result dict'''
+ qmp_args = dict()
+ for k in args.keys():
+ if conv_keys:
+ qmp_args[k.translate(self.underscore_to_dash)] = args[k]
+ else:
+ qmp_args[k] = args[k]
+
+ return self._qmp.cmd(cmd, args=qmp_args)
+
+ def command(self, cmd, conv_keys=True, **args):
+ reply = self.qmp(cmd, conv_keys, **args)
+ if reply is None:
+ raise Exception("Monitor is closed")
+ if "error" in reply:
+ raise Exception(reply["error"]["desc"])
+ return reply["return"]
+
+ def get_qmp_event(self, wait=False):
+ '''Poll for one queued QMP events and return it'''
+ if len(self._events) > 0:
+ return self._events.pop(0)
+ return self._qmp.pull_event(wait=wait)
+
+ def get_qmp_events(self, wait=False):
+ '''Poll for queued QMP events and return a list of dicts'''
+ events = self._qmp.get_events(wait=wait)
+ events.extend(self._events)
+ del self._events[:]
+ self._qmp.clear_events()
+ return events
+
+ def event_wait(self, name, timeout=60.0, match=None):
+ # Search cached events
+ for event in self._events:
+ if (event['event'] == name) and event_match(event, match):
+ self._events.remove(event)
+ return event
+
+ # Poll for new events
+ while True:
+ event = self._qmp.pull_event(wait=timeout)
+ if (event['event'] == name) and event_match(event, match):
+ return event
+ self._events.append(event)
+
+ return None
+
+ def get_log(self):
+ return self._iolog
diff --git a/scripts/qtest.py b/scripts/qtest.py
index a9714453a2..03bc7f6c9b 100644
--- a/scripts/qtest.py
+++ b/scripts/qtest.py
@@ -13,6 +13,11 @@
import errno
import socket
+import string
+import os
+import subprocess
+import qmp.qmp
+import qemu
class QEMUQtestProtocol(object):
def __init__(self, address, server=False):
@@ -69,3 +74,32 @@ class QEMUQtestProtocol(object):
def settimeout(self, timeout):
self._sock.settimeout(timeout)
+
+
+class QEMUQtestMachine(qemu.QEMUMachine):
+ '''A QEMU VM'''
+
+ def __init__(self, binary, args=[], name=None, test_dir="/var/tmp"):
+ super(self, QEMUQtestMachine).__init__(binary, args, name, test_dir)
+ self._qtest_path = os.path.join(test_dir, name + "-qtest.sock")
+
+ def _base_args(self):
+ args = super(self, QEMUQtestMachine)._base_args()
+ args.extend(['-qtest', 'unix:path=' + self._qtest_path])
+ return args
+
+ def _pre_launch(self):
+ super(self, QEMUQtestMachine)._pre_launch()
+ self._qtest = QEMUQtestProtocol(self._qtest_path, server=True)
+
+ def _post_launch(self):
+ super(self, QEMUQtestMachine)._post_launch()
+ self._qtest.accept()
+
+ def _post_shutdown(self):
+ super(self, QEMUQtestMachine)._post_shutdown()
+ self._remove_if_exists(self._qtest_path)
+
+ def qtest(self, cmd):
+ '''Send a qtest command to guest'''
+ return self._qtest.cmd(cmd)