diff options
-rw-r--r-- | README | 56 | ||||
-rwxr-xr-x | do_qemu_com_redhat_qxl_screendump.sh | 10 | ||||
-rwxr-xr-x | do_qemu_info_spice.sh | 3 | ||||
-rwxr-xr-x | do_qemu_query_spice.sh | 7 | ||||
-rwxr-xr-x | do_qemu_query_spice_disabled.sh | 7 | ||||
-rw-r--r-- | qmp.py | 157 | ||||
-rwxr-xr-x | screen_dump_qxl | 124 | ||||
-rwxr-xr-x | spice_make_certs.sh | 39 | ||||
-rwxr-xr-x | spice_migrate.py | 450 | ||||
-rwxr-xr-x | test_vdagent_in_win7_client_edit_to_taste.sh | 2 |
10 files changed, 854 insertions, 1 deletions
@@ -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 + @@ -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 |