[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Xen-devel] [PATCH] Allow xm to spawn vnc viewer



The new merged qemu no longer has the ability to spawn a vnc viewer
process in the bowels of the xend/qemu stack.  In this patch we
provide support for this use case in a different manner - one more
akin to the mechanism used for `xm console' and `xm create -c'.

We introduce new xm options:
   xm create --vncviewer [--vncviewer-autopass]
   xm vncviewer [--vncviewer-autopass]

These spawn a VNC viewer, obtaining the relevant information
(including the port number and if you tell it your viewer supports it
the password to use) directly from xenstore.

Like xm console it waits in the foreground for the vnc port to become
available; the timeout case isn't handled as well as it might be - it
just causes the whole program (xm) to die with `Alarm clock' but this
is difficult to deal with given the current structure of the xs Python
lowlevel interface, which doesn't provide a timeout on the call to
wait for a xenstore watch.

Signed-off-by: Ian Jackson <ian.jackson@xxxxxxxxxxxxx>

diff -r bfe5ad83a491 tools/python/xen/util/utils.py
--- a/tools/python/xen/util/utils.py    Wed Jul 30 10:55:06 2008 +0100
+++ b/tools/python/xen/util/utils.py    Wed Jul 30 15:53:26 2008 +0100
@@ -1,6 +1,50 @@ import traceback
 import traceback
 import sys
+import os
 
 def exception_string(e):
         (ty,v,tb) = sys.exc_info()
         return traceback.format_exception_only(ty,v)
+
+def daemonize(prog, args, stdin_tmpfile=None):
+    """Runs a program as a daemon with the list of arguments.  Returns the PID
+    of the daemonized program, or returns 0 on error.
+    """
+    r, w = os.pipe()
+    pid = os.fork()
+
+    if pid == 0:
+        os.close(r)
+        w = os.fdopen(w, 'w')
+        os.setsid()
+        try:
+            pid2 = os.fork()
+        except:
+            pid2 = None
+        if pid2 == 0:
+            os.chdir("/")
+            null_fd = os.open("/dev/null", os.O_RDWR)
+            if stdin_tmpfile is not None:
+                os.dup2(stdin_tmpfile.fileno(), 0)
+            else:
+                os.dup2(null_fd, 0)
+            os.dup2(null_fd, 1)
+            os.dup2(null_fd, 2)
+            for fd in range(3, 256):
+                try:
+                    os.close(fd)
+                except:
+                    pass
+            os.execvp(prog, args)
+            os._exit(1)
+        else:
+            w.write(str(pid2 or 0))
+            w.close()
+            os._exit(0)
+    os.close(w)
+    r = os.fdopen(r)
+    daemon_pid = int(r.read())
+    r.close()
+    os.waitpid(pid, 0)
+    return daemon_pid
+
diff -r bfe5ad83a491 tools/python/xen/xm/console.py
--- a/tools/python/xen/xm/console.py    Wed Jul 30 10:55:06 2008 +0100
+++ b/tools/python/xen/xm/console.py    Wed Jul 30 16:13:40 2008 +0100
@@ -15,10 +15,71 @@
 # Copyright (C) 2005 XenSource Ltd
 #============================================================================
 
+import xen.util.auxbin
+import xen.lowlevel.xs
+import os
+import sys
+import signal
+from xen.util import utils
 
 XENCONSOLE = "xenconsole"
 
-import xen.util.auxbin
-
 def execConsole(domid):
     xen.util.auxbin.execute(XENCONSOLE, [str(domid)])
+
+
+class OurXenstoreConnection:
+    def __init__(self):
+        self.handle = xen.lowlevel.xs.xs()
+    def read_eventually(self, path):
+        watch = None
+        trans = None
+        try:
+            signal.alarm(10)
+            watch = self.handle.watch(path, None)
+            while True:
+                result = self.handle.read('0', path)
+                if result is not None:
+                    return result
+                self.handle.read_watch()
+            self.handle.unwatch(path, watch)
+            signal.alarm(0)
+        except:
+            signal.alarm(0)
+            if watch is not None: self.handle.unwatch(path, watch)
+            raise
+    def read_maybe(self, path):
+        return self.handle.read('0', path)
+
+def runVncViewer(domid, do_autopass, do_daemonize=False):
+    xs = OurXenstoreConnection()
+    d = '/local/domain/%d/' % domid
+    vnc_port = xs.read_eventually(d + 'console/vnc-port')
+    vfb_backend = xs.read_maybe(d + 'device/vfb/0/backend')
+    vnc_listen = None
+    vnc_password = None
+    vnc_password_tmpfile = None
+    cmdl = ['vncviewer']
+    if vfb_backend is not None:
+        vnc_listen = xs.read_maybe(vfb_backend + '/vnclisten')
+        if do_autopass:
+            vnc_password = xs.read_maybe(vfb_backend + '/vncpasswd')
+            if vnc_password is not None:
+                cmdl.append('-autopass')
+                vnc_password_tmpfile = os.tmpfile()
+                print >>vnc_password_tmpfile, vnc_password
+                vnc_password_tmpfile.seek(0)
+                vnc_password_tmpfile.flush()
+    if vnc_listen is None:
+        vnc_listen = 'localhost'
+    cmdl.append('%s:%d' % (vnc_listen, int(vnc_port) - 5900))
+    if do_daemonize:
+        pid = utils.daemonize('vncviewer', cmdl, vnc_password_tmpfile)
+        if pid == 0:
+            puts >>sys.stderr, 'failed to invoke vncviewer'
+            os._exit(-1)
+    else:
+        print 'invoking ', ' '.join(cmdl)
+        if vnc_password_tmpfile is not None:
+            os.dup2(vnc_password_tmpfile.fileno(), 0)
+        os.execvp('vncviewer', cmdl)
diff -r bfe5ad83a491 tools/python/xen/xm/create.py
--- a/tools/python/xen/xm/create.py     Wed Jul 30 10:55:06 2008 +0100
+++ b/tools/python/xen/xm/create.py     Wed Jul 30 15:55:11 2008 +0100
@@ -36,10 +36,12 @@ from xen.util import vscsi_util
 from xen.util import vscsi_util
 import xen.util.xsm.xsm as security
 from xen.xm.main import serverType, SERVER_XEN_API, get_single_vm
+from xen.util import utils
 
 from xen.xm.opts import *
 
 from main import server
+from main import domain_name_to_domid
 import console
 
 
@@ -118,6 +120,14 @@ gopts.opt('console_autoconnect', short='
           fn=set_true, default=0,
           use="Connect to the console after the domain is created.")
 
+gopts.opt('vncviewer',
+          fn=set_true, default=0,
+          use="Connect to the VNC display after the domain is created.")
+
+gopts.opt('vncviewer-autopass',
+          fn=set_true, default=0,
+          use="Pass VNC password to viewer via stdin and -autopass.")
+
 gopts.var('vncpasswd', val='NAME',
           fn=set_value, default=None,
           use="Password for VNC console on HVM domain.")
@@ -128,7 +138,7 @@ gopts.var('vncviewer', val='no|yes',
            "The address of the vncviewer is passed to the domain on the "
            "kernel command line using 'VNC_SERVER=<host>:<port>'. The port "
            "used by vnc is 5500 + DISPLAY. A display value with a free port "
-           "is chosen if possible.\nOnly valid when vnc=1.")
+           "is chosen if possible.\nOnly valid when vnc=1.\nDEPRECATED")
 
 gopts.var('vncconsole', val='no|yes',
           fn=set_bool, default=None,
@@ -1108,44 +1118,6 @@ def choose_vnc_display():
     return None
 vncpid = None
 
-def daemonize(prog, args):
-    """Runs a program as a daemon with the list of arguments.  Returns the PID
-    of the daemonized program, or returns 0 on error.
-    """
-    r, w = os.pipe()
-    pid = os.fork()
-
-    if pid == 0:
-        os.close(r)
-        w = os.fdopen(w, 'w')
-        os.setsid()
-        try:
-            pid2 = os.fork()
-        except:
-            pid2 = None
-        if pid2 == 0:
-            os.chdir("/")
-            for fd in range(0, 256):
-                try:
-                    os.close(fd)
-                except:
-                    pass
-            os.open("/dev/null", os.O_RDWR)
-            os.dup2(0, 1)
-            os.dup2(0, 2)
-            os.execvp(prog, args)
-            os._exit(1)
-        else:
-            w.write(str(pid2 or 0))
-            w.close()
-            os._exit(0)
-    os.close(w)
-    r = os.fdopen(r)
-    daemon_pid = int(r.read())
-    r.close()
-    os.waitpid(pid, 0)
-    return daemon_pid
-
 def spawn_vnc(display):
     """Spawns a vncviewer that listens on the specified display.  On success,
     returns the port that the vncviewer is listening on and sets the global
@@ -1154,7 +1126,7 @@ def spawn_vnc(display):
     vncargs = (["vncviewer", "-log", "*:stdout:0",
             "-listen", "%d" % (VNC_BASE_PORT + display) ])
     global vncpid
-    vncpid = daemonize("vncviewer", vncargs)
+    vncpid = utils.daemonize("vncviewer", vncargs)
     if vncpid == 0:
         return 0
 
@@ -1362,6 +1334,11 @@ def main(argv):
     elif not opts.is_xml:
         dom = make_domain(opts, config)
         
+    if opts.vals.vncviewer:
+        domid = domain_name_to_domid(sxp.child_value(config, 'name', -1))
+        vncviewer_autopass = getattr(opts.vals,'vncviewer-autopass', False)
+        console.runVncViewer(domid, vncviewer_autopass, True)
+    
 def do_console(domain_name):
     cpid = os.fork() 
     if cpid != 0:
@@ -1373,13 +1350,7 @@ def do_console(domain_name):
                 if os.WEXITSTATUS(rv) != 0:
                     sys.exit(os.WEXITSTATUS(rv))
             try:
-                # Acquire the console of the created dom
-                if serverType == SERVER_XEN_API:
-                    domid = server.xenapi.VM.get_domid(
-                               get_single_vm(domain_name))
-                else:
-                    dom = server.xend.domain(domain_name)
-                    domid = int(sxp.child_value(dom, 'domid', '-1'))
+                domid = domain_name_to_domid(domain_name)
                 console.execConsole(domid)
             except:
                 pass
diff -r bfe5ad83a491 tools/python/xen/xm/main.py
--- a/tools/python/xen/xm/main.py       Wed Jul 30 10:55:06 2008 +0100
+++ b/tools/python/xen/xm/main.py       Wed Jul 30 16:04:25 2008 +0100
@@ -64,6 +64,9 @@ from xen.xend import XendOptions
 from xen.xend import XendOptions
 xoptions = XendOptions.instance()
 
+import signal
+signal.signal(signal.SIGINT, signal.SIG_DFL)
+
 # getopt.gnu_getopt is better, but only exists in Python 2.3+.  Use
 # getopt.getopt if gnu_getopt is not available.  This will mean that options
 # may only be specified before positional arguments.
@@ -97,6 +100,8 @@ SUBCOMMAND_HELP = {
     
     'console'     : ('[-q|--quiet] <Domain>',
                      'Attach to <Domain>\'s console.'),
+    'vncviewer'   : ('[--[vncviewer-]autopass] <Domain>',
+                     'Attach to <Domain>\'s VNC server.'),
     'create'      : ('<ConfigFile> [options] [vars]',
                      'Create a domain based on <ConfigFile>.'),
     'destroy'     : ('<Domain>',
@@ -243,6 +248,10 @@ SUBCOMMAND_OPTIONS = {
     'console': (
        ('-q', '--quiet', 'Do not print an error message if the domain does not 
exist'),
     ),
+    'vncviewer': (
+       ('', '--autopass', 'Pass VNC password to viewer via stdin and 
-autopass'),
+       ('', '--vncviewer-autopass', '(consistency alias for --autopass)'),
+    ),
     'dmesg': (
        ('-c', '--clear', 'Clear dmesg buffer as well as printing it'),
     ),
@@ -260,6 +269,8 @@ SUBCOMMAND_OPTIONS = {
     'start': (
        ('-p', '--paused', 'Do not unpause domain after starting it'),
        ('-c', '--console_autoconnect', 'Connect to the console after the 
domain is created'),
+       ('', '--vncviewer', 'Connect to display via VNC after the domain is 
created'),
+       ('', '--vncviewer-autopass', 'Pass VNC password to viewer via stdin and 
-autopass'),
     ),
     'resume': (
        ('-p', '--paused', 'Do not unpause domain after resuming it'),
@@ -277,6 +288,7 @@ SUBCOMMAND_OPTIONS = {
 
 common_commands = [
     "console",
+    "vncviewer",
     "create",
     "new",
     "delete",
@@ -304,6 +316,7 @@ common_commands = [
 
 domain_commands = [
     "console",
+    "vncviewer",
     "create",
     "new",
     "delete",
@@ -1185,14 +1198,20 @@ def xm_start(args):
 
     paused = False
     console_autoconnect = False
+    vncviewer = False
+    vncviewer_autopass = False
 
     try:
-        (options, params) = getopt.gnu_getopt(args, 'cp', 
['console_autoconnect','paused'])
+        (options, params) = getopt.gnu_getopt(args, 'cp', 
['console_autoconnect','paused','vncviewer','vncviewer-autopass'])
         for (k, v) in options:
             if k in ('-p', '--paused'):
                 paused = True
             if k in ('-c', '--console_autoconnect'):
                 console_autoconnect = True
+            if k in ('--vncviewer'):
+                vncviewer = True
+            if k in ('--vncviewer-autopass'):
+                vncviewer_autopass = True
 
         if len(params) != 1:
             raise OptionError("Expects 1 argument")
@@ -1204,6 +1223,9 @@ def xm_start(args):
 
     if console_autoconnect:
         start_do_console(dom)
+
+    if console_autoconnect:
+        console.runVncViewer(domid, vncviewer_autopass, True)
 
     try:
         if serverType == SERVER_XEN_API:
@@ -1783,6 +1805,40 @@ def xm_console(args):
     console.execConsole(domid)
 
 
+def domain_name_to_domid(domain_name):
+    if serverType == SERVER_XEN_API:
+        domid = server.xenapi.VM.get_domid(
+                   get_single_vm(domain_name))
+    else:
+        dom = server.xend.domain(domain_name)
+        domid = int(sxp.child_value(dom, 'domid', '-1'))
+    return domid
+
+def xm_vncviewer(args):
+    autopass = False;
+
+    try:
+        (options, params) = getopt.gnu_getopt(args, '', 
['autopass','vncviewer-autopass'])
+    except getopt.GetoptError, opterr:
+        err(opterr)
+        usage('vncviewer')
+
+    for (k, v) in options:
+        if k in ['--autopass','--vncviewer-autopass']:
+            autopass = True
+        else:
+            assert False
+
+    if len(params) != 1:
+        err('No domain given (or several parameters specified)')
+        usage('vncviewer')
+
+    dom = params[0]
+    domid = domain_name_to_domid(dom)
+
+    console.runVncViewer(domid, autopass)
+
+
 def xm_uptime(args):
     short_mode = 0
 
@@ -2617,6 +2673,7 @@ commands = {
     "event-monitor": xm_event_monitor,
     # console commands
     "console": xm_console,
+    "vncviewer": xm_vncviewer,
     # xenstat commands
     "top": xm_top,
     # domain commands
_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxxxxxxxx
http://lists.xensource.com/xen-devel

 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.