summary refs log tree commit diff stats
path: root/scripts/qemu.py
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/qemu.py
parent991e7c46504807bd89ba8debeccc5211e0b7f221 (diff)
downloadfocaccia-qemu-66613974468fb6e1609fb3eabf55981b1ee436cf.tar.gz
focaccia-qemu-66613974468fb6e1609fb3eabf55981b1ee436cf.zip
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/qemu.py')
-rw-r--r--scripts/qemu.py202
1 files changed, 202 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