#!/usr/bin/python import signal import os import sys import subprocess import atexit import socket import datetime import re import time import shutil import itertools import tempfile try: import argparse except: print("missing argparse. try: easy_install argparse") sys.exit(1) try: import guestfs import hivex except: print("missing guestfs or hivex") guestfs = None def which(x): path = os.environ['PATH'] 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') vncviewer = which('vncviewer') bandwidthmon_exe = which('bandwidthmon') 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', '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', } spicy_exe = os.path.join(HOME, 'spice/upstream/bin/spicy') spice_gtk_exe = os.path.join(HOME, 'spice/upstream/bin/remote-viewer') spicec_exe = os.path.join(HOME, 'spice/upstream/bin/spicec') spicy_teuf_exe = os.path.join(HOME, 'src/spice_upstream/teuf-spice-gtk/gtk/spicy') roots = {'rhel6':'rhel6', 'upstream':'upstream'} root_default = 'upstream' defaults = dict( winxp=dict( root='upstream', args='--port 9001 --clients 1 --tablet --guestdebug 1 --qxldebug 1 --windbg-client 9000'.split(), ), wlk=dict( root='upstream', args='--port 9002 --clients 1 --tablet'.split(), ), win7=dict( root='upstream', args='--port 9003 --clients 1 --tablet --guestdebug 2 --qxldebug 1 --windbg-client 9000'.split() ), windevel=dict( root='rhel6', args='--port 9004 --clients 1 --windbg-server 9000'.split() ), f14ccid=dict( root='upstream', args='--port 9005 --clients 1 --tablet --guestdebug 4 --qxldebug 1 --smartcard'.split() ), win2008r2=dict( root='upstream', args='--port 9006 --clients 1 --tablet --revision 2'.split(), ), ) 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(compile(fd.read(), os.path.basename(config_path), 'exec')) 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( shutdown=dict(contents=""" shutdown /p """), suspend=dict(contents=r""" %s shutdown /p """ % (""" c:\suspend.exe rem ping -n 2 localhost """ * 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]), suspend_for_life=dict(contents=r""" set RESCHANGE="c:\reschange.exe" %RESCHANGE% -attach -mon 1024,768,32,0,0 -mon 800,600,32,1024,0 loop: c:\suspend.exe goto loop """, files=[res_change_exe, suspend_exe]), resolution1=dict(contents=r""" set RESCHANGE="c:\reschange.exe" %s shutdown /p """ % (""" %RESCHANGE% -mon 800,600,32,0,0 %RESCHANGE% -mon 600,800,32,0,0 %RESCHANGE% -mon 1024,768,32,0,0 %RESCHANGE% -mon 768,1024,32,0,0 %RESCHANGE% -mon 1024,768,32,0,0 """ * 60), files=[res_change_exe]), resolution2=dict(contents=r""" set RESCHANGE="c:\reschange.exe" %s shutdown /p """ % (""" %RESCHANGE% -attach -mon 1024,768,32,0,0 -mon 800,600,32,1024,0 %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=[res_change_exe]), # /Device All takes a ton of time. whql_pnpdtest=dict(contents=r""" c:\pnpdtest /Device All /concurrentio /stress /driverpath c:\qxl /noenddialog /except display test 1 /except display test 5 """, files=whql_pnpdtest_files), ) ################################################################################ """ 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, client='spicec', 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" ################################################################################ def set_title(x): sys.stdout.write("\033]0;%s\007" % str(x)) def file_in_use(x): print("not calling lsof -t %s" % x) return False return not subprocess.call(['lsof', '-t', x], stdout=subprocess.PIPE, stderr=subprocess.PIPE) def oldness(f): if not os.path.exists(f): return 'file doesn\'t exist' d = datetime.datetime.now() - datetime.datetime.fromtimestamp(os.stat(f).st_mtime) if d.days > 0: return '%d days' % d.days elif d.seconds > 3600: return '%d hours' % int(d.seconds / 3600) elif d.seconds > 60: return '%d minutes' % int(d.seconds / 60) return '%d seconds' % d.seconds ################################################################################ def get_lib(fname, rexp): l = os.popen('%s "%s"' % (lddpath, fname)).readlines() refs_raw=[x.split('=>') for x in l if '=>' in x] refs=[(r[0].strip().split()[0], r[1].strip().split()[0]) for r in refs_raw] ref = [r[1] for r in refs if re.match(rexp, r[0])] if len(ref) == 0: return 'library not found' return ref[0] def make_iso(iso_name, root, title): cmd = 'mkisofs -J -R -V %s_%s -o %s %s' % (title, datetime.datetime.now().strtime('%Y%m%d_%H%M'), iso_name, root) print(cmd) os.system(cmd) def version_tuple(symbol): return tuple(map(int, symbol.strip().rsplit('_', 1)[1].split('.'))) def get_versioned_library_version(lib, version_prefix): versions = [version_tuple(l) for l in os.popen('nm %s' % lib).readlines() if version_prefix in l] return max(versions) if len(versions) > 0 else None def diagnose_bios_bin(bios_dir): bios_bin = os.path.join(bios_dir, 'bios.bin') print("bios.bin age: %s (%s)" % (oldness(bios_bin), bios_dir)) def diagnose_vgabios_bin(bios_dir): vgabios = os.path.join(bios_dir, 'vgabios.bin') vgabios_qxl = os.path.join(bios_dir, 'vgabios-qxl.bin') print("vgabios.bin age: %s (%s)" % (oldness(vgabios), bios_dir)) print("vgabios-qxl.bin age: %s (%s)" % (oldness(vgabios_qxl), bios_dir)) def print_qemu_and_spice_versions(which, qemu_exec, clientexe, bios_dir): base = which + ' ' print(red % (base * (80 / len(base)))) print("QEMU age: %s (%s)" % (oldness(qemu_exec), qemu_exec)) diagnose_bios_bin(bios_dir) diagnose_vgabios_bin(bios_dir) for exe, lib_name, version_prefix in [ (qemu_exec, 'spice-server', 'SPICE_SERVER'), (clientexe, 'cacard', 'CACARD')]: lib = get_lib(exe, 'lib%s.*' % lib_name) print("%s age: %s (%s)" % (lib_name, oldness(lib), lib)) print("%s version: %r" % (lib_name, get_versioned_library_version(lib, version_prefix))) print(red % (base * (80 / len(base)))) def get_temp_name(prefix, postfix): global temp_files i = [0] def make_name(i): name = '%s_%03d%s' % (prefix, i[0], postfix) i[0] += 1 return name filename = make_name(i) while os.path.exists(filename): filename = make_name(i) return filename def create_qcow_with_backing_file(target_base, backing_file, erase_at_exit, use_temp): if not os.path.exists(backing_file): raise Exception("Non existant backing file") if backing_file[:1] != '/': backing_file = os.path.realpath(backing_file) if use_temp: filename = get_temp_name(target_base, '.qcow2') else: filename = target_base subprocess.call(['qemu-img', 'create', '-f', 'qcow2', '-o', 'backing_file=%s' % backing_file, filename]) if erase_at_exit: temp_files.append(filename) return filename def get_image(image, args): image_base_fullpath = image if not os.path.exists(image_base_fullpath): print("missing image %s" % image_base_fullpath) return None if not args.second and file_in_use(image_base_fullpath): print("image in use and --second not in arguments") return None if args.backing_file and args.migrator: print "cannot have a migration source using a backing file" sys.exit(-1) base_image_name = os.path.basename(image) if args.backing_file: image_fullpath = create_qcow_with_backing_file( target_base=os.path.join(os.path.dirname(base_image_name), base_image_name), backing_file=image_base_fullpath, erase_at_exit=args.erase_temp_backing_file, use_temp=True) elif args.migrator: image_fullpath = '/tmp/' + base_image_name + '.migrate' + os.path.splitext(image)[1] if not os.path.exists(image_fullpath): image_fullpath = create_qcow_with_backing_file( target_base=image_fullpath, backing_file=image_base_fullpath, erase_at_exit=False, use_temp=False) else: if not args.incoming: print "migrator, migration image exists already, but not incoming - delete original first please" print "image: %s" % image_fullpath sys.exit(-1) else: image_fullpath = image_base_fullpath print("image %s" % image_fullpath) return image_fullpath def get_images(args): return [get_image(image=im, args=args) for im in args.images] 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_and_env(args): parts = (['/usr/bin/libtool', '--mode=execute', 'cgdb', '--', '-ex', 'handle SIGUSR2 nostop noprint', '--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_spice_option(args): ret = ["disable-ticketing"] if args.spice_port: ret.append('port=%s' % args.spice_port) if args.tls_port: if not args.x509_dir: if not args.server_key: print("have tls-port but missing server-key") sys.exit(1) if not args.server_cert: print("have tls-port but missing server-cert") sys.exit(1) if not args.ca_cert: print("have tls-port but missing ca-cert") sys.exit(1) ret.append('tls-port=%s' % args.tls_port) if args.x509_dir: ret.extend(['x509-dir=%s' % args.x509_dir]) else: ret.extend([ 'x509-key-file=%s' % args.server_key, 'x509-cert-file=%s' % args.server_cert, 'x509-cacert-file=%s' % args.ca_cert, ]) if args.server_key_password: ret.append('x509-key-password=%s' % args.server_key_password) if args.sasl: ret.append('sasl') if args.wan_jpeg_compression: ret.append('jpeg-wan-compression=%s' % args.wan_jpeg_compression) if args.image_compression: ret.append('image-compression=%s' % args.image_compression) if args.streaming_video: ret.append('streaming-video=%s' % args.streaming_video) if args.secure_channels: ret.extend(['tls-channel=%s' % name for name in args.secure_channels.split(',')]) if args.egl_path: ret.append('egl-path=%s' % args.egl_path) return ['-spice', ','.join(ret)] class MissingFileException(Exception): pass def raise_unless_exists(msg, filename): if not os.path.exists(filename): raise MissingFileException(msg % filename) return filename class NoRootDirException(Exception): pass def get_root_dir(args): raise_unless_exists("root-dir %r is missing", args.root_dir) if not args.root: raise NoRootDirException() install_root = roots[args.root] root = os.path.join(args.root_dir, install_root) raise_unless_exists("missing %r", root) return root def get_bios_dir(args): """ if roots[root] is not there default to whatever is in --biosdir, which would be the fedora/RHEL x86_64 default. """ if args.bios_dir: return args.bios_dir if not args.root: print("--bios-dir not supplied nor --root") sys.exit(1) return os.path.join(get_root_dir(args), 'share/qemu') def get_qemu_exec(args): """ First check --qemu-exec if that is an executable file path assume it is qemu and take it if not check the root dict, if it is there check for a --root-dir, if not complain, if it is there then: add root_dir/roots[root]/lib to LD_LIBRARY_PATH (need this?) add root_dir/roots[root]/bin to PATH use qemu = root_dir/roots[root]/bin/qemu_exec_name qemu_exec_name is hardcoded to qemu-system-x86_64 """ if args.qemu_exec: raise_unless_exists("--qemu-exec doesn't exist: %r", args.qemu_exec) qemu_exec = args.qemu_exec else: root = get_root_dir(args) libdir = os.path.join(root, 'lib') os.environ['LD_LIBRARY_PATH'] = libdir bindir = os.path.join(root, 'bin') os.environ['PATH'] = bindir + ':' + os.environ['PATH'] qemu_exec = os.path.join(bindir, qemu_exec_name) return raise_unless_exists("missing qemu-exec %r", qemu_exec) def bridge_participants(brname): if not os.path.exists('/sys/class/net/%s' % brname): print("no such bridge %s" % brname) sys.exit(1) return os.listdir('/sys/class/net/%s/brif/' % brname) def interface_list(): with open('/proc/net/dev') as f: lines = f.readlines()[2:] return [x.split(':', 1)[0].strip() for x in lines] def fatal_error(msg): print(red % msg) sys.exit(1) def build_qemu_cmdline_and_env(args): """ Build qemu command line. Most of it is straight forward translation of the arguments from command line of spice_launcher to the qemu related parts. The start hinges on deciding where the qemu executable is. This script is designed to be used either (originally only) on development environment where qemu is installed into a non standard location (originally HOME/spice/), or on a standard system where qemu and the bios are at different ('/' common prefix) locations. So the algorithm is: '--qemu' is absolute path? take that, expect a --bios-dir too --qemu exists? require --dest-dir, and make sure --qemu is in qemus dict (set in the config file, .spice_launcher). """ environment = [] qemu_exec = 'no-qemu-exec-defined' bios_dir = 'no-bios-dir-defined' if args.test_prog: return build_test_cmdline_and_env(args) try: bios_dir = get_bios_dir(args) qemu_exec = get_qemu_exec(args) except (NoRootDirException, MissingFileException), e: if args.clients >= 0: print "no root dir - %s" % str(e) sys.exit(1) args.image_fullpaths = get_images(args) if args.record_cmd: print("recording cmd ring to %r" % args.record_cmd) os.environ['SPICE_WORKER_RECORD_FILENAME'] = args.record_cmd if args.ld_library_path: # This removes any existing one, so you have to add it yourself. environment.append('LD_LIBRARY_PATH', ':'.join(args.ld_library_path)) if len(args.ld_preload) > 0: for lib in args.ld_preload: if not os.path.exists(lib): print("no such file or access problem: %r" % lib) sys.exit(1) environment.append(('LD_PRELOAD', ':'.join(args.ld_preload))) os.system('echo $LD_LIBRARY_PATH') cmdline = [qemu_exec, "-chardev", "stdio,id=muxstdio,mux=on", "-mon", "chardev=muxstdio,mode=readline", "-enable-kvm", "-L", bios_dir, "-m", str(args.memory)] for image in args.image_fullpaths: cmdline.extend(["-drive", "file=%s,cache=%s,if=%s,readonly=%s" % (image, args.cache, args.block_if, args.read_only)]) if args.vga != 'virtio': cmdline += ["-vga", args.vga] else: cmdline += ['-device', 'virtio-vga'] if args.cpu != 'none': cmdline += ["-cpu", str(args.cpu)] if args.spice: cmdline += build_spice_option(args) if args.qmp_port or args.qmp_path: # TODO - make it both ipv6 and ipv4? didn't test, but if no ipv4 is given # the socket may only be ipv6, which complicates a lot of python code needlessly. if args.qmp_path: addr = 'path=%s' % args.qmp_path args.qmp_port = 0 else: addr = 'host=127.0.0.1,port=%s' % args.qmp_port cmdline.extend(["-chardev", "socket,id=qmpmon,%s,server,nowait,ipv4" % addr, "-mon", "chardev=qmpmon,mode=control"]) if args.snapshot and args.migrator: print "migrator conflicts with snapshot, snapshot ignored" args.snapshot = False if args.snapshot: cmdline.append("-snapshot") if args.spice_critical_no_abort: environment.append(('SPICE_ABORT_LEVEL', '-1')) if args.no_shutdown: cmdline.append('-no-shutdown') if args.cpus > 1: cmdline.extend(['-smp', str(args.cpus)]) if args.guestdebug > 0: cmdline.extend(['-global', 'qxl-vga.guestdebug=%d' % args.guestdebug]) if args.qxldebug > 0: cmdline.extend(['-global', 'qxl-vga.debug=%d' % args.qxldebug]) if args.cmdlog > 0: cmdline.extend(['-global', 'qxl-vga.cmdlog=%d' % args.cmdlog]) if args.qxl_ram: cmdline.extend(['-global', 'qxl-vga.ram_size=%s' % args.qxl_ram]) if args.qxl_vram != None: cmdline.extend(['-global', 'qxl-vga.vram_size=%s' % args.qxl_vram]) if args.qxl_fb: def int_to_megabytes(x): if x < 1024: return x return x / 1024 / 1024 cmdline.extend(['-global', 'qxl-vga.vgamem_mb=%s' % int(int_to_megabytes(args.qxl_fb))]) if args.heads > 1: cmdline.extend(['-global', 'qxl-vga.num_heads=%s' % args.heads]) if args.qxl > 1: device_params = [ 'qxl', 'guestdebug=%d' % args.guestdebug, 'cmdlog=%d' % args.cmdlog, 'debug=%d' % args.qxldebug, 'multifunction=%s' % ('on' if args.multifunction else 'off')] if args.qxl_ram: device_params.append('ram_size=%d' % args.qxl_ram) if args.qxl_vram: device_params.append('vram_size=%s' % args.qxl_vram) for i in xrange(1, args.qxl): cmdline.extend(['-device', ','.join(device_params) + ',id=qxl%d' % i]) if args.sound == 'intel-hda': # -soundhw hda not in RHEL? #cmdline.extend(['-soundhw', 'hda']) cmdline.extend(['-device', 'intel-hda,id=sound0', '-device', 'hda-duplex,id=sound0-codec0,bus=sound0.0,cad=0']) if args.sound: environment.append(('QEMU_AUDIO_DRV', 'spice')) if args.revision: cmdline.extend(['-global', 'qxl-vga.revision=%d' % args.revision]) if args.tablet or args.smartcard != 'off' or args.usbredir: #cmdline.extend(['-usb']) if args.tablet: print("enabling usb for tablet") elif args.smartcard: print("enabling usb for smartcard") else: print("enabling usb for usbredir") cmdline.extend(['-readconfig', '/home/alon/src/spice_upstream/qemu/docs/ich9-ehci-uhci.cfg']) if args.usbredir: cmdline.extend(['-chardev', 'spicevmc,name=usbredir,id=usbredirchardev1', '-device', 'usb-redir,chardev=usbredirchardev1,id=usbredirdev1,bus=ehci.0,debug=3']) if args.tablet: cmdline.extend(['-device', 'usb-tablet']) #if args.usbredir: # cmdlilne.extend([]) need_virtio_serial = args.vdagent or args.kdvirtserial or args.virtconsole or len(args.webdav) > 0 if need_virtio_serial: cmdline.extend(['-device', 'virtio-serial-pci,id=virtio-serial0']) # virtio-serial,multifunction=on if args.vdagent: cmdline.extend(['-chardev', 'spicevmc,name=vdagent,id=vdagent', '-device', 'virtserialport,chardev=vdagent,bus=virtio-serial0.0,name=com.redhat.spice.0']) if args.virtconsole: # instead of making this unix specific the user has # to input stuff # TODO: validate # TODO: switch to a virsh wrapper. (vdsm??) cmdline.extend(['-chardev', '%s,id=virtconsole' % args.virtconsole, '-device', 'virtconsole,chardev=virtconsole,name=org.alon.console']) if False and args.kdvirtserial: cmdline.extend(['-chardev', 'socket,host=127.0.0.1,port=12000,id=kd', '-device', 'virtserialport,chardev=kd,name=qemu.kd']) if args.ehci or args.usb_redir: cmdline.extend(['-readconfig', '/home/alon/src/spice_rhel6/qemu-kvm-rhel6/docs/ich9-ehci-uhci.cfg']) if args.usb_redir: cmdline.extend(['-chardev', 'spicevmc,name=usbredir,id=usbredirchardev', '-device', 'usb-redir,chardev=usbredirchardev,id=usbredirdev']) if args.kd or args.kdlisten: # TODO - connect to a kd running on host (wine) or on vm2 (like # --windbg-server and --windbg-client) socket_param = 'socket,id=kd,host=127.0.0.1,port=%s' % ( args.kd or args.kdlisten) if args.kdlisten: socket_param += ',server' cmdline.extend(['-chardev', socket_param, '-device', 'kd,chardev=kd%s' % ( ','+args.kdflags if args.kdflags else '')]) if args.rtc == 'slew': cmdline.extend(['-rtc', 'base=utc,driftfix=slew']) # danpb suggested this and the above rtc are the recommended by KVM/RHEV # team. - but it only works with qemu-kvm right now. #cmdline.extend(['-no-kvm-pit-reinjection']) # user networking if args.smb: cmdline.extend(['-net', 'user,smb=%s,smbserver=10.0.2.2' % args.smb]) if args.network == 'user': cmdline.extend('-netdev user,id=hostnet0'.split()) elif args.network == 'bridge': sudo_prefix = '' if os.getuid() != 0: print("using sudo to create device") sudo_prefix = 'sudo ' if args.tun in interface_list(): print("tun device %s already exists, deleting" % args.tun) if os.system('%stunctl -u %s -g %s -b -t %s' % (sudo_prefix, args.uid, args.gid, args.tun)): fatal_error("failed to create tun device") if os.system('%sifconfig %s 0.0.0.0 promisc up' % (sudo_prefix, args.tun)): fatal_error("ifconfig up failed") if args.tun in bridge_participants(args.bridge): print("someone is already using tun in this bridge, hopefully it is us") else: if os.system('%sbrctl addif %s %s' % (sudo_prefix, args.bridge, args.tun)): print("error calling brctl") sys.exit(1) cmdline.extend(['-netdev', 'tap,id=hostnet0,ifname=%s,script=no,downscript=no' % args.tun]) # TODO: --network-device == 'none' conflicts with bridge, but we don't check for that. if args.network_device == 'none': cmdline.extend(['-net', 'none']) else: cmdline.extend(["-device", "%s,netdev=hostnet0,id=net0%s" % (args.network_device, (',mac=%s' % args.mac) if args.mac else '')]) #cmdline.extend(["-net", "nic,model=virtio,macaddr=%s" % args.mac]) # windbg (TODO - better then serial) if args.windbg_server: cmdline.extend(('-serial tcp::%d,server,nowait' % args.windbg_server).split()) if args.windbg_client: if port_is_available(args.windbg_client): cmdline.extend(('-serial tcp:127.0.0.1:%d' % args.windbg_client).split()) else: print("nothing listening on %d, do you want to start something there? (p.s. maybe systemd service?)" % args.windbg_client) if args.serial: cmdline.extend(['-chardev', args.serial+',id=serial', '-serial', 'chardev:serial']) # smartcard if args.smartcard != 'off': ccid_debug = 1 passthru_debug = 1 cmdline.extend(['-chardev', 'spicevmc,id=smartcard,debug=3,name=smartcard', "-device", "usb-ccid,debug=%s,id=ccid" % ccid_debug, "-device", "ccid-card-passthru,debug=%s,chardev=smartcard" % (passthru_debug), ]) # bios debugging if args.debugbios: cmdline.extend('-device isa-debugcon,iobase=0x402,chardev=muxstdio'.split()) # incoming migration if args.incoming: cmdline.extend(('-incoming tcp:127.0.0.1:%d' % args.incoming).split()) if args.freeze: cmdline.extend(['-S']) # vnc server if args.vnc_port: if (args.vnc_port < 5900): print("vnc_port must be >= 5900") cmdline.extend(('-vnc %s:%s' % (args.spice_host, args.vnc_port - 5900)).split()) # cdrom if args.winqxl_cdrom: if args.cdrom: print("winqxl-cdrom and cdrom are mutually exclusive") sys.exit(1) if not os.path.exists(qxl_win_root): print("you need to edit qxl_win_root path in %s for this option" % ( sys.argv[0])) sys.exit(1) make_iso('/tmp/winqxl.iso', qxl_win_root, title='qxl') args.cdrom = '/tmp/winqxl.iso' if args.cdrom: if not os.path.exists(args.cdrom): print("missing cdrom image %r" % args.cdrom) sys.exit(1) cmdline.extend(['-cdrom', args.cdrom]) # bios if args.bios: if not os.path.exists(args.bios): print("supplied bios %r does not exist" % args.bios) sys.exit(1) cmdline.extend(['-bios', args.bios]) # abort control if args.abort_level: environment.append(('SPICE_ABORT_LEVEL', args.abort_level)) # File system sharing via fsdev if len(args.share) > 0: for i, share in enumerate(args.share): share_id = 'share-%d' % i # TODO - -fsdev + -device tag = share.rsplit('/', 2)[-1] cmdline.extend(['-virtfs', ','.join(['local', 'path=%s' % share, 'security_model=none', 'mount_tag=%s' % tag])]) print("sharing %s as %s" % (share, tag)) # File system sharing via webdav if len(args.webdav) > 0: for i, share in enumerate(args.webdav): if i > 0: print("webdav: not supporting more than one") break print("webdav: ignoring share name %d") share_id = 'webdav-%d' % i cmdline.extend(['-device', 'virtserialport,bus=virtio-serial0.0,chardev=webdav-char-%(i)d,id=webdav-ser-%(i)d,name=org.spice-space.webdav.%(i)d' % locals(), '-chardev', 'spiceport,name=org.spice-space.webdav.%(i)d,id=webdav-char-%(i)d' % locals()]) # Qemu gdb support if args.gdb is not None: if args.gdb == -1: args.gdb = 5555 print("TODO: find vacant port for gdb server") cmdline.extend(['-gdb', 'tcp::%s' % args.gdb]) print("connect to gdb on %s. TODO - open terminal automatically" % args.gdb) ###### print_qemu_and_spice_versions(qemu_exec=qemu_exec, clientexe=get_client_exe(args.client), which=qemu_exec, bios_dir=bios_dir) # wrap in various tools if args.qemu_gdb: cgdb = which('cgdb') if not cgdb: gdb = which('gdb') if gdb: print("using gdb instead of cgdb") cgdb = gdb else: print("missing both cgdb and gdb") sys.exit(1) cmds_args = [] if args.gdbcmds: cmds_file_name = '/tmp/gdbcmds' with open(cmds_file_name, 'w+') as fd: fd.writelines([(x + '\n') for x in args.gdbcmds]) cmds_args = ['-x', cmds_file_name] cmdline = [cgdb, '--'] + cmds_args+ ['--args'] + cmdline if args.memcheck: print("wrapping with valgrind memcheck") cmdline = ['valgrind', '--error-limit=no', '--leak-check=full', '--suppressions=%s/bin/kvm.supp' % HOME, '--log-file=/tmp/kvm_%s.valgrind' % time.strftime('%Y%m%d_%H%M%S',time.gmtime())] + cmdline if args.get_virt_memset: environment.append(('SPICE_GET_VIRT_MEMSET', '1')) if args.time: print("wrapping in time") cmdline = ['/usr/bin/time', '--verbose'] + cmdline # TODO - gate this environment.append(('SPICE_DEBUG_ALLOW_MC', '1')) return cmdline, environment def run_xephyr(display, size=(1024,768+40), show_output=False, debug=False): if not display: return proc = start_process(('Xephyr -screen %dx%d :%d.0' % (size[0], size[1], display)).split()) wait_for_port(6000 + display) if debug: os.kill(proc.pid, signal.SIGUSR1) def rpdb2_break(): import rpbd2 rpdb2.rpdb2.start_embedded_debugger('a') def get_client_exe(client): return {'spicec': spicec_exe, 'spicy': spicy_exe, 'spice-gtk': spice_gtk_exe}.get(client, client) def run_with_display(f): def wrapper(*args, **kw): old_display = None if 'display' in kw and kw['display']: old_display = os.environ['DISPLAY'] os.environ['DISPLAY'] = ':%d.0' % kw['display'] ret = f(*args, **kw) if old_display: os.environ['DISPLAY'] = old_display return ret wrapper.__name__ = 'wrapped_' + f.__name__ wrapper.__doc__ = f.__doc__ return wrapper def spice_uri(host, port, tls_port): s = ['spice://%s?' % host] if port > 0: s.append('port=%d' % port) if tls_port > 0: s.append('tls-port=%d' % tls_port) return ''.join(s) @run_with_display def run_client(conf, host, port, client, smartcard, certs, dbdir, tls_port=None, ca_file=None, host_subject=None, display=None, auto_conf=False, client_debug=False): args = [get_client_exe(client)] if client == 'spice-gtk': args.append(spice_uri(host, port, tls_port)) else: args.extend(['-h', host]) if port: args.extend(['-p', str(port)]) if smartcard == 'sw': if client == 'spicec': args.extend(['--smartcard', '--smartcard-cert', ','.join(certs), '--smartcard-db', dbdir]) elif client == 'spicy': args.extend(['--spice-smartcard', '--spice-smartcard-certificates=%s' % (','.join(certs)), '--spice-smartcard-db=%s' % dbdir]) else: print("Error: client %s not supported for smartcard yet, please add support" % client) sys.exit(1) if auto_conf and client != 'spice-gtk': args.append('--full-screen=auto-conf') if tls_port: if client != 'spice-gtk': args.extend(['-s', str(tls_port)]) args.extend(['--spice-host-subject=%s' % host_subject, '--spice-ca-file=%s' % ca_file]) if dbdir and not os.path.exists(dbdir): print("error - dbdir supplied does not exist") sys.exit(1) environment = dict(os.environ) if client_debug: environment['SPICE_DEBUG'] = '1' environment['G_DEBUG'] = 'all' if conf.ld_library_path: environment['LD_LIBRARY_PATH'] = ':'.join(conf.ld_library_path) if conf.ld_preload: environment['LD_PRELOAD'] = ':'.join(conf.ld_preload) return start_process(args=args, kill=True, env=environment) @run_with_display def run_vnc_client(host, port, display=None): return start_process(args=('%s %s::%s' % (vncviewer, host, port)).split(), kill=True) def port_is_available(port): """ do NOT open a socket to check connection - blows up with socat/netcat """ # looking at netcat source -z (zflag) can't figure a different way. return True def port_is_available_evil_opens_port(port): s = socket.socket(socket.AF_INET) ret = False try: s.connect(('127.0.0.1', port)) s.close() ret = True except: pass return ret def wait_for_port(port): if not port: return print "waiting for port %d to be available" % port # TODO: do this without actually opening the port - maybe just look at /proc/qemu_process_id/fd? s = socket.socket(socket.AF_INET) while True: try: s.connect(('127.0.0.1', port)) s.close() break except: time.sleep(1) pass ################################################################################ #default_qmp_port = 20000 # TODO - port allocation (=PortPromisedRange(20000, 100)) default_spice_port = 10000 def size(v): if len(v) == 0: raise Exception('bad size value') c = v[-1:] if c in 'mMgGkK': val = int(v[:-1]) if c == 'M': val *= 1024*1024 elif c == ',': val *= 1000000 elif c == 'k': val *= 1000 elif c == 'K': val *= 1024 elif c == 'G': val *= 1024*1024*1024 elif c == 'g': val *= 1000*1000*1000 else: val = int(v) return val def parseargs(): """ Parse command line. Some of the parameters are pre populated: if sys.argv[0] is a symlink of the form: - lookup name in images dictionary (set in config) . - lookup name in images and root in qemus. """ image, root = None, root_default name = os.path.basename(sys.argv[0]) default_args = [] 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 'root' in defaults[base_part]: root = defaults[base_part]['root'] if len(parts) == 2: image, root = 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.') network_group = parser.add_argument_group('network') devices_group = parser.add_argument_group('devices') misc_group = parser.add_argument_group('misc') disk_group = parser.add_argument_group('disk') tests_group = parser.add_argument_group('tests') client_group = parser.add_argument_group('clients') qemu_group = parser.add_argument_group('qemu') threed_group = parser.add_argument_group('3d') threed_group.add_argument('--egl-path') qemu_group.add_argument('--qemu-exec') qemu_group.add_argument('--ld-preload', action='append', default=[]) qemu_group.add_argument('--ld-library-path', action='append', default=[]) qemu_group.add_argument('--root', default=root, choices=list(roots.keys()) + [None]) qemu_group.add_argument('--root-dir', default=os.path.join(HOME, 'spice')) qemu_group.add_argument('--bios-dir', default=None) qemu_group.add_argument('--bios', default=None) disk_group.add_argument('--image', dest='images', default=[image] if image else [], action='append') disk_group.add_argument('--block-if', default='virtio', choices=['ahci', 'ide', 'virtio']) disk_group.add_argument('--read-only', default='off', action='store_const', const='on') disk_group.add_argument('--snapshot', action='store_true', default=True) disk_group.add_argument('--no-snapshot', dest='snapshot', action='store_false') disk_group.add_argument('--cdrom', default=None) disk_group.add_argument('--winqxl-cdrom', default=False, action='store_true') disk_group.add_argument('--second', default=False, action='store_true') disk_group.add_argument('--keep-temp-backing-file', dest='erase_temp_backing_file', default=True, action='store_false') disk_group.add_argument('--copy-in', action='append') disk_group.add_argument('--no-backing-file', default=True, action='store_false', dest='backing_file', help='use the image as a backing file for the actual image') disk_group.add_argument('--startup') disk_group.add_argument('--cache', choices=['unsafe','off','writeback','writethrough'], default='unsafe') # second best performance is writeback disk_group.add_argument('--runonce') disk_group.add_argument('--clear-runonce', default=False, action='store_true') disk_group.add_argument('--share', help='path that will be provided to guest as 9p (-fsdev) filesystem', action='append', default=[]) disk_group.add_argument('--webdav', help='path that will be provided to guest as webdav', action='append', default=[]) client_group.add_argument('--no-spice', default=True, action='store_false', dest='spice') client_group.add_argument('--client', default='spice-gtk') client_group.add_argument('--clients', default=0, type=int) client_group.add_argument('--client-debug', default=False, action='store_true') client_group.add_argument('--port', dest='spice_port', type=int, default=default_spice_port) client_group.add_argument('--host', dest='spice_host', default='127.0.0.1') client_group.add_argument('--tls-port', type=int) client_group.add_argument('--x509-dir') client_group.add_argument('--ca-cert') client_group.add_argument('--server-cert') client_group.add_argument('--server-key') client_group.add_argument('--server-key-password') client_group.add_argument('--host-subject') client_group.add_argument('--sasl', action='store_true', default=False) client_group.add_argument('--secure-channels') # TODO -verify, a,b,c client_group.add_argument('--vncport', dest='vnc_port', type=int) client_group.add_argument('--vncclients', dest='vnc_clients', type=int, default=1) devices_group.add_argument('--vga', choices=['qxl', 'cirrus', 'virtio'], default='qxl') devices_group.add_argument('--qxl', type=int, default=1) devices_group.add_argument('--heads', type=int, default=1) devices_group.add_argument('--qxl-ram', type=size) devices_group.add_argument('--qxl-vram', type=size) devices_group.add_argument('--qxl-fb', type=size) devices_group.add_argument('--wan-jpeg-compression', choices=['always', 'auto', 'never']) devices_group.add_argument('--image-compression', choices=['off', 'auto', 'on', 'quic', 'auto_glz', 'auto_lz']) devices_group.add_argument('--streaming-video', choices=['off', 'all', 'filter']) devices_group.add_argument('--tablet', action='store_true', default=False) devices_group.add_argument('--vdagent', dest='vdagent', action='store_true', default=False) devices_group.add_argument('--virtconsole') devices_group.add_argument('--serial') devices_group.add_argument('--no-vdagent', dest='vdagent', action='store_false') devices_group.add_argument('--kdvirtserial', action='store_true', default=False) devices_group.add_argument('--kd', type=int, help='add virtual kernel debugger') devices_group.add_argument('--kdlisten', type=int, help='add virtual kernel debugger in listen') devices_group.add_argument('--kdflags') devices_group.add_argument('--no-tablet', action='store_false') devices_group.add_argument('--guestdebug', type=int, default=0) devices_group.add_argument('--qxldebug', type=int, default=0) devices_group.add_argument('--cmdlog', type=int, default=0) devices_group.add_argument('--memory', type=int, default=512) devices_group.add_argument('--cpus', type=int, default=2) devices_group.add_argument('--smartcard', choices=['off','hw','sw'], default='off') devices_group.add_argument('--smartcard-dbdir', dest='dbdir') devices_group.add_argument('--smartcard-certs', dest='certs', action='append') devices_group.add_argument('--usbredir', action='store_true', default=False) devices_group.add_argument('--debugbios', action='store_true', default=False) devices_group.add_argument('--network-device', choices=['virtio-net-pci', 'rtl8139','none'], default='virtio-net-pci') devices_group.add_argument('--sound', choices=['intel-hda', 'ac97', None], default=None) devices_group.add_argument('--revision', default=None, type=int) devices_group.add_argument('--cpu', choices=['host', 'none'], default='host', help='qemu cpu flag') devices_group.add_argument('--multifunction', action='store_true', default=False, help='libvirt uses multifunction exclusively') devices_group.add_argument('--ehci', action='store_true', default=False) devices_group.add_argument('--usb-redir', action='store_true', default=False) devices_group.add_argument('--rtc', choices=['slew']) network_group.add_argument('--smb', default=None) network_group.add_argument('--network', choices=['user', 'bridge', 'none', None], default='user') network_group.add_argument('--mac') network_group.add_argument('--bridge', default='virbr0') network_group.add_argument('--tun', default='tap0') misc_group.add_argument('--spice-critical-no-abort', action='store_true', default=False) misc_group.add_argument('--shutdown', dest='no_shutdown', action='store_false', default=True) misc_group.add_argument('--time', action='store_true', default=False) misc_group.add_argument('--qemu-gdb', default=False, action='store_true', help="debug qemu itself") misc_group.add_argument('--gdbcmd', dest='gdbcmds', type=str, action='append') misc_group.add_argument('--gdb', default=None, help='qemu gdbserver (use --qemu-gdb for debugging qemu)') misc_group.add_argument('--windbg-server', type=int, default=None) misc_group.add_argument('--windbg-client', type=int, default=None) misc_group.add_argument('--incoming', type=int, default=None) misc_group.add_argument('--migrator', action='store_true', default=False) misc_group.add_argument('--title', default=None) misc_group.add_argument('--freeze', default=False, action='store_true') misc_group.add_argument('--uid', type=int, default= int(os.environ.get('SUDO_UID', os.getuid()))) misc_group.add_argument('--gid', type=int, default= int(os.environ.get('SUDO_GID', os.getgid()))) misc_group.add_argument('--print-commandline-only', default=False, action='store_true') misc_group.add_argument('--memcheck', default=False, action='store_true') misc_group.add_argument('--get-virt-memset', default=False, action='store_true') tests_group.add_argument('--wintest', choices=win_tests.keys()) tests_group.add_argument('--record-cmd', help='record qxl command ring') tests_group.add_argument('--xephyr', action='store_true', default=False) tests_group.add_argument('--xdm', '--xephyr-dbg-mode', dest='xephyr', action='store_const', const='debug') tests_group.add_argument('--spicedump', default=False, action='store_true') tests_group.add_argument('--spicedump-filter') tests_group.add_argument('--bandwidthmon', action='store_true') # convenient to run tests via the same framework, though this is not using # any of of the qemu flags, just the client running parts. tests_group.add_argument('--test-prog', dest='test_prog', choices=test_progs.keys()) tests_group.add_argument('--test-func', dest='test_func', choices=tests) tests_group.add_argument('--replay-file', dest='replay_file') tests_group.add_argument('--print-events', action='store_true', default=False) tests_group.add_argument('--reset-on-shutdown', action='store_true', default=False) tests_group.add_argument('--qmp-port', default=None, type=int) tests_group.add_argument('--qmp-path', default=None) # log levels are enum values for SpiceLogLevel from spice-common/common/log.h tests_group.add_argument('--abort-level', choices=['-1', 0, 1, 2, 3, 4], default=None) # extract extra parameters, those after "--", from default_args # the same is done for sys.argv[1:] by parse_known_args default_rest = [] if '--' in default_args: i = default_args.index('--') default_rest = default_args[i + 1:] default_args = default_args[:i] else: default_rest = [] args, rest = parser.parse_known_args(default_args + sys.argv[1:]) rest = default_rest + rest args.images = [images[im] if im in images.keys() else im for im in args.images] if len(parts) == 1 and not args.test_prog and len(args.images) == 0: if image not in defaults: print("symlink %r not in %r" % (image, defaults.keys())) sys.exit(1) if not args.root: args.root = defaults[image]['root'] elif len(args.images) == 0: args.images = ['/dev/null'] # tls arguments if args.x509_dir: args.ca_cert = os.path.join(args.x509_dir, 'ca-cert.pem') # qmp arguments validation if not args.qmp_port and any([args.reset_on_shutdown, args.print_events]): print("error: cannot disable qmp-port and use --reset-on-shutdown or --print-events") sys.exit(1) return args, rest def set_runonce_registry(g, root, runonce_paths): print("setting RunOnce to %r" % runonce_paths) systemroot = g.inspect_get_windows_systemroot(root) path = "%s/system32/config/software" % systemroot # equivalent to HKLM\\Software path = g.case_sensitive_path(path) g.download (path, "/tmp/software") h = hivex.Hivex("/tmp/software", write=True) 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 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) def get_guestfs(image): if not guestfs: print("missing guestfs") sys.exit(1) g = guestfs.GuestFS() g.set_trace(1) g.add_drive_opts(image) g.launch() roots = g.inspect_os () root = roots[0] g.mount_options("", root, "/") return g def set_runonce(image, program): g = get_guestfs(image) if program is None: values = [] else: runonce_base = 'runonce.bat' if os.path.exists(program): runonce_base = os.path.basename(program) g.upload(filename=program, remotefilename='/' + runonce_base) else: g.write('/' + runonce_base, program) values = ['c:\\' + runonce_base] set_runonce_registry(g, root, values) g.sync() g.umount_all() del g def copy_file_in(g, image, path, dest): if g == None: g = get_guestfs(image) if not os.path.exists(path): print("%s does not exist" % path) sys.exit(1) print("copying %r contents to c:\\" % (path)) g.upload(path, os.path.join(dest, os.path.basename(path))) def copy_dir_in(g, image, path): if not os.path.isdir(path): copy_file_in(g, image, path, '/') return g if g == None: g = get_guestfs(image) print("copying %r contents to c:\\" % (path)) temp_name = shutil.make_archive('/tmp/spice2.copy_in', 'tar', path + '/') dest = '/' + os.path.basename(path) if not g.is_dir(dest): print("mkdir %r in image" % dest) g.mkdir(dest) g.tar_in(tarfile=temp_name, directory=dest) os.unlink(temp_name) return g startup = '/ProgramData/Microsoft/Windows/Start Menu/Programs/Startup' def set_startup_from_file(g, image, path): if not os.path.exists(path): print("startup parameter is either a filename or a path") print("copying file %s to Startup on %s" % (path, image)) filename = os.path.basename(path) dest = os.path.join(startup, filename) if g == None: g = get_guestfs(image) g.upload(filename=path, remotefilename=dest) return g def set_startup_from_contents(g, image, filename, contents): dest = os.path.join(startup, filename) if g == None: g = get_guestfs(image) 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 = [] temp_dirs = [] 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 print(repr(args)) 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 def temp_dir(): global temp_dirs d = tempfile.mkdtemp() temp_dirs.append(d) return d def cleanup_file(filename): global temp_files temp_files.append(filename) return filename def cleanup(): for p in processes: print("killing pid %s" % p.pid) try: p.send_signal(signal.SIGUSR1) p.kill() p.kill() # qemu started with --no-shutdown, needs two signals p.wait() except OSError: pass for m in temp_files: if not os.path.exists(m): continue print("removing %r" % m) os.unlink(m) for d in temp_dirs: if not os.path.exists(d): continue print("removing directory %r" % d) shutil.rmtree(d) 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): cleanup() sys.exit(0) #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): print "got signal, args = %s" % repr(args) cleanup() sys.exit(1) old = signal.signal(signal.SIGHUP, on_exit) print("SIGHUP old:", old) def x_available(display): return not (os.popen("xrandr -d %s 2>&1" % display).read().strip() == "Can't open display") def launch_clients(args, display, clients, num_clients): print("starting clients") cur_display = display.next() run_xephyr(display=cur_display, debug=(args.xephyr=='debug')) wait_for_port(args.spice_port) wait_for_port(args.tls_port) for i in xrange(num_clients): client_process = run_client( conf=args, host=args.spice_host, port=args.spice_port, client=args.client, smartcard=args.smartcard, certs=args.certs, dbdir=args.dbdir, display=cur_display, tls_port=args.tls_port, ca_file=args.ca_cert, host_subject=args.host_subject, client_debug=args.client_debug) clients.append(client_process) ################################################################################ clients = [] vnc_clients = [] def default_get_display(): while True: yield None def xephyr_get_display(): i = 2 while True: yield i i += 1 def block_until_file_exists(filename): while not os.path.exists(filename): time.sleep(1) def main(): import qmp_helpers args, rest = parseargs() # remove first instance of '--' from rest if '--' in rest: del rest[rest.index('--')] update_certs(args) args_cmdline, env = build_qemu_cmdline_and_env(args) cmdline = args_cmdline + rest if args.print_commandline_only: print(repr(cmdline)) print(' '.join(cmdline)) def iterswitches(cmdline): ret = [] for e in cmdline: if e[0] == '-': yield ' '.join(ret) ret = [] ret.append(e) if len(ret): yield ' '.join(ret) maxlen = max(len(e) for e in iterswitches(cmdline)) + 9 print('\\\n'.join((((' ' if i > 0 else '') + e).ljust(maxlen) for i, e in enumerate(iterswitches(cmdline))))) return for k, v in env: print("setting environment variable %s = %r" % (k, v)) os.environ[k] = v atexit.register(cleanup) # NOTE: implicit assumption that first image is the drive we want. This hasn't been used in a while. first_image = args.image_fullpaths[0] if args.runonce: set_runonce(first_image, args.runonce) elif args.clear_runonce: set_runonce(first_image, None) g = None if args.startup: g = set_startup_from_file(g, first_image, args.startup) if args.wintest: g = set_startup_from_contents(g, image=first_image, filename='startup.bat', contents=win_tests[args.wintest]['contents']) # NOTE: can just use autoplay and a suitable cdrom iso for these, faster then guestfs by far. for filename in win_tests[args.wintest].get('files', []): g = copy_file_in(g, first_image, filename, '/') if args.copy_in: for p in args.copy_in: g = copy_dir_in(g, first_image, p) if g: g.sync() g.umount_all() del g set_title("%s%s" % ( 'I ' if args.incoming else '', args.title if args.title else os.path.basename(sys.argv[0]) )) ignore_ctrlc() exit_on_hup() # At this point drop privileges before executing any process. Networking and # anything else requiring super-user privileges should have already been done. if os.getuid() == 0: print("dropping super user privileges") os.setuid(args.uid) os.setgid(args.gid) if args.xephyr: display = xephyr_get_display() else: display = default_get_display() if args.clients >= 0: test_process = start_process(args=cmdline, show_output=True) else: # hack to just launch a client without yet another command line argument. launch_clients(args, display, clients, -args.clients) sys.exit(0) # some qmp using apps require some forking qmp_handlers = [] if args.qmp_port: wait_for_port(args.qmp_port) qmp_address = ('127.0.0.1', args.qmp_port) if args.print_events: qmp_handlers.append(qmp_helpers.print_events()) if args.reset_on_shutdown: qmp_handlers.append(qmp_helpers.reset_on_shutdown()) if len(qmp_handlers) > 0: qmp_helpers.fork_and_handle(qmp_address, qmp_handlers) if args.spicedump: print("running 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 127.0.0.1:%s%s' % (python_exe, spicedump_exe, proxy_port, real_spice_port, ' -f %s' % args.spicedump_filter if args.spicedump_filter else '' )).split()) if args.bandwidthmon: if not bandwidthmon_exe or not os.path.exists(bandwidthmon_exe): print("missing bandwidthmon_exe (%r)" % bandwidthmon_exe) sys.exit(1) print("running bandwidthmon") real_spice_port = args.spice_port args.spice_port = proxy_port = real_spice_port + 1000 proxy_args = [bandwidthmon_exe, '--listen-port', str(proxy_port), '--remote-port', str(real_spice_port), '--remote-host', '127.0.0.1'] # TODO - go over the ports once, to avoid errors later if args.vnc_port: real_vnc_port = args.vnc_port args.vnc_port = proxy_port = real_vnc_port + 1000 proxy_args.extend(['--listen-port', str(proxy_port), '--remote-port', str(real_vnc_port)]) start_process(args=proxy_args) if args.test_func: test_func = globals()['test_'+args.test_func] test_func(server=test_process, args=args) else: if args.clients > 0: launch_clients(args, display, clients, args.clients) 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) cur_display = display.next() for i in xrange(args.vnc_clients): run_xephyr(display=cur_display, debug=(args.xephyr=='debug')) client_process = run_vnc_client(host=args.spice_host, port=args.vnc_port, display=cur_display) vnc_clients.append(client_process) print("waiting for test_process") test_process.wait() if __name__ == '__main__': main()