summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README56
-rwxr-xr-xdo_qemu_com_redhat_qxl_screendump.sh10
-rwxr-xr-xdo_qemu_info_spice.sh3
-rwxr-xr-xdo_qemu_query_spice.sh7
-rwxr-xr-xdo_qemu_query_spice_disabled.sh7
-rw-r--r--qmp.py157
-rwxr-xr-xscreen_dump_qxl124
-rwxr-xr-xspice_make_certs.sh39
-rwxr-xr-xspice_migrate.py450
-rwxr-xr-xtest_vdagent_in_win7_client_edit_to_taste.sh2
10 files changed, 854 insertions, 1 deletions
diff --git a/README b/README
index 16c89d1..b8d0c81 100644
--- a/README
+++ b/README
@@ -1 +1,55 @@
-tests for spice bugzilla (bz) entries.
+Tests for SPICE
+===============
+
+Migration test quick startup
+============================
+
+What it does: repeatedly migrates qemu-kvm instances with a single client attached.
+
+use test_vdagent_in_win7_client_edit_to_taste.sh
+
+Slightly less quick
+===================
+./migrate.py --qemu <full path to qemu-kvm executable> --image <image to test> --client spicec
+
+Will default to looking for 'which qemu-system-x86_64' and using no image (so just bios,
+not really testing qxl, but yes testing the switch host functionality)
+
+defaults to a very small subset of the actual command line used by libvirt:
+vdagent (virtio-serial)
+usbtablet
+qxl
+
+defaults to spicy client, hence --client spicec required to test current client.
+
+no virtio-net (so the default slirp based network with rtl8139), no virtio-block.
+
+if supplied with --image will run it in -snapshot.
+
+
+Full migrate.py --help
+======================
+
+usage: migrate.py [-h] [--qmp1 QMP1] [--qmp2 QMP2] [--spice_port1 SPICE_PORT1]
+ [--spice_port2 SPICE_PORT2] [--migrate_port MIGRATE_PORT]
+ [--client_count CLIENT_COUNT] [--qemu QEMU]
+ [--log_filename LOG_FILENAME] [--image IMAGE]
+ [--client {spicec,spicy}] [--vdagent {on,off}]
+ [--usbtablet {on,off}]
+
+Process some integers.
+
+optional arguments:
+ -h, --help show this help message and exit
+ --qmp1 QMP1
+ --qmp2 QMP2
+ --spice_port1 SPICE_PORT1
+ --spice_port2 SPICE_PORT2
+ --migrate_port MIGRATE_PORT
+ --client_count CLIENT_COUNT
+ --qemu QEMU
+ --log_filename LOG_FILENAME
+ --image IMAGE
+ --client {spicec,spicy}
+ --vdagent {on,off}
+ --usbtablet {on,off}
diff --git a/do_qemu_com_redhat_qxl_screendump.sh b/do_qemu_com_redhat_qxl_screendump.sh
new file mode 100755
index 0000000..92d84a0
--- /dev/null
+++ b/do_qemu_com_redhat_qxl_screendump.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+which qemu-system-x86_64
+# The sleep is due to a bug (unreported) with -qmp stdio usage.
+# can be removed if I use a unix socket probably.
+(
+sleep 1
+echo '{ "execute" : "qmp_capabilities" }'
+echo '{ "execute" : "__com.redhat_qxl_screendump", "arguments" : { "id" : "0", "filename" : "test.ppm" } }'
+echo '{ "execute" : "quit" }') | qemu-system-x86_64 -spice port=9999 -qmp stdio
+
diff --git a/do_qemu_info_spice.sh b/do_qemu_info_spice.sh
new file mode 100755
index 0000000..01c3b8a
--- /dev/null
+++ b/do_qemu_info_spice.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+(echo info spice; echo quit) | qemu-system-x86_64 -spice port=9999 -monitor stdio
+
diff --git a/do_qemu_query_spice.sh b/do_qemu_query_spice.sh
new file mode 100755
index 0000000..c1c079c
--- /dev/null
+++ b/do_qemu_query_spice.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+(
+sleep 1
+echo '{"execute": "qmp_capabilities"}'
+echo '{"execute":"query-spice"}'
+echo '{ "execute": "quit" }') | qemu-system-x86_64 -spice port=9999 -qmp stdio
+
diff --git a/do_qemu_query_spice_disabled.sh b/do_qemu_query_spice_disabled.sh
new file mode 100755
index 0000000..85a4dfd
--- /dev/null
+++ b/do_qemu_query_spice_disabled.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+(
+sleep 1
+echo '{"execute": "qmp_capabilities"}'
+echo '{"execute":"query-spice"}'
+echo '{ "execute": "quit" }') | qemu-system-x86_64 -qmp stdio
+
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()
diff --git a/screen_dump_qxl b/screen_dump_qxl
new file mode 100755
index 0000000..1511f96
--- /dev/null
+++ b/screen_dump_qxl
@@ -0,0 +1,124 @@
+#!/usr/bin/python
+
+import argparse
+import sys
+import os
+import subprocess
+import pyinotify
+import time
+import gtk
+import Image
+import atexit
+
+parser = argparse.ArgumentParser()
+parser.add_argument('--qmp-shell-root', default='/home/alon/src/spice_upstream/qemu/QMP')
+parser.add_argument('--host', default='localhost')
+parser.add_argument('--port', default=0, type=int)
+parser.add_argument('--path', default='')
+parser.add_argument('--filename', default='/tmp/a.ppm')
+parser.add_argument('--show', default='showimage')
+parser.add_argument('--noerase', dest='erase', default=True, action='store_false')
+parser.add_argument('--id', default='')
+parser.add_argument('--times', default=1, type=int)
+parser.add_argument('--sleep', default=0, type=int)
+args = parser.parse_args(sys.argv[1:])
+if not args.path and not args.port:
+ qmp_addr = '/tmp/qmp'
+elif args.path:
+ qmp_addr = args.path
+else:
+ qmp_addr = (args.host, args.port)
+print "connecting to %s" % str(qmp_addr)
+qmp_shell = '%s/qmp-shell' % args.qmp_shell_root
+if not os.path.exists(qmp_shell):
+ print("qmp_shell not at %s" % qmp_shell)
+ os.exit(1)
+sys.path.append(args.qmp_shell_root)
+import qmp
+mon = qmp.QEMUMonitorProtocol(address=qmp_addr)
+mon.connect()
+
+def get_notifier_events():
+ notifier.process_events()
+ if notifier.check_events(10):
+ # read notified events and enqeue them
+ notifier.read_events()
+ return True
+
+class GUI:
+ def __init__(self, args):
+ self.window = gtk.Window()
+ self.image = gtk.Image()
+ self.vbox = gtk.VBox()
+ self.window.add(self.vbox)
+ self.vbox.add(self.image)
+ self.button = gtk.Button()
+ self.button.set_label('take screenshot')
+ self.button.connect("clicked", self.take_screenshot)
+ self.vbox.add(self.button)
+ self.current_image = 0
+ self.times = args.times
+ self.args = args
+ self.window.show_all()
+
+ def take_screenshot(self, _button):
+ # use timeout_add to avoid being inside the button callback.
+ gtk.timeout_add(0, self.take_screen_shot)
+
+ def show_image(self, filename):
+ im = Image.open('/tmp/a.ppm')
+ buf=gtk.gdk.pixbuf_new_from_data(im.tostring(),
+ gtk.gdk.COLORSPACE_RGB, 0, 8, im.size[0], im.size[1],
+ im.size[0]*3)
+ self.image.set_from_pixbuf(buf)
+ self.current_image += 1
+ self.window.set_title(str(self.current_image))
+ # proceed in a different stack to allow update of screen
+ gtk.timeout_add(args.sleep, self.take_screen_shot)
+
+ def take_screen_shot(self):
+ if self.current_image < self.times:
+ take_screen_dump(self.args.id, self.args.filename)
+ else:
+ raw_input('press enter to quit')
+ sys.exit(0)
+ return False
+
+gui = GUI(args)
+
+if os.path.exists(args.filename):
+ print "removing old %s" % args.filename
+ os.unlink(args.filename)
+
+def cleanup():
+ if args.erase:
+ print("erasing %s" % args.filename)
+ os.unlink(args.filename)
+
+atexit.register(cleanup)
+
+# watch for write to target
+class ProcessFile(pyinotify.ProcessEvent):
+ def process_IN_CLOSE_WRITE(self, event):
+ if event.name != os.path.basename(args.filename):
+ return
+ gui.show_image(args.filename)
+
+if args.show:
+ wm = pyinotify.WatchManager()
+ notifier = pyinotify.Notifier(wm, ProcessFile())
+ wdd = wm.add_watch(os.path.dirname(args.filename), pyinotify.IN_CLOSE_WRITE)
+
+def take_screen_dump(qxl_id, filename):
+ print "saving qxl_id %r to %s" % (qxl_id, filename)
+ if qxl_id != '':
+ mon.cmd('__com.redhat_qxl_screendump', dict(id=str(qxl_id),
+ filename=filename))
+ else:
+ mon.cmd('screendump', dict(filename=filename))
+
+take_screen_dump(args.id, args.filename)
+
+# ugly - can't we connect gtk to inotify directly
+gtk.timeout_add(10, get_notifier_events)
+gtk.mainloop()
diff --git a/spice_make_certs.sh b/spice_make_certs.sh
new file mode 100755
index 0000000..7765def
--- /dev/null
+++ b/spice_make_certs.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+SERVER_KEY=server-key.pem
+
+# creating a key for our ca
+if [ ! -e ca-key.pem ]; then
+ openssl genrsa -des3 -out ca-key.pem 1024
+fi
+# creating a ca
+if [ ! -e ca-cert.pem ]; then
+ openssl req -new -x509 -days 1095 -key ca-key.pem -out ca-cert.pem -subj "/C=IL/L=Raanana/O=Red Hat/CN=my CA"
+fi
+# create server key
+if [ ! -e $SERVER_KEY ]; then
+ openssl genrsa -out $SERVER_KEY 1024
+fi
+# create a certificate signing request (csr)
+if [ ! -e server-key.csr ]; then
+ openssl req -new -key $SERVER_KEY -out server-key.csr -subj "/C=IL/L=Raanana/O=Red Hat/CN=my server"
+fi
+# signing our server certificate with this ca
+if [ ! -e server-cert.pem ]; then
+ openssl x509 -req -days 1095 -in server-key.csr -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 -out server-cert.pem
+fi
+
+# now create a key that doesn't require a passphrase
+openssl rsa -in $SERVER_KEY -out $SERVER_KEY.insecure
+mv $SERVER_KEY $SERVER_KEY.secure
+mv $SERVER_KEY.insecure $SERVER_KEY
+
+# show the results (no other effect)
+openssl rsa -noout -text -in $SERVER_KEY
+openssl rsa -noout -text -in ca-key.pem
+openssl req -noout -text -in server-key.csr
+openssl x509 -noout -text -in server-cert.pem
+openssl x509 -noout -text -in ca-cert.pem
+
+# echo --host-subject
+echo "your --host-subject is" \" `openssl x509 -noout -text -in server-cert.pem | grep Subject: | cut -f 10- -d " "` \"
diff --git a/spice_migrate.py b/spice_migrate.py
new file mode 100755
index 0000000..b40b3e8
--- /dev/null
+++ b/spice_migrate.py
@@ -0,0 +1,450 @@
+#!/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/spice_migrate.py
+
+Will create two temporary unix sockets in /tmp
+Will leave a log file, migrate.log, in current directory.
+
+Has a gui now:
+ ./spice_migrate.py --gtk --sleep 5
+ launch gtk gui
+ wait for "start migration" button press
+ update button label when migrations happen
+ sleep 5 seconds between migrations
+
+"""
+
+"""
+uses glib event loop for timers
+"""
+
+#
+# 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
+import resource
+import glib
+
+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-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')
+ parser.add_argument('--vdagent', choices=['on', 'off'], default='on')
+ parser.add_argument('--usbtablet', choices=['on', 'off'], default='on')
+ parser.add_argument('--secure', choices=['on', 'off'], default='off')
+ parser.add_argument('--host-subject', default=None)
+ parser.add_argument('--sleep', default=20, type=float)
+ parser.add_argument('--glib', default=False, action='store_true')
+ parser.add_argument('--gtk', default=False, action='store_true')
+ 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)
+ orig_client = args.client
+ if not os.path.exists(args.client):
+ args.client = os.popen('which %s' % args.client).read().strip()
+ if not os.path.exists(args.client):
+ print "client %r not found" % orig_client
+ sys.exit(1)
+ print "qemu = %s" % args.qemu_exec
+ print "LD_LIBRARY_PATH=%s" % os.environ.get('LD_LIBRARY_PATH', None)
+ #print "ldd qemu:"
+ #os.system('ldd %s' % args.qemu_exec)
+ return args
+
+def start_qemu(qemu_exec, image, spice_port, qmp_connection,
+ incoming_port=None, extra_args=[], secure=False,
+ debug=None, verbose=False):
+ incoming_args = []
+ if incoming_port:
+ incoming_args = ("-incoming tcp::%s" % incoming_port).split()
+ spice_params = ["disable-ticketing"]
+ if secure:
+ spice_params.extend(['tls-port=%s' % spice_port, 'x509-dir=%s' % os.getcwd()])
+ else:
+ spice_params.append("port=%s" % spice_port)
+ if ':' not in qmp_connection:
+ qmp_argument = "unix:%s,server,nowait" % qmp_connection
+ else:
+ host, port = qmp_connection.split(':')
+ qmp_argument = "socket:host=%s,port=%s,server,nowait" % (host, port)
+ args = ([qemu_exec, "--enable-kvm", "-qmp", qmp_argument,
+ '-spice', ','.join(spice_params)] + extra_args + incoming_args)
+ if os.path.exists(image):
+ args += ["-m", "512", "-drive",
+ "file=%s,index=0,media=disk,cache=unsafe" % image, "-snapshot"]
+ if debug:
+ executable = debug[0]
+ args = list(debug) + args
+ else:
+ executable = qemu_exec
+ if verbose:
+ print repr(args)
+ proc = Popen(args, executable=qemu_exec, stdin=PIPE, stdout=PIPE)
+ while not os.path.exists(qmp_connection):
+ time.sleep(0.1)
+ proc.qmp_connection = qmp_connection
+ proc.qmp = qmp.QEMUMonitorProtocol(qmp_connection)
+ 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 client_old_commandline(spice_port, secure, host_subject):
+ cmdline = ['-h', 'localhost']
+ if secure:
+ cmdline.extend(['-s', str(spice_port), '--host-subject', host_subject, '--ca-file',
+ os.path.join(os.getcwd(), 'ca-cert.pem')])
+ else:
+ cmdline.extend(['-p', str(spice_port)])
+ return cmdline
+
+def client_url_commandline(spice_port, secure, host_subject):
+ if secure:
+ raise Exception("don't know how a spice:// works for secure connections")
+ return ['spice://localhost:%s' % (spice_port)]
+
+def start_client(client, spice_port, secure, host_subject, verbose=False):
+ client = str(client)
+ if (client.endswith('remote-viewer')):
+ cmdline = client_url_commandline(spice_port, secure, host_subject)
+ else:
+ cmdline = client_old_commandline(spice_port, secure, host_subject)
+ cmdline = [client] + cmdline
+ if verbose:
+ print cmdline
+ print ' '.join(cmdline)
+ return Popen(cmdline, executable=client)
+
+def _wait_active(q, active):
+ events = ["RESUME"] if active else ["STOP"]
+ while True:
+ try:
+ ret = q.cmd("query-status")
+ except:
+ # ValueError
+ yield '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
+ yield 'sleep', (0.5)
+
+def _wait_for_event(q, event):
+ while True:
+ for e in q.get_events():
+ if e["event"] == event:
+ return
+ yield 'sleep', (0.5)
+
+def cleanup(migrator):
+ print "doing cleanup"
+ migrator.close()
+
+known_commands = {
+ 'wait_active': _wait_active,
+ 'wait_for_event': _wait_for_event
+}
+
+def blocker(it):
+ for cmd, args in it:
+ if not hasattr(args, '__len__'):
+ args = (args,)
+ if cmd in known_commands:
+ blocker(known_commands[cmd](*args))
+ elif cmd == 'sleep':
+ time.sleep(*args)
+ else:
+ import pdb; pdb.set_trace()
+ raise Exception('unhandled cmd %s' % cmd)
+
+def glib_wrap_iter(it):
+ def sleep_done(*args):
+ glib_wrap_iter(it)
+ return False
+ try:
+ cmd, args = it.next()
+ except StopIteration:
+ return
+ if not hasattr(args, '__len__'):
+ args = (args,)
+ if cmd in known_commands:
+ def handle_and_continue(cmd1, args1):
+ it2 = known_commands[cmd1](*args1)
+ for cmd, args in it2:
+ yield cmd, args
+ glib_wrap_iter(it)
+ return glib_wrap_iter(handle_and_continue(cmd1=cmd, args1=args))
+ elif cmd == 'sleep':
+ assert(len(args) == 1)
+ glib.timeout_add(int(1000 * args[0]), sleep_done)
+ else:
+ raise Exception("unhandled cmd")
+
+def test_glib_wrap_iter():
+ mainloop = glib.MainLoop()
+ def it():
+ t = time.time()
+ yield 'sleep', 0.1
+ t2 = time.time()
+ yield 'sleep', 0.3
+ t3 = time.time()
+ print ('%2.2f, %2.2f' % (t2 - t, t3 - t2))
+ mainloop.quit()
+ raise SystemExit
+ glib_wrap_iter(it())
+ mainloop.run()
+
+class Migrator(object):
+
+ migration_count = 0
+ on_migrate = lambda self, mig_count: None
+
+ def __init__(self, log, client, qemu_exec, image, monitor_files, client_count=1,
+ spice_ports=[17000, 17001], migration_port=17002, vdagent=False,
+ usbtablet=False, secure=False, host_subject='',
+ extra_args=[], extra_per_vm=([], []), sound=False):
+ 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
+ self.secure = secure
+ self.host_subject = host_subject
+ if self.vdagent:
+ extra_args.extend(['-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'])
+ if sound:
+ extra_args.extend(['-device', 'intel-hda,id=sound0', '-device', 'hda-duplex,id=sound0-codec0,bus=sound0.0,cad=0'])
+ self.extra_args = extra_args
+ self.extra_per_vm = extra_per_vm
+ 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
+ extra_args = self.extra_args + self.extra_per_vm[which]
+ return start_qemu(qemu_exec=self.qemu_exec, image=self.image,
+ spice_port=self.spice_ports[which],
+ qmp_connection=self.monitor_files[which],
+ extra_args=extra_args,
+ secure=self.secure,
+ **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 start_clients(self):
+ if len(self.clients) == self.client_count:
+ return 0
+ start_count = len(self.clients)
+ for i in range(len(self.clients), self.client_count):
+ self.log.write("launching a client %d\n" % (len(self.clients) + 1))
+ self.clients.append(start_client(client=self.client,
+ spice_port=self.spice_ports[0], secure=self.secure, host_subject=self.host_subject))
+ return len(self.clients) - start_count
+
+ def _iterate(self, wait_for_user_input=False):
+ yield 'wait_active', (self.active.qmp, True)
+ yield 'wait_active', (self.target.qmp, False)
+ started = self.start_clients()
+ if started > 0:
+ # wait for all clients events
+ for i in xrange(started):
+ yield 'wait_for_event', (self.active.qmp, 'SPICE_INITIALIZED')
+ if wait_for_user_input:
+ print "waiting for Enter to start migrations"
+ raw_input()
+ migrate_info_arguments = {'protocol':'spice', 'hostname':'localhost'}
+ if self.secure:
+ migrate_info_arguments['port'] = -1
+ migrate_info_arguments['tls-port'] = self.target.spice_port
+ migrate_info_arguments['cert-subject'] = self.host_subject
+ else:
+ migrate_info_arguments['port'] = self.target.spice_port
+ self.active.qmp.cmd('client_migrate_info', migrate_info_arguments)
+ self.active.qmp.cmd('migrate', {'uri': 'tcp:localhost:%s' % self.migration_port})
+ yield 'wait_active', (self.active.qmp, False)
+ yield 'wait_active', (self.target.qmp, True)
+ yield '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_connection = dead.qmp_connection
+ 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)
+ self.migration_count += 1
+ self.on_migrate(self.migration_count)
+
+ def blocking_iterate(self, wait_for_user_input=False):
+ blocker(self._iterate(wait_for_user_input))
+
+ def loop(self, dt):
+ dt = int(dt)
+ self.count = 0
+ def single():
+ def single_iter():
+ for cmd, args in self._iterate(False):
+ yield cmd, args
+ self.count += 1
+ glib.timeout_add(dt, single)
+ glib_wrap_iter(single_iter())
+ return False
+ single()
+
+def start_migrator(log, args, extra_args, extra_per_vm):
+ 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'),
+ secure=(args.secure=='on'), host_subject=args.host_subject,
+ extra_args=extra_args, extra_per_vm=extra_per_vm, sound=False)
+ atexit.register(cleanup, migrator)
+ return migrator
+
+def qmp_gui(args, migrator):
+ import gtk
+ mainloop = glib.MainLoop()
+ def on_click(button):
+ migrator.loop(args.sleep*1000)
+ def on_quit(button):
+ mainloop.quit()
+ def on_migrate(mig_count):
+ b.set_label("migrated %d" % mig_count)
+ vbox = gtk.VBox()
+ w = gtk.Window()
+ w.add(vbox)
+ bquit = gtk.Button('quit')
+ bquit.connect("clicked", on_quit)
+ vbox.add(bquit)
+ b = gtk.Button("start migration")
+ vbox.add(b)
+ b.connect("clicked", on_click)
+ w.show_all()
+ migrator.on_migrate = on_migrate
+ return mainloop
+
+def main_gui(log, args, extra_args, extra_per_vm=([], [])):
+ migrator = start_migrator(log=log, args=args, extra_args=extra_args,
+ extra_per_vm=extra_per_vm)
+ migrator.start_clients()
+ mainloop = qmp_gui(args, migrator)
+ mainloop.run()
+
+def main_console(log, args, extra_args):
+ migrator = start_migrator(log=log, args=args, extra_args=extra_args,
+ extra_per_vm=([], []))
+ if args.glib:
+ migrator.loop(args.sleep*1000)
+ mainloop = glib.MainLoop()
+ mainloop.run()
+ else:
+ for i in xrange(50):
+ migrator.blocking_iterate()
+ print "sleeping %d seconds" % args.sleep
+ time.sleep(args.sleep)
+
+if __name__ == '__main__':
+ # Limit core dumps to 10GB (default is no core dumps)
+ resource.setrlimit(resource.RLIMIT_CORE, (10*1000*1000*1000, -1))
+ args = get_args()
+ extra_args = ['-vga', 'qxl', '-device', 'qxl']
+ host_subject = None
+ if args.secure == 'on':
+ if any([not os.path.exists(f) for f in "ca-key.pem ca-cert.pem server-cert.pem server-key.pem".split()]):
+ os.system('./spice_make_certs.sh')
+ args.host_subject = ','.join(os.popen('openssl x509 -noout -text -in server-cert.pem | grep Subject: | cut -f 10- -d " "').read().strip().split(', '))
+ print "log file %s" % args.log_filename
+ log = open(args.log_filename, "a+")
+ log.write("# "+str(datetime.datetime.now())+"\n")
+ if args.gtk:
+ main_gui(log, args, extra_args)
+ else:
+ main_console(log, args, extra_args)
diff --git a/test_vdagent_in_win7_client_edit_to_taste.sh b/test_vdagent_in_win7_client_edit_to_taste.sh
new file mode 100755
index 0000000..04e85af
--- /dev/null
+++ b/test_vdagent_in_win7_client_edit_to_taste.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+./migrate.py --client spicec --qemu /home/alon/spice/rhel6/bin/qemu-system-x86_64 --image /store/images/win7x86_qxl_tests.img