diff options
author | Alon Levy <alevy@redhat.com> | 2011-07-28 19:14:52 +0300 |
---|---|---|
committer | Alon Levy <alevy@redhat.com> | 2011-07-28 19:15:55 +0300 |
commit | 115b16df48908ebe24c7c856e6ce6b26d9a88bb2 (patch) | |
tree | a0999a17785ac4197f63892dce335621ebd327c6 | |
parent | e4a983f3365514c62151fe3704c1bade1b547ccf (diff) |
multiple
command line refactor
function for client command line build
options for tls channel (ca, key)
beginning of qmp usage, just adding the monitor. (via qmp.py from qemu repo)
-rw-r--r-- | qmp.py | 157 | ||||
-rwxr-xr-x | spice2 | 129 |
2 files changed, 256 insertions, 30 deletions
@@ -0,0 +1,157 @@ +# QEMU Monitor Protocol Python class +# +# Copyright (C) 2009, 2010 Red Hat Inc. +# +# Authors: +# Luiz Capitulino <lcapitulino@redhat.com> +# +# This work is licensed under the terms of the GNU GPL, version 2. See +# the COPYING file in the top-level directory. + +import json +import errno +import socket + +class QMPError(Exception): + pass + +class QMPConnectError(QMPError): + pass + +class QMPCapabilitiesError(QMPError): + pass + +class QEMUMonitorProtocol: + def __init__(self, address, server=False): + """ + Create a QEMUMonitorProtocol class. + + @param address: QEMU address, can be either a unix socket path (string) + or a tuple in the form ( address, port ) for a TCP + connection + @param server: server mode listens on the socket (bool) + @raise socket.error on socket connection errors + @note No connection is established, this is done by the connect() or + accept() methods + """ + self.__events = [] + self.__address = address + self.__sock = self.__get_sock() + if server: + self.__sock.bind(self.__address) + self.__sock.listen(1) + + def __get_sock(self): + if isinstance(self.__address, tuple): + family = socket.AF_INET + else: + family = socket.AF_UNIX + return socket.socket(family, socket.SOCK_STREAM) + + def __negotiate_capabilities(self): + self.__sockfile = self.__sock.makefile() + greeting = self.__json_read() + if greeting is None or not greeting.has_key('QMP'): + raise QMPConnectError + # Greeting seems ok, negotiate capabilities + resp = self.cmd('qmp_capabilities') + if "return" in resp: + return greeting + raise QMPCapabilitiesError + + def __json_read(self, only_event=False): + while True: + data = self.__sockfile.readline() + if not data: + return + resp = json.loads(data) + if 'event' in resp: + self.__events.append(resp) + if not only_event: + continue + return resp + + error = socket.error + + def connect(self): + """ + Connect to the QMP Monitor and perform capabilities negotiation. + + @return QMP greeting dict + @raise socket.error on socket connection errors + @raise QMPConnectError if the greeting is not received + @raise QMPCapabilitiesError if fails to negotiate capabilities + """ + self.__sock.connect(self.__address) + return self.__negotiate_capabilities() + + def accept(self): + """ + Await connection from QMP Monitor and perform capabilities negotiation. + + @return QMP greeting dict + @raise socket.error on socket connection errors + @raise QMPConnectError if the greeting is not received + @raise QMPCapabilitiesError if fails to negotiate capabilities + """ + self.__sock, _ = self.__sock.accept() + return self.__negotiate_capabilities() + + def cmd_obj(self, qmp_cmd): + """ + Send a QMP command to the QMP Monitor. + + @param qmp_cmd: QMP command to be sent as a Python dict + @return QMP response as a Python dict or None if the connection has + been closed + """ + try: + self.__sock.sendall(json.dumps(qmp_cmd)) + except socket.error, err: + if err[0] == errno.EPIPE: + return + raise socket.error(err) + return self.__json_read() + + def cmd(self, name, args=None, id=None): + """ + Build a QMP command and send it to the QMP Monitor. + + @param name: command name (string) + @param args: command arguments (dict) + @param id: command id (dict, list, string or int) + """ + qmp_cmd = { 'execute': name } + if args: + qmp_cmd['arguments'] = args + if id: + qmp_cmd['id'] = id + return self.cmd_obj(qmp_cmd) + + def get_events(self, wait=False): + """ + Get a list of available QMP events. + + @param wait: block until an event is available (bool) + """ + self.__sock.setblocking(0) + try: + self.__json_read() + except socket.error, err: + if err[0] == errno.EAGAIN: + # No data available + pass + self.__sock.setblocking(1) + if not self.__events and wait: + self.__json_read(only_event=True) + return self.__events + + def clear_events(self): + """ + Clear current list of pending events. + """ + self.__events = [] + + def close(self): + self.__sock.close() + self.__sockfile.close() @@ -315,7 +315,37 @@ def build_test_cmdline(args): args.port = 5912 return ' '.join(parts) -def build_cmdline(args): +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') + return ['-spice', ','.join(ret)] + +def build_qemu_cmdline(args): if args.test_prog: return build_test_cmdline(args) qemu = args.qemu @@ -336,16 +366,20 @@ def build_cmdline(args): os.environ['SPICE_WORKER_RECORD_FILENAME'] = args.record_cmd os.system('echo $LD_LIBRARY_PATH') cmdline = [qemuexe, - "-chardev", "stdio,id=muxstdio,mux=on", - "-spice", "disable-ticketing,port=%s" % args.spice_port, + "-chardev", "stdio,id=muxstdio,mux=on", "-mon", "chardev=muxstdio,mode=readline", "-vga", args.vga, "-drive", "file=%s,cache=%s" % (args.image_fullpath, args.cache), - "-snapshot", "-mon", "chardev=muxstdio", "-enable-kvm", + "-snapshot", "-enable-kvm", "-net", "nic,model=virtio", "-L", bios_dir, "-m", str(args.memory), - "-cpu", str(args.cpu), - '-no-shutdown' if args.no_shutdown else ''] + "-cpu", str(args.cpu)] + build_spice_option(args) + if args.qmp_port: + cmdline.extend(["-chardev", + "socket,id=qmpmon,host=localhost,port=%s,server" % args.qmp_port, + "-mon", "chardev=qmpmon,mode=control",]) + if args.no_shutdown: + cmdline.append('-no-shutdown') if args.cpus > 1: cmdline.extend(['-smp', str(args.cpus)]) if args.guestdebug > 0: @@ -361,7 +395,7 @@ def build_cmdline(args): cmdline.extend(['-global', 'qxl-vga.revision=%d' % args.revision]) if args.tablet: cmdline.extend(['-usb', '-device', 'usb-tablet']) - else: + if args.vdagent: cmdline.extend(['-device', 'virtio-serial', '-chardev', 'spicevmc,name=vdagent,id=vdagent', '-device', 'virtserialport,chardev=vdagent,name=com.redhat.spice.0']) # user networking @@ -381,7 +415,7 @@ def build_cmdline(args): if args.smartcard != 'off': ccid_debug = 1 passthru_debug = 1 - cmdline.extend(['-chardev spicevmc,id=smartcard,debug=3,name=smartcard', + 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), ]) @@ -418,7 +452,9 @@ def build_cmdline(args): ###### - print_qemu_and_spice_versions(qemuexe=qemuexe, which=qemu, bios_dir=bios_dir) + print_qemu_and_spice_versions(qemuexe=qemuexe, + clientexe=get_client_ext(args.oldclient), + which=qemu, bios_dir=bios_dir) # wrap in various tools if args.cgdb: cgdb = which('cgdb') @@ -441,33 +477,42 @@ 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 = '' +def get_client_ext(oldclient): + return spicec_exe if oldclient else spicy_exe + +def run_client(host, port, oldclient, + smartcard, certs, dbdir, + tls_port=None, ca_file=None, host_subject=None, + display=None, auto_conf=False): + args = [get_client_ext(oldclient), '-h', host] + if port: + args.extend(['-p', str(port)]) old_display = None if display is not None: old_display = os.environ['DISPLAY'] os.environ['DISPLAY'] = ':%d.0' % display if smartcard == 'sw': if oldclient: - s_opts = '--smartcard --smartcard-certs %s --smartcard-db %s' % ( - ','.join(certs), dbdir) + args.extend(['--smartcard', + '--smartcard-cert', ','.join(certs), '--smartcard-db', dbdir]) else: - s_opts = '--certificates=%s --certificate-db=%s' % ( - ','.join(certs), dbdir) + args.extend(['--spice-smartcard', + '--spice-smartcard-certificates=%s' % (','.join(certs)), + '--spice-smartcard-db=%s' % dbdir]) if auto_conf and oldclient: - ac_opts = '--full-screen=auto-conf' + 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 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 %(autoconf)s' % - dict( - host=host, port=port, - exe=spicec_exe if oldclient else spicy_exe, - autoconf=ac_opts, - smartcard=s_opts)).split(), kill=True) + p = start_process(args=args, kill=True) if old_display: os.environ['DISPLAY'] = old_display return p @@ -545,12 +590,23 @@ def parseargs(): parser.add_argument('--memcheck', dest='memcheck', default=False, action='store_true') parser.add_argument('--oldclient', dest='oldclient', default=False, action='store_true') parser.add_argument('--clients', dest='clients', default=0, type=int) - parser.add_argument('--port', dest='spice_port', type=int, action='store', default=6666) + parser.add_argument('--port', dest='spice_port', type=int, action='store') parser.add_argument('--host', dest='spice_host', default='127.0.0.1') parser.add_argument('--qxl', dest='qxl', type=int, default=1) + parser.add_argument('--qmp-port', dest='qmp_port', type=int, default=0) parser.add_argument('--tablet', dest='tablet', action='store_true', default=False) + parser.add_argument('--vdagent', dest='vdagent', action='store_true', default=False) + parser.add_argument('--novdagent', dest='vdagent', action='store_false') parser.add_argument('--notablet', dest='tablet', action='store_false') parser.add_argument('--xephyr', dest='xephyr', action='store_true', default=False) + parser.add_argument('--tls-port', type=int) + parser.add_argument('--x509-dir') + parser.add_argument('--ca-cert') + parser.add_argument('--server-cert') + parser.add_argument('--server-key') + parser.add_argument('--server-key-password') + parser.add_argument('--host-subject') + parser.add_argument('--sasl', dest='sasl', action='store_true', default=False) parser.add_argument('--smb', dest='smb', default=None) parser.add_argument('--cgdb', dest='cgdb', default=False, action='store_true') parser.add_argument('--guestdebug', dest='guestdebug', type=int, default=0) @@ -598,6 +654,9 @@ def parseargs(): print "image not provided. use --image" parser.print_usage() sys.exit(1) + # tls arguments + if args.x509_dir: + args.ca_cert = os.path.join(args.x509_dir, 'ca-cert.pem') return args def set_runonce_registry(g, root, runonce_paths): @@ -708,6 +767,7 @@ class MyProcess(subprocess.Popen): def start_process(args, kill=False, show_output=True, **kw): global processes + print repr(args) if show_output: p = MyProcess(args, **kw) else: @@ -765,7 +825,7 @@ def xephyr_get_display(): def main(): args = parseargs() update_certs(args) - cmdline = build_cmdline(args) + cmdline = build_qemu_cmdline(args) atexit.register(cleanup) if args.runonce: set_runonce(args.image_fullpath, args.runonce) @@ -791,8 +851,8 @@ def main(): 'I ' if args.incoming else '', args.title if args.title else os.path.basename(sys.argv[0]) )) - print repr(cmdline) if args.showcmdline: + print repr(cmdline) return ignore_ctrlc() exit_on_hup() @@ -809,6 +869,9 @@ def main(): (python_exe, spicedump_exe, proxy_port, real_spice_port, ' -f %s' % args.spicedump_filter if args.spicedump_filter else '' )).split()) + if args.qmp_port: + #start_process( + print "TODO" if args.test_func: test_func = globals()['test_'+args.test_func] test_func(server=test_process, args=args) @@ -818,13 +881,19 @@ def main(): cur_display = display.next() if cur_display: run_xephyr(display=cur_display) - wait_for_port(args.spice_port) + if args.spice_port: + wait_for_port(args.spice_port) + else: + wait_for_port(args.tls_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) + display=cur_display, + tls_port=args.tls_port, + ca_file=args.ca_cert, + host_subject=args.host_subject) clients.append(client_process) if args.vnc_port and args.vnc_clients > 0: if not vncviewer: |