From 27734ecaddedd26d379009b671c6cb170c01c719 Mon Sep 17 00:00:00 2001 From: Alon Levy Date: Tue, 19 Jul 2011 15:57:45 +0300 Subject: multiple add spicedump hooks add SIGHUP handler (you can do killall -SIGHUP win7) --- spice2 | 348 +++++++++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 251 insertions(+), 97 deletions(-) diff --git a/spice2 b/spice2 index d76d6f3..a7a2d5a 100755 --- a/spice2 +++ b/spice2 @@ -11,6 +11,7 @@ import datetime import re import time import shutil +import itertools try: import guestfs @@ -19,15 +20,33 @@ except: print "missing guestfs or hivex" guestfs = None +def which(x): + for f in path.split(':'): + full_path = os.path.join(f, x) + if os.path.exists(full_path): + return full_path + return None + +################################################################################ +lddpath = which('ldd') +spicedump_exe = which('spicedump.py') +python_exe = which('python') +#python_exe = which('pypy') + HOME = os.environ['HOME'] ################################################################################ +# Edit instead of this default section. +config_path = os.path.join(HOME, '.spice_launcher') +initial_defaults = """# vim: set filetype=python : +import os +HOME = os.environ['HOME'] qemu_exec_name = "qemu-system-x86_64" images = { 'windevel': '/store/images/windbg_winxp.img', 'winxp': '/store/images/winxp_sp3_qxl_after_cirrus.img', 'win7': '/home/alon/images2/win7x86_qxl_tests.img', - 'kindle': '/home/alon/images2/win7_kindle.img', 'win2008r2': '/media/PassportExt4/images/win2008r2_qxl.qcow2', + 'wlk': '/media/PassportExt4/images/win2008r2_wlk.qcow2', 'f14ccid': '/media/PassportExt4/images/F14_CCID.testing.qcow2', 'winxp.inst': '/store/images/winxp_installation_test.qcow2', } @@ -39,17 +58,29 @@ qemus = {'rhel6':'rhel6', 'upstream':'upstream'} qemu_default = 'upstream' defaults = dict( + winxp=dict( + qemu='upstream', + args='--port 9001 --clients 1 --tablet --guestdebug 1 --qxldebug 1 --windbg-client 9000'.split(), + ), + wlk=dict( + qemu='upstream', + args='--port 9002 --clients 1 --tablet'.split(), + ), win7=dict( qemu='upstream', - args='--port 8888 --clients 1 --tablet --guestdebug 2 --qxldebug 1 --windbg-client 9000'.split() + args='--port 9003 --clients 1 --tablet --guestdebug 2 --qxldebug 1 --windbg-client 9000'.split() ), windevel=dict( qemu='rhel6', - args='--port 6666 --clients 1 --windbg-server 9000'.split() + args='--port 9004 --clients 1 --windbg-server 9000'.split() ), f14ccid=dict( qemu='upstream', - args='--port 9999 --clients 1 --tablet --guestdebug 4 --qxldebug 1 --smartcard'.split() + args='--port 9005 --clients 1 --tablet --guestdebug 4 --qxldebug 1 --smartcard'.split() + ), + win2008r2=dict( + qemu='upstream', + args='--port 9006 --clients 1 --tablet --revision 2 --'.split(), ), ) @@ -57,6 +88,27 @@ spice_src_path = os.path.join(HOME, 'src/spice_upstream/spice') qxl_win_root = os.path.join(HOME, 'shared_win/qxl/dist') +# required utilities for automated windows tests +res_change_exe = HOME+'/shared_win/spice_upstream/util/reschange/reschange.exe' +suspend_exe = HOME+'/shared_win/spice_upstream/util/suspend/suspend.exe' +""" + +if not os.path.exists(config_path): + with open(config_path, 'w+') as fd: + fd.write(initial_defaults) + print "created config file %s" % config_path +with open(config_path, 'r') as fd: + exec(fd) + +for name, default_location in [ + ('res_change_exe', 'res_change.exe'), + ('suspend_exe', ('suspend.exe')]: + if not os.path.exists(globals()[name]) and os.path.exists(default_location): + print "config file points to non existant %r (%r), changing to local copy" % ( + name, globals()[name]) + globals()[name] = default_location + + ################################################################################ win_tests = dict( @@ -69,7 +121,16 @@ shutdown /p """ % (""" c:\suspend.exe rem ping -n 2 localhost -""" * 20), files=[HOME+'/shared_win/spice_upstream/util/suspend/suspend.exe']), +""" * 20), files=[suspend_exe]), +suspend2=dict(contents=r""" +set RESCHANGE="c:\reschange.exe" +%%RESCHANGE%% -attach -mon 1024,768,32,0,0 -mon 800,600,32,1024,0 +%s +shutdown /p +""" % (r""" +c:\suspend.exe +rem ping -n 2 localhost +""" * 20), files=[res_change_exe, suspend_exe]), resolution1=dict(contents=r""" set RESCHANGE="c:\reschange.exe" %s @@ -80,7 +141,7 @@ shutdown /p %RESCHANGE% -mon 1024,768,32,0,0 %RESCHANGE% -mon 768,1024,32,0,0 %RESCHANGE% -mon 1024,768,32,0,0 -""" * 60), files=[HOME+'/shared_win/spice_upstream/util/reschange/reschange.exe']), +""" * 60), files=[res_change_exe]), resolution2=dict(contents=r""" set RESCHANGE="c:\reschange.exe" %s @@ -90,11 +151,45 @@ shutdown /p %RESCHANGE% -attach -mon 1024,768,32,0,0 %RESCHANGE% -attach -mon 1024,768,32,0,0 -mon 800,600,32,1024,0 %RESCHANGE% -attach -mon 1024,768,32,0,0 -""" * 60), files=[HOME+'/shared_win/spice_upstream/util/reschange/reschange.exe']), +""" * 60), files=[res_change_exe]), ) ################################################################################ +""" +tests are run without any clients connected, right now they can only +create / kill clients, later they should: + send events to the clients (mouse/keyboard) + parse all messages going to client and coming from client + parse all messages going from qemu to guest agent and from it + (presumably this can be done using a proxy and two chardevs) + send qmp messages to qemu (connect/disconnect stuff, do screenshots) + +Yes, this is all autotest stuff. maybe I should ask them to turn autotest into +a library. +""" +def test_agent_connection_stress(server, args): + display = 2 + run_xephyr(display=display) + auto_conf = itertools.cycle([False, True]) + times = itertools.chain([120], itertools.repeat(10)) + for i in xrange(100): + client = run_client(host=args.spice_host, port=args.spice_port, + oldclient=True, smartcard=False, + auto_conf=auto_conf.next(), + certs=[], dbdir=None, + display=display) + t = times.next() + print "sleeping %d" % t + time.sleep(t) + client.spice_kill() + client.wait() + +g = globals().keys() +tests = [x[5:] for x in g if x.startswith('test_')] + +################################################################################ + red = "\x1b[101m%s\x1b[0m" path = os.environ['PATH'] @@ -106,13 +201,6 @@ def set_title(x): def file_in_use(x): return not os.system('lsof -t %s > /dev/null' % x) -def which(x): - for f in path.split(':'): - full_path = os.path.join(f, x) - if os.path.exists(full_path): - return full_path - return None - def oldness(f): if not os.path.exists(f): return 'file doesn\'t exist' @@ -125,7 +213,8 @@ def oldness(f): return '%d minutes' % int(d.seconds / 60) return '%d seconds' % d.seconds -lddpath = which('ldd') +################################################################################ + def get_lib(fname, rexp): l = os.popen('%s "%s"' % (lddpath, fname)).readlines() refs_raw=[x.split('=>') for x in l if '=>' in x] @@ -190,23 +279,24 @@ def get_image(args): print "image %s" % image_fullpath return image_fullpath +test_progs = { + 'streaming': 'test_display_streaming', + 'display': 'test_display_no_ssl', + 'playback': 'test_playback', + 'sockets': 'test_just_sockets_no_ssl', + 'replay': 'replay', +} def build_test_cmdline(args): - test_progs = { - 'streaming': 'test_display_streaming', - 'display': 'test_display_no_ssl', - 'playback': 'test_playback', - 'sockets': 'test_just_sockets_no_ssl', - 'replay': 'replay', - } - parts = (['/usr/bin/libtool', '--mode=execute', 'cgdb', '--args'] if args.cgdb else []) + [os.path.join(spice_src_path, 'server/tests', test_progs[args.test])] - if args.test == 'replay' and args.replay_file: + parts = (['/usr/bin/libtool', '--mode=execute', 'cgdb', '--args'] if args.cgdb else []) + [os.path.join(spice_src_path, 'server/tests', + test_progs[args.test_prog])] + if args.test_prog == 'replay' and args.replay_file: parts.append(args.replay_file) # tests have this port hardcoded args.port = 5912 return ' '.join(parts) def build_cmdline(args): - if args.test: + if args.test_prog: return build_test_cmdline(args) qemu = args.qemu if qemu not in qemus: @@ -263,7 +353,7 @@ def build_cmdline(args): else: print "nothing listening on %d, do you want to start something there? (p.s. maybe systemd service?)" % args.windbg_client # smartcard - if args.smartcard: + if args.smartcard != 'off': ccid_debug = 1 passthru_debug = 1 cmdline.extend([' -chardev spicevmc,id=smartcard,debug=3,name=smartcard', @@ -299,7 +389,7 @@ def build_cmdline(args): if not os.path.exists(args.cdrom): print "missing cdrom image %r" % args.cdrom sys.exit(1) - cmdline.append(' -cdrom "%s"' % args.cdrom) + cmdline.append(' -cdrom %s' % args.cdrom) ###### cmdline = ''.join(cmdline) @@ -321,28 +411,42 @@ def build_cmdline(args): cmdline = '/usr/bin/time --verbose %s' % cmdline return cmdline -def run_xephyr(display, size=(1024,768+40)): +def run_xephyr(display, size=(1024,768+40), show_output=False): + # TODO -wait for it to start listening to socket return start_process(('Xephyr -screen %dx%d :%d.0' % (size[0], size[1], display)).split()) -def run_client(host, port, oldclient, smartcard, certs, dbdir, display=None): +def rpdb2_break(): + import rpbd2 + rpdb2.rpdb2.start_embedded_debugger('a') + +def run_client(host, port, oldclient, smartcard, certs, dbdir, display=None, + auto_conf=False): s_opts = '' + ac_opts = '' old_display = None if display is not None: old_display = os.environ['DISPLAY'] os.environ['DISPLAY'] = ':%d.0' % display - if smartcard and certs: + if smartcard == 'sw': if oldclient: s_opts = '--smartcard --smartcard-certs %s --smartcard-db %s' % ( ','.join(certs), dbdir) else: s_opts = '--certificates=%s --certificate-db=%s' % ( ','.join(certs), dbdir) - + if auto_conf and oldclient: + ac_opts = '--full-screen=auto-conf' + if dbdir and not os.path.exists(dbdir): + print "error - dbdir supplied does not exist" + sys.exit(1) p = start_process( - args=('%(exe)s -h %(host)s -p %(port)s %(smartcard)s' % dict( - host=host, port=port, exe=spicec_exe if oldclient else spicy_exe, - smartcard=s_opts)).split(), kill=True) + args=('%(exe)s -h %(host)s -p %(port)s %(smartcard)s %(autoconf)s' % + dict( + host=host, port=port, + exe=spicec_exe if oldclient else spicy_exe, + autoconf=ac_opts, + smartcard=s_opts)).split(), kill=True) if old_display: os.environ['DISPLAY'] = old_display return p @@ -381,25 +485,30 @@ def parseargs(): # if provided with a name of the form image.qemu take that # instead of --image , --qemu # but still let --image and --qemu override it - image, qemu = None, None - name = sys.argv[0] - parts = name.split('.') + image, qemu = None, qemu_default + name = os.path.basename(sys.argv[0]) default_args = [] - if len(parts) == 1: - # this used to be a bunch of bash scripts until I found it prevented - # from ever quoting a space - image = os.path.basename(parts[0]) - if len(parts) == 2: - image, qemu = parts - image = os.path.basename(image) - if image: - if image in defaults: - default_args = defaults[image]['args'] - if image not in images: - print "missing image %r" % image - sys.exit(1) - image = images[image] - parser = argparse.ArgumentParser(description='Process some integers.') + parts = [] + if os.path.islink(sys.argv[0]) and name != os.path.basename(os.readlink(sys.argv[0])): + # you can symlink spice2 -> win7.upstream or win7, it will run the win7 + # from the images dictionary + parts = name.split('.') + base_part = parts[0] if len(parts) >= 1 else None + if len(parts) == 1: + if base_part in defaults and 'qemu' in defaults[base_part]: + qemu = defaults[base_part]['qemu'] + if len(parts) == 2: + image, qemu = parts + image = os.path.basename(image) + if base_part: + if base_part in defaults: + default_args = defaults[base_part]['args'] + if base_part not in images: + print "missing key in images %r" % base_part + print images.keys() + sys.exit(1) + image = images[base_part] + parser = argparse.ArgumentParser(description='SPICE Launcher script, mark 2. Run spice, qemu, spice tests, automated tests on pre prepared images. Still missing: automated driver installation.') parser.add_argument('--record-cmd', dest='record_cmd', help='record qxl command ring') parser.add_argument('--copy-in', dest='copy_in', action='append') parser.add_argument('--nobacking-file', default=True, action='store_false', dest='backing_file', help='use the image as a backing file for the actual image') @@ -430,15 +539,14 @@ def parseargs(): parser.add_argument('--windbg-client', dest='windbg_client', type=int, default=None) parser.add_argument('--memory', dest='memory', type=int, default=512) parser.add_argument('--cpus', dest='cpus', type=int, default=2) - parser.add_argument('--nosmartcard', dest='smartcard', action='store_false', default=False) - parser.add_argument('--smartcard', dest='smartcard', action='store_true', default=False) + parser.add_argument('--smartcard', dest='smartcard', choices=['off','hw','sw'], default='off') parser.add_argument('--smartcard-dbdir', dest='dbdir') parser.add_argument('--smartcard-certs', dest='certs', action='append') parser.add_argument('--debugbios', dest='debugbios', action='store_true', default=False) parser.add_argument('--image', dest='image', default=image) parser.add_argument('--cdrom', dest='cdrom', default=None) parser.add_argument('--winqxl-cdrom', dest='winqxl_cdrom', default=False, action='store_true') - parser.add_argument('--qemu', dest='qemu', default=None, choices=qemus.keys() + [None]) + parser.add_argument('--qemu', dest='qemu', default=qemu, choices=qemus.keys() + [None]) parser.add_argument('--incoming', dest='incoming', type=int, default=None) parser.add_argument('--title', dest='title', default=None) parser.add_argument('--second', dest='second', default=False, action='store_true') @@ -448,19 +556,25 @@ def parseargs(): parser.add_argument('--vncclients', dest='vnc_clients', type=int, default=1) parser.add_argument('--cpu', dest='cpu', choices=['host'], default='host', help='qemu cpu flag') parser.add_argument('--keep-temp-backing-file', dest='erase_temp_backing_file', default=True, action='store_false') + parser.add_argument('--spicedump', dest='spicedump', default=False, action='store_true') + parser.add_argument('--spicedump-filter', dest='spicedump_filter') # convenient to run tests via the same framework, though this is not using # any of of the qemu flags, just the client running parts. - parser.add_argument('--test', dest='test', choices=['streaming', 'display', 'playback', 'replay']) + parser.add_argument('--test-prog', dest='test_prog', + choices=test_progs.keys()) + parser.add_argument('--test-func', dest='test_func', choices=tests) parser.add_argument('--replay-file', dest='replay_file') args = parser.parse_args(default_args + sys.argv[1:]) - if len(parts) == 1 and not args.test and not args.image: + if len(parts) == 1 and not args.test_prog and not args.image: if image not in defaults: print "symlink %r not in %r" % (image, defaults.keys()) sys.exit(1) if not args.qemu: args.qemu = defaults[image]['qemu'] - if not args.qemu: - args.qemu = qemu_default + elif not args.image: + print "image not provided. use --image" + parser.print_usage() + sys.exit(1) return args def set_runonce_registry(g, root, runonce_paths): @@ -473,7 +587,7 @@ def set_runonce_registry(g, root, runonce_paths): p = h.root() for k in r'Microsoft\Windows\CurrentVersion\RunOnce'.split('\\'): p = h.node_get_child(p, k) - # 1 - hive_t_REG_SZ - unknown + # 1 - hive_t_REG_SZ - unknown h.node_set_values(p, [{'key': 'Spice2RunOnce', 't': 1, 'value': runonce_path.encode('utf-16le')} for runonce_path in runonce_paths]) h.commit(None) g.upload("/tmp/software", path) @@ -551,14 +665,32 @@ def set_startup_from_contents(g, image, filename, contents): g.write(dest, contents) return g +def update_certs(args): + if args.smartcard != 'sw' or args.certs != []: + return + if os.environ['SPICE_LAUNCHER_CERTS'] != '': + print "missing SPICE_LAUNCHER_CERTS" + sys.exit(1) + if os.environ['SPICE_LAUNCHER_DB']: + args.dbdir = os.environ['SPICE_LAUNCHER_DB'] + ################################################################################ processes = [] temp_files = [] -def start_process(args, kill=False, **kw): +class MyProcess(subprocess.Popen): + def spice_kill(self): + processes.remove(self) + return self.kill() + +def start_process(args, kill=False, show_output=True, **kw): global processes - p = subprocess.Popen(args, **kw) - p.spice_kill = kill + if show_output: + p = MyProcess(args, **kw) + else: + p = MyProcess(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + **kw) + p._spice_kill = kill processes.append(p) return p @@ -579,20 +711,37 @@ def cleanup(): os.system('reset -I') def if_processes_are_dead_then_exit(*args): - if all((p.poll() is not None or p.spice_kill) for p in processes): + if all((p.poll() is not None or p._spice_kill) for p in processes): cleanup() sys.exit(0) - #print '|'.join(('%d: %s, %s' % (p.pid, p.poll(), p.spice_kill)) for p in processes) + #print '|'.join(('%d: %s, %s' % (p.pid, p.poll(), p._spice_kill)) for p in processes) def ignore_ctrlc(): signal.signal(signal.SIGINT, if_processes_are_dead_then_exit) +def exit_on_hup(): + def on_exit(*args): + cleanup() + sys.exit(1) + old = signal.signal(signal.SIGHUP, on_exit) + print "SIGHUP old:", old + ################################################################################ clients = [] vnc_clients = [] +def default_get_display(): + while True: + yield None +def xephyr_get_display(): + i = 2 + while True: + yield i + i += 1 + def main(): args = parseargs() + update_certs(args) cmdline = build_cmdline(args) atexit.register(cleanup) if args.runonce: @@ -623,42 +772,47 @@ def main(): if args.showcmdline: return ignore_ctrlc() - test_process = start_process(args=cmdline.split()) - def default_get_display(): - while True: - yield None - def xephyr_get_display(): - i = 2 - while True: - yield i - i += 1 + exit_on_hup() + test_process = start_process(args=cmdline.split(), show_output=True) if args.xephyr: display = xephyr_get_display() else: display = default_get_display() - if args.clients > 0: - print "starting clients" - cur_display = display.next() - if cur_display: - run_xephyr(display=cur_display) - wait_for_port(args.spice_port) - for i in xrange(args.clients): - client_process = run_client( - host=args.spice_host, port=args.spice_port, - oldclient=args.oldclient, smartcard=args.smartcard, - certs=args.certs, dbdir=args.dbdir, - display=cur_display) - clients.append(client_process) - print "waiting for test_process" - if args.vnc_port and args.vnc_clients > 0: - if not vncviewer: - print "vncviewer not in path - not starting" - else: - print "starting vnc clients" - wait_for_port(args.vnc_port) - for i in xrange(args.vnc_clients): - client_process = run_vnc_client(host=args.spice_host, port=args.vnc_port) - vnc_clients.append(client_process) + if args.spicedump: + real_spice_port = args.spice_port + args.spice_port = proxy_port = real_spice_port + 1000 + # run a spicedump proxy in the middle + start_process(args=('%s %s -p -l %s -r localhost:%s%s' % + (python_exe, spicedump_exe, proxy_port, real_spice_port, + ' -f %s' % args.spicedump_filter if args.spicedump_filter else '' + )).split()) + if args.test_func: + test_func = globals()['test_'+args.test_func] + test_func(server=test_process, args=args) + else: + if args.clients > 0: + print "starting clients" + cur_display = display.next() + if cur_display: + run_xephyr(display=cur_display) + wait_for_port(args.spice_port) + for i in xrange(args.clients): + client_process = run_client( + host=args.spice_host, port=args.spice_port, + oldclient=args.oldclient, smartcard=args.smartcard, + certs=args.certs, dbdir=args.dbdir, + display=cur_display) + clients.append(client_process) + if args.vnc_port and args.vnc_clients > 0: + if not vncviewer: + print "vncviewer not in path - not starting" + else: + print "starting vnc clients" + wait_for_port(args.vnc_port) + for i in xrange(args.vnc_clients): + client_process = run_vnc_client(host=args.spice_host, port=args.vnc_port) + vnc_clients.append(client_process) + print "waiting for test_process" test_process.wait() if __name__ == '__main__': -- cgit v1.2.3