summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlon Levy <alevy@redhat.com>2011-08-10 15:52:32 +0300
committerAlon Levy <alevy@redhat.com>2011-08-10 15:52:32 +0300
commit64bc68be040286b419f310fd88d9299906820932 (patch)
treed9ca747cc69f86e1347d951bf5edd2d1edb9c310
parent2fe2fbf688f847738f25cd68d8d596686efa0950 (diff)
add migration test (from spice/tests, with changes, and with qmp.py from qemu)
-rwxr-xr-xmigrate.py233
-rw-r--r--qmp.py157
2 files changed, 390 insertions, 0 deletions
diff --git a/migrate.py b/migrate.py
new file mode 100755
index 0000000..83770d5
--- /dev/null
+++ b/migrate.py
@@ -0,0 +1,233 @@
+#!/usr/bin/env python
+"""
+Spice Migration test
+
+Somewhat stressfull test of continuous migration with spice in VGA mode or QXL mode,
+depends on supplying an image in IMAGE variable (if no image is supplied then
+VGA mode since it will just be SeaBIOS).
+
+Dependencies:
+either qmp in python path or running with spice and qemu side by side:
+qemu/QMP/qmp.py
+spice/tests/migrate.py
+
+Will create two temporary unix sockets in /tmp
+Will leave a log file, migrate_test.log, in current directory.
+"""
+
+#
+# start one spiceclient, have two machines (active and target),
+# and repeat:
+# active wait until it's active
+# active client_migrate_info
+# active migrate tcp:localhost:9000
+# _wait for event of quit
+# active stop, active<->passive
+#
+# wait until it's active
+# command query-status, if running good
+# if not listen to events until event of running
+
+try:
+ import qmp
+except:
+ import sys
+ sys.path.append("../../qemu/QMP")
+ try:
+ import qmp
+ except:
+ print "can't find qmp"
+ raise SystemExit
+import sys
+from subprocess import Popen, PIPE
+import os
+import time
+import socket
+import datetime
+import atexit
+import argparse
+
+def get_args():
+ parser = argparse.ArgumentParser(description='Process some integers.')
+ parser.add_argument('--qmp1', dest='qmp1', default='/tmp/migrate_test.1.qmp')
+ parser.add_argument('--qmp2', dest='qmp2', default='/tmp/migrate_test.2.qmp')
+ parser.add_argument('--spice_port1', dest='spice_port1', type=int, default=5911)
+ parser.add_argument('--spice_port2', dest='spice_port2', type=int, default=6911)
+ parser.add_argument('--migrate_port', dest='migrate_port', type=int, default=8000)
+ parser.add_argument('--client_count', dest='client_count', type=int, default=1)
+ parser.add_argument('--qemu', dest='qemu', default='../../qemu/x86_64-softmmu/qemu-system-x86_64')
+ parser.add_argument('--log_filename', dest='log_filename', default='migrate.log')
+ parser.add_argument('--image', dest='image', default='')
+ parser.add_argument('--client', dest='client', default='spicy', choices=['spicec', 'spicy'])
+ parser.add_argument('--vdagent', choices=['on', 'off'], default='on')
+ parser.add_argument('--usbtablet', choices=['on', 'off'], default='on')
+ args = parser.parse_args(sys.argv[1:])
+ if os.path.exists(args.qemu):
+ args.qemu_exec = args.qemu
+ else:
+ args.qemu_exec = os.popen("which %s" % args.qemu).read().strip()
+ if not os.path.exists(args.qemu_exec):
+ print "qemu not found (qemu = %r)" % args.qemu_exec
+ sys.exit(1)
+ print "qemu = %s" % args.qemu_exec
+ return args
+
+def start_qemu(qemu_exec, image, spice_port, qmp_filename, incoming_port=None, extra_args=[]):
+ incoming_args = []
+ if incoming_port:
+ incoming_args = ("-incoming tcp::%s" % incoming_port).split()
+ args = ([qemu_exec, "-qmp", "unix:%s,server,nowait" % qmp_filename,
+ "-spice", "disable-ticketing,port=%s" % spice_port]
+ + extra_args + incoming_args)
+ if os.path.exists(image):
+ args += ["-m", "512", "-drive",
+ "file=%s,index=0,media=disk,cache=unsafe" % image, "-snapshot"]
+ print repr(args)
+ proc = Popen(args, executable=qemu_exec, stdin=PIPE, stdout=PIPE)
+ while not os.path.exists(qmp_filename):
+ time.sleep(0.1)
+ proc.qmp_filename = qmp_filename
+ proc.qmp = qmp.QEMUMonitorProtocol(qmp_filename)
+ while True:
+ try:
+ proc.qmp.connect()
+ break
+ except socket.error, err:
+ pass
+ proc.spice_port = spice_port
+ proc.incoming_port = incoming_port
+ return proc
+
+def start_client(client, spice_port):
+ return Popen(("%(client)s -h localhost -p %(port)d" % dict(port=spice_port,
+ client=client)).split(), executable=client)
+
+def wait_active(q, active):
+ events = ["RESUME"] if active else ["STOP"]
+ while True:
+ try:
+ ret = q.cmd("query-status")
+ except:
+ # ValueError
+ time.sleep(0.1)
+ continue
+ if ret and ret.has_key("return"):
+ if ret["return"]["running"] == active:
+ break
+ for e in q.get_events():
+ if e["event"] in events:
+ break
+ time.sleep(0.5)
+
+def wait_for_event(q, event):
+ while True:
+ for e in q.get_events():
+ if e["event"] == event:
+ return
+ time.sleep(0.5)
+
+def cleanup(migrator):
+ print "doing cleanup"
+ migrator.close()
+
+class Migrator(object):
+
+ migration_count = 0
+
+ def __init__(self, log, client, qemu_exec, image, monitor_files, client_count,
+ spice_ports, migration_port, vdagent, usbtablet):
+ self.client = client
+ self.log = log
+ self.qemu_exec = qemu_exec
+ self.image = image
+ self.migration_port = migration_port
+ self.client_count = client_count
+ self.monitor_files = monitor_files
+ self.spice_ports = spice_ports
+ self.vdagent = vdagent
+ self.usbtablet = usbtablet
+ extra_args = []
+ if self.vdagent:
+ extra_args = ['-device', 'virtio-serial', '-chardev', 'spicevmc,name=vdagent,id=vdagent', '-device', 'virtserialport,chardev=vdagent,name=com.redhat.spice.0']
+ if self.usbtablet:
+ extra_args.extend(['-usb', '-device', 'usb-tablet'])
+ self.extra_args = extra_args
+ self.active = self.start_qemu(which=0, incoming=False)
+ self.target = self.start_qemu(which=1, incoming=True)
+ self.remove_monitor_files()
+ self.clients = []
+
+ def start_qemu(self, which, incoming):
+ kw = {}
+ if incoming:
+ kw['incoming_port'] = self.migration_port
+ return start_qemu(qemu_exec=self.qemu_exec, image=self.image,
+ spice_port=self.spice_ports[which],
+ qmp_filename=self.monitor_files[which],
+ extra_args=self.extra_args,
+ **kw)
+
+
+ def close(self):
+ self.remove_monitor_files()
+ self.kill_qemu()
+
+ def kill_qemu(self):
+ for p in [self.active, self.target]:
+ print "killing and waiting for qemu pid %s" % p.pid
+ p.kill()
+ p.wait()
+
+ def remove_monitor_files(self):
+ for x in self.monitor_files:
+ if os.path.exists(x):
+ os.unlink(x)
+
+ def iterate(self, wait_for_user_input=False):
+ wait_active(self.active.qmp, True)
+ wait_active(self.target.qmp, False)
+ if len(self.clients) == 0:
+ for i in range(self.client_count):
+ self.clients.append(start_client(client=self.client,
+ spice_port=self.spice_ports[0]))
+ wait_for_event(self.active.qmp, 'SPICE_INITIALIZED')
+ if wait_for_user_input:
+ print "waiting for Enter to start migrations"
+ raw_input()
+ self.active.qmp.cmd('client_migrate_info', {'protocol':'spice',
+ 'hostname':'localhost', 'port':self.target.spice_port})
+ self.active.qmp.cmd('migrate', {'uri': 'tcp:localhost:%s' % self.migration_port})
+ wait_active(self.active.qmp, False)
+ wait_active(self.target.qmp, True)
+ wait_for_event(self.target.qmp, 'SPICE_CONNECTED')
+ dead = self.active
+ dead.qmp.cmd("quit")
+ dead.qmp.close()
+ dead.wait()
+ new_spice_port = dead.spice_port
+ new_qmp_filename = dead.qmp_filename
+ self.log.write("# STDOUT dead %s\n" % dead.pid)
+ self.log.write(dead.stdout.read())
+ del dead
+ self.active = self.target
+ which = 0 if new_spice_port == self.spice_ports[0] else 1
+ self.target = self.start_qemu(which=which, incoming=True)
+ print self.migration_count
+ self.migration_count += 1
+
+def main():
+ args = get_args()
+ print "log file %s" % args.log_filename
+ log = open(args.log_filename, "a+")
+ log.write("# "+str(datetime.datetime.now())+"\n")
+ migrator = Migrator(client=args.client, qemu_exec=args.qemu_exec,
+ image=args.image, log=log, monitor_files=[args.qmp1, args.qmp2],
+ migration_port=args.migrate_port, spice_ports=[args.spice_port1,
+ args.spice_port2], client_count=args.client_count, vdagent=(args.vdagent=='on'),
+ usbtablet=(args.usbtablet=='on'))
+ atexit.register(cleanup, migrator)
+ while True:
+ migrator.iterate()
+
+if __name__ == '__main__':
+ main()
diff --git a/qmp.py b/qmp.py
new file mode 100644
index 0000000..c7dbea0
--- /dev/null
+++ b/qmp.py
@@ -0,0 +1,157 @@
+# QEMU Monitor Protocol Python class
+#
+# Copyright (C) 2009, 2010 Red Hat Inc.
+#
+# Authors:
+# Luiz Capitulino <lcapitulino@redhat.com>
+#
+# 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
+
+class QMPError(Exception):
+ pass
+
+class QMPConnectError(QMPError):
+ pass
+
+class QMPCapabilitiesError(QMPError):
+ pass
+
+class QEMUMonitorProtocol:
+ def __init__(self, address, server=False):
+ """
+ Create a QEMUMonitorProtocol class.
+
+ @param address: QEMU address, can be either a unix socket path (string)
+ or a tuple in the form ( address, port ) for a TCP
+ connection
+ @param server: server mode listens on the socket (bool)
+ @raise socket.error on socket connection errors
+ @note No connection is established, this is done by the connect() or
+ accept() methods
+ """
+ self.__events = []
+ self.__address = address
+ self.__sock = self.__get_sock()
+ if server:
+ self.__sock.bind(self.__address)
+ self.__sock.listen(1)
+
+ def __get_sock(self):
+ 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):
+ self.__sockfile = self.__sock.makefile()
+ greeting = self.__json_read()
+ if greeting is None or not greeting.has_key('QMP'):
+ raise QMPConnectError
+ # Greeting seems ok, negotiate capabilities
+ resp = self.cmd('qmp_capabilities')
+ if "return" in resp:
+ return greeting
+ raise QMPCapabilitiesError
+
+ def __json_read(self, only_event=False):
+ while True:
+ data = self.__sockfile.readline()
+ if not data:
+ return
+ resp = json.loads(data)
+ if 'event' in resp:
+ self.__events.append(resp)
+ if not only_event:
+ continue
+ return resp
+
+ error = socket.error
+
+ def connect(self):
+ """
+ Connect to the QMP Monitor and perform capabilities negotiation.
+
+ @return QMP greeting dict
+ @raise socket.error on socket connection errors
+ @raise QMPConnectError if the greeting is not received
+ @raise QMPCapabilitiesError if fails to negotiate capabilities
+ """
+ self.__sock.connect(self.__address)
+ return self.__negotiate_capabilities()
+
+ def accept(self):
+ """
+ Await connection from QMP Monitor and perform capabilities negotiation.
+
+ @return QMP greeting dict
+ @raise socket.error on socket connection errors
+ @raise QMPConnectError if the greeting is not received
+ @raise QMPCapabilitiesError if fails to negotiate capabilities
+ """
+ self.__sock, _ = self.__sock.accept()
+ return self.__negotiate_capabilities()
+
+ def cmd_obj(self, qmp_cmd):
+ """
+ Send a QMP command to the QMP Monitor.
+
+ @param qmp_cmd: QMP command to be sent as a Python dict
+ @return QMP response as a Python dict or None if the connection has
+ been closed
+ """
+ try:
+ self.__sock.sendall(json.dumps(qmp_cmd))
+ except socket.error, err:
+ if err[0] == errno.EPIPE:
+ return
+ raise socket.error(err)
+ return self.__json_read()
+
+ def cmd(self, name, args=None, 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)
+ """
+ qmp_cmd = { 'execute': name }
+ if args:
+ qmp_cmd['arguments'] = args
+ if id:
+ qmp_cmd['id'] = id
+ return self.cmd_obj(qmp_cmd)
+
+ def get_events(self, wait=False):
+ """
+ Get a list of available QMP events.
+
+ @param wait: block until an event is available (bool)
+ """
+ self.__sock.setblocking(0)
+ try:
+ self.__json_read()
+ except socket.error, err:
+ if err[0] == errno.EAGAIN:
+ # No data available
+ pass
+ self.__sock.setblocking(1)
+ if not self.__events and wait:
+ self.__json_read(only_event=True)
+ return self.__events
+
+ def clear_events(self):
+ """
+ Clear current list of pending events.
+ """
+ self.__events = []
+
+ def close(self):
+ self.__sock.close()
+ self.__sockfile.close()