diff options
-rwxr-xr-x | spice2 | 288 |
1 files changed, 238 insertions, 50 deletions
@@ -11,6 +11,7 @@ import re import time import shutil import itertools +import tempfile try: import argparse @@ -60,6 +61,7 @@ images = { } 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'} @@ -295,8 +297,16 @@ def get_temp_name(prefix, postfix): filename = make_name(i) return filename -def create_qcow_with_backing_file(target_base, backing_file, erase_at_exit): - filename = get_temp_name(target_base, '.qcow2') +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: @@ -311,12 +321,28 @@ def get_image(args): 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(args.image) if args.backing_file: - base_image_name = os.path.basename(args.image) image_fullpath = create_qcow_with_backing_file( - target_base='/tmp/'+base_image_name, + 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) + erase_at_exit=args.erase_temp_backing_file, + use_temp=True) + elif args.migrator: + image_fullpath = '/tmp/'+base_image_name+'.migrate' + os.path.splitext(args.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) @@ -370,6 +396,8 @@ def build_spice_option(args): ret.append('jpeg-wan-compression=%s' % args.wan_jpeg_compression) if args.image_compression: ret.append('image-compression=%s' % args.image_compression) + if args.secure_channels: + ret.extend(['tls-channel=%s' % name for name in args.secure_channels.split(',')]) return ['-spice', ','.join(ret)] class MissingFileException(Exception): @@ -430,7 +458,6 @@ def get_qemu_exec(args): return raise_unless_exists("missing qemu-exec %r", qemu_exec) def bridge_participants(brname): - interfaces = [] if not os.path.exists('/sys/class/net/%s' % brname): print("no such bridge %s" % brname) sys.exit(1) @@ -481,13 +508,19 @@ def build_qemu_cmdline_and_env(args): os.system('echo $LD_LIBRARY_PATH') cmdline = [qemu_exec, "-chardev", "stdio,id=muxstdio,mux=on", "-mon", "chardev=muxstdio,mode=readline", - "-vga", args.vga, "-drive", "file=%s,cache=%s,if=%s,readonly=%s" % (args.image_fullpath, args.cache, args.block_if, args.read_only), "-enable-kvm", "-L", bios_dir, "-m", - str(args.memory), - "-cpu", str(args.cpu)] + build_spice_option(args) + str(args.memory)] + 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. @@ -499,8 +532,13 @@ def build_qemu_cmdline_and_env(args): 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: @@ -515,6 +553,14 @@ def build_qemu_cmdline_and_env(args): 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', @@ -536,21 +582,46 @@ def build_qemu_cmdline_and_env(args): environment.append(('QEMU_AUDIO_DRV', 'spice')) if args.revision: cmdline.extend(['-global', 'qxl-vga.revision=%d' % args.revision]) - if args.tablet or args.smartcard: - cmdline.extend(['-usb']) + 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']) - need_virtio_serial = args.vdagent or args.kdvirtserial + #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,multifunction=on']) + 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,name=com.redhat.spice.0']) + '-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: + 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) @@ -561,6 +632,11 @@ def build_qemu_cmdline_and_env(args): 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]) @@ -599,6 +675,8 @@ def build_qemu_cmdline_and_env(args): 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 @@ -638,18 +716,53 @@ def build_qemu_cmdline_and_env(args): 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.cgdb: + if args.qemu_gdb: cgdb = which('cgdb') if not cgdb: gdb = which('gdb') @@ -673,6 +786,7 @@ def build_qemu_cmdline_and_env(args): if args.time: print("wrapping in time") cmdline = ['/usr/bin/time', '--verbose'] + cmdline + environment.append(('SPICE_DEBUG_ALLOW_MC', '1')) return cmdline, environment def run_xephyr(display, size=(1024,768+40), show_output=False, debug=False): @@ -689,7 +803,7 @@ def rpdb2_break(): rpdb2.rpdb2.start_embedded_debugger('a') def get_client_exe(client): - return {'spicec': spicec_exe, 'spicy': spicy_exe}.get(client, 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): @@ -705,14 +819,26 @@ def run_with_display(f): 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(host, port, client, smartcard, certs, dbdir, tls_port=None, ca_file=None, host_subject=None, - display=None, auto_conf=False): - args = [get_client_exe(client), '-h', host] - if port: - args.extend(['-p', str(port)]) + 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', @@ -724,20 +850,21 @@ def run_client(host, port, client, else: print("Error: client %s not supported for smartcard yet, please add support" % client) sys.exit(1) - if auto_conf and oldclient: + if auto_conf and client != 'spice-gtk': args.append('--full-screen=auto-conf') if tls_port: - if oldclient: - args.extend(['-s', str(tls_port), '--host-subject', - host_subject, '--ca-file', ca_file]) - else: - args.extend(['-s', str(tls_port), - '--spice-host-subject=%s' % host_subject, - '--spice-ca-file=%s' % ca_file]) + 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) - return start_process(args=args, kill=True) + environment = dict(os.environ) + if client_debug: + environment['SPICE_DEBUG'] = '1' + environment['G_DEBUG'] = 'all' + return start_process(args=args, kill=True, env=environment) @run_with_display def run_vnc_client(host, port, display=None): @@ -778,7 +905,30 @@ def wait_for_port(port): ################################################################################ -default_qmp_port = 20000 # TODO - port allocation (=PortPromisedRange(20000, 100)) +#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: @@ -823,7 +973,7 @@ def parseargs(): qemu_group.add_argument('--bios-dir', default=None) qemu_group.add_argument('--bios', default=None) disk_group.add_argument('--image', default=image) - disk_group.add_argument('--block-if', default='ide', choices=['acpi', 'ide', 'virtio']) + 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') @@ -837,9 +987,13 @@ def parseargs(): 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') - client_group.add_argument('--client', default='spicec') + 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('--port', dest='spice_port', type=int, action='store') + 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') @@ -849,21 +1003,26 @@ def parseargs(): 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'], + devices_group.add_argument('--vga', choices=['qxl', 'cirrus', 'virtio'], default='qxl') devices_group.add_argument('--qxl', type=int, default=1) - devices_group.add_argument('--qxl-ram', type=int) - devices_group.add_argument('--qxl-vram', type=int) + 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']) + choices=['off', 'auto', 'on', 'quic', 'auto_glz', 'auto_lz']) 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) @@ -883,6 +1042,8 @@ def parseargs(): 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', @@ -890,28 +1051,33 @@ def parseargs(): default='virtio-net-pci') devices_group.add_argument('--sound', choices=['intel-hda', 'ac97', None], default=None) - network_group.add_argument('--smb', default=None) - network_group.add_argument('--network', choices=['user', 'bridge', None], - default='user') - network_group.add_argument('--mac') - network_group.add_argument('--bridge', default='virbr0') - network_group.add_argument('--tun', default='tap0') devices_group.add_argument('--revision', default=None, type=int) - devices_group.add_argument('--cpu', choices=['host'], default='host', + 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('--cgdb', default=False, action='store_true') + 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= @@ -937,15 +1103,19 @@ def parseargs(): 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=default_qmp_port, type=int) + 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:] - del default_args[i:] + default_args = default_args[:i] + else: + default_rest = [] args, rest = parser.parse_known_args(default_args + sys.argv[1:]) rest = default_rest + rest if args.image in images.keys(): @@ -1068,6 +1238,7 @@ def update_certs(args): ################################################################################ processes = [] temp_files = [] +temp_dirs = [] class MyProcess(subprocess.Popen): def spice_kill(self): @@ -1086,7 +1257,14 @@ def start_process(args, kill=False, show_output=True, **kw): 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 @@ -1103,8 +1281,13 @@ def cleanup(): for m in temp_files: if not os.path.exists(m): continue - print("removing %s" % m) + 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): @@ -1118,11 +1301,15 @@ def ignore_ctrlc(): 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() @@ -1137,7 +1324,8 @@ def launch_clients(args, display, clients, num_clients): display=cur_display, tls_port=args.tls_port, ca_file=args.ca_cert, - host_subject=args.host_subject) + host_subject=args.host_subject, + client_debug=args.client_debug) clients.append(client_process) |