ia64/xen-unstable

changeset 18204:21dd1fdb73d8

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@eu.citrix.com>
author Keir Fraser <keir.fraser@citrix.com>
date Wed Jul 30 16:22:45 2008 +0100 (2008-07-30)
parents 9ee2e41a68a1
children e257f75a2cdf
files tools/python/xen/util/utils.py tools/python/xen/xm/console.py tools/python/xen/xm/create.py tools/python/xen/xm/main.py
line diff
     1.1 --- a/tools/python/xen/util/utils.py	Wed Jul 30 15:25:09 2008 +0100
     1.2 +++ b/tools/python/xen/util/utils.py	Wed Jul 30 16:22:45 2008 +0100
     1.3 @@ -1,6 +1,50 @@
     1.4  import traceback
     1.5  import sys
     1.6 +import os
     1.7  
     1.8  def exception_string(e):
     1.9          (ty,v,tb) = sys.exc_info()
    1.10          return traceback.format_exception_only(ty,v)
    1.11 +
    1.12 +def daemonize(prog, args, stdin_tmpfile=None):
    1.13 +    """Runs a program as a daemon with the list of arguments.  Returns the PID
    1.14 +    of the daemonized program, or returns 0 on error.
    1.15 +    """
    1.16 +    r, w = os.pipe()
    1.17 +    pid = os.fork()
    1.18 +
    1.19 +    if pid == 0:
    1.20 +        os.close(r)
    1.21 +        w = os.fdopen(w, 'w')
    1.22 +        os.setsid()
    1.23 +        try:
    1.24 +            pid2 = os.fork()
    1.25 +        except:
    1.26 +            pid2 = None
    1.27 +        if pid2 == 0:
    1.28 +            os.chdir("/")
    1.29 +            null_fd = os.open("/dev/null", os.O_RDWR)
    1.30 +            if stdin_tmpfile is not None:
    1.31 +                os.dup2(stdin_tmpfile.fileno(), 0)
    1.32 +            else:
    1.33 +                os.dup2(null_fd, 0)
    1.34 +            os.dup2(null_fd, 1)
    1.35 +            os.dup2(null_fd, 2)
    1.36 +            for fd in range(3, 256):
    1.37 +                try:
    1.38 +                    os.close(fd)
    1.39 +                except:
    1.40 +                    pass
    1.41 +            os.execvp(prog, args)
    1.42 +            os._exit(1)
    1.43 +        else:
    1.44 +            w.write(str(pid2 or 0))
    1.45 +            w.close()
    1.46 +            os._exit(0)
    1.47 +    os.close(w)
    1.48 +    r = os.fdopen(r)
    1.49 +    daemon_pid = int(r.read())
    1.50 +    r.close()
    1.51 +    os.waitpid(pid, 0)
    1.52 +    return daemon_pid
    1.53 +
     2.1 --- a/tools/python/xen/xm/console.py	Wed Jul 30 15:25:09 2008 +0100
     2.2 +++ b/tools/python/xen/xm/console.py	Wed Jul 30 16:22:45 2008 +0100
     2.3 @@ -15,10 +15,71 @@
     2.4  # Copyright (C) 2005 XenSource Ltd
     2.5  #============================================================================
     2.6  
     2.7 +import xen.util.auxbin
     2.8 +import xen.lowlevel.xs
     2.9 +import os
    2.10 +import sys
    2.11 +import signal
    2.12 +from xen.util import utils
    2.13  
    2.14  XENCONSOLE = "xenconsole"
    2.15  
    2.16 -import xen.util.auxbin
    2.17 -
    2.18  def execConsole(domid):
    2.19      xen.util.auxbin.execute(XENCONSOLE, [str(domid)])
    2.20 +
    2.21 +
    2.22 +class OurXenstoreConnection:
    2.23 +    def __init__(self):
    2.24 +        self.handle = xen.lowlevel.xs.xs()
    2.25 +    def read_eventually(self, path):
    2.26 +        watch = None
    2.27 +        trans = None
    2.28 +        try:
    2.29 +            signal.alarm(10)
    2.30 +            watch = self.handle.watch(path, None)
    2.31 +            while True:
    2.32 +                result = self.handle.read('0', path)
    2.33 +                if result is not None:
    2.34 +                    return result
    2.35 +                self.handle.read_watch()
    2.36 +            self.handle.unwatch(path, watch)
    2.37 +            signal.alarm(0)
    2.38 +        except:
    2.39 +            signal.alarm(0)
    2.40 +            if watch is not None: self.handle.unwatch(path, watch)
    2.41 +            raise
    2.42 +    def read_maybe(self, path):
    2.43 +        return self.handle.read('0', path)
    2.44 +
    2.45 +def runVncViewer(domid, do_autopass, do_daemonize=False):
    2.46 +    xs = OurXenstoreConnection()
    2.47 +    d = '/local/domain/%d/' % domid
    2.48 +    vnc_port = xs.read_eventually(d + 'console/vnc-port')
    2.49 +    vfb_backend = xs.read_maybe(d + 'device/vfb/0/backend')
    2.50 +    vnc_listen = None
    2.51 +    vnc_password = None
    2.52 +    vnc_password_tmpfile = None
    2.53 +    cmdl = ['vncviewer']
    2.54 +    if vfb_backend is not None:
    2.55 +        vnc_listen = xs.read_maybe(vfb_backend + '/vnclisten')
    2.56 +        if do_autopass:
    2.57 +            vnc_password = xs.read_maybe(vfb_backend + '/vncpasswd')
    2.58 +            if vnc_password is not None:
    2.59 +                cmdl.append('-autopass')
    2.60 +                vnc_password_tmpfile = os.tmpfile()
    2.61 +                print >>vnc_password_tmpfile, vnc_password
    2.62 +                vnc_password_tmpfile.seek(0)
    2.63 +                vnc_password_tmpfile.flush()
    2.64 +    if vnc_listen is None:
    2.65 +        vnc_listen = 'localhost'
    2.66 +    cmdl.append('%s:%d' % (vnc_listen, int(vnc_port) - 5900))
    2.67 +    if do_daemonize:
    2.68 +        pid = utils.daemonize('vncviewer', cmdl, vnc_password_tmpfile)
    2.69 +        if pid == 0:
    2.70 +            puts >>sys.stderr, 'failed to invoke vncviewer'
    2.71 +            os._exit(-1)
    2.72 +    else:
    2.73 +        print 'invoking ', ' '.join(cmdl)
    2.74 +        if vnc_password_tmpfile is not None:
    2.75 +            os.dup2(vnc_password_tmpfile.fileno(), 0)
    2.76 +        os.execvp('vncviewer', cmdl)
     3.1 --- a/tools/python/xen/xm/create.py	Wed Jul 30 15:25:09 2008 +0100
     3.2 +++ b/tools/python/xen/xm/create.py	Wed Jul 30 16:22:45 2008 +0100
     3.3 @@ -36,10 +36,12 @@ from xen.util import blkif
     3.4  from xen.util import vscsi_util
     3.5  import xen.util.xsm.xsm as security
     3.6  from xen.xm.main import serverType, SERVER_XEN_API, get_single_vm
     3.7 +from xen.util import utils
     3.8  
     3.9  from xen.xm.opts import *
    3.10  
    3.11  from main import server
    3.12 +from main import domain_name_to_domid
    3.13  import console
    3.14  
    3.15  
    3.16 @@ -118,6 +120,14 @@ gopts.opt('console_autoconnect', short='
    3.17            fn=set_true, default=0,
    3.18            use="Connect to the console after the domain is created.")
    3.19  
    3.20 +gopts.opt('vncviewer',
    3.21 +          fn=set_true, default=0,
    3.22 +          use="Connect to the VNC display after the domain is created.")
    3.23 +
    3.24 +gopts.opt('vncviewer-autopass',
    3.25 +          fn=set_true, default=0,
    3.26 +          use="Pass VNC password to viewer via stdin and -autopass.")
    3.27 +
    3.28  gopts.var('vncpasswd', val='NAME',
    3.29            fn=set_value, default=None,
    3.30            use="Password for VNC console on HVM domain.")
    3.31 @@ -128,7 +138,7 @@ gopts.var('vncviewer', val='no|yes',
    3.32             "The address of the vncviewer is passed to the domain on the "
    3.33             "kernel command line using 'VNC_SERVER=<host>:<port>'. The port "
    3.34             "used by vnc is 5500 + DISPLAY. A display value with a free port "
    3.35 -           "is chosen if possible.\nOnly valid when vnc=1.")
    3.36 +           "is chosen if possible.\nOnly valid when vnc=1.\nDEPRECATED")
    3.37  
    3.38  gopts.var('vncconsole', val='no|yes',
    3.39            fn=set_bool, default=None,
    3.40 @@ -1108,44 +1118,6 @@ def choose_vnc_display():
    3.41      return None
    3.42  vncpid = None
    3.43  
    3.44 -def daemonize(prog, args):
    3.45 -    """Runs a program as a daemon with the list of arguments.  Returns the PID
    3.46 -    of the daemonized program, or returns 0 on error.
    3.47 -    """
    3.48 -    r, w = os.pipe()
    3.49 -    pid = os.fork()
    3.50 -
    3.51 -    if pid == 0:
    3.52 -        os.close(r)
    3.53 -        w = os.fdopen(w, 'w')
    3.54 -        os.setsid()
    3.55 -        try:
    3.56 -            pid2 = os.fork()
    3.57 -        except:
    3.58 -            pid2 = None
    3.59 -        if pid2 == 0:
    3.60 -            os.chdir("/")
    3.61 -            for fd in range(0, 256):
    3.62 -                try:
    3.63 -                    os.close(fd)
    3.64 -                except:
    3.65 -                    pass
    3.66 -            os.open("/dev/null", os.O_RDWR)
    3.67 -            os.dup2(0, 1)
    3.68 -            os.dup2(0, 2)
    3.69 -            os.execvp(prog, args)
    3.70 -            os._exit(1)
    3.71 -        else:
    3.72 -            w.write(str(pid2 or 0))
    3.73 -            w.close()
    3.74 -            os._exit(0)
    3.75 -    os.close(w)
    3.76 -    r = os.fdopen(r)
    3.77 -    daemon_pid = int(r.read())
    3.78 -    r.close()
    3.79 -    os.waitpid(pid, 0)
    3.80 -    return daemon_pid
    3.81 -
    3.82  def spawn_vnc(display):
    3.83      """Spawns a vncviewer that listens on the specified display.  On success,
    3.84      returns the port that the vncviewer is listening on and sets the global
    3.85 @@ -1154,7 +1126,7 @@ def spawn_vnc(display):
    3.86      vncargs = (["vncviewer", "-log", "*:stdout:0",
    3.87              "-listen", "%d" % (VNC_BASE_PORT + display) ])
    3.88      global vncpid
    3.89 -    vncpid = daemonize("vncviewer", vncargs)
    3.90 +    vncpid = utils.daemonize("vncviewer", vncargs)
    3.91      if vncpid == 0:
    3.92          return 0
    3.93  
    3.94 @@ -1362,6 +1334,11 @@ def main(argv):
    3.95      elif not opts.is_xml:
    3.96          dom = make_domain(opts, config)
    3.97          
    3.98 +    if opts.vals.vncviewer:
    3.99 +        domid = domain_name_to_domid(sxp.child_value(config, 'name', -1))
   3.100 +        vncviewer_autopass = getattr(opts.vals,'vncviewer-autopass', False)
   3.101 +        console.runVncViewer(domid, vncviewer_autopass, True)
   3.102 +    
   3.103  def do_console(domain_name):
   3.104      cpid = os.fork() 
   3.105      if cpid != 0:
   3.106 @@ -1373,13 +1350,7 @@ def do_console(domain_name):
   3.107                  if os.WEXITSTATUS(rv) != 0:
   3.108                      sys.exit(os.WEXITSTATUS(rv))
   3.109              try:
   3.110 -                # Acquire the console of the created dom
   3.111 -                if serverType == SERVER_XEN_API:
   3.112 -                    domid = server.xenapi.VM.get_domid(
   3.113 -                               get_single_vm(domain_name))
   3.114 -                else:
   3.115 -                    dom = server.xend.domain(domain_name)
   3.116 -                    domid = int(sxp.child_value(dom, 'domid', '-1'))
   3.117 +                domid = domain_name_to_domid(domain_name)
   3.118                  console.execConsole(domid)
   3.119              except:
   3.120                  pass
     4.1 --- a/tools/python/xen/xm/main.py	Wed Jul 30 15:25:09 2008 +0100
     4.2 +++ b/tools/python/xen/xm/main.py	Wed Jul 30 16:22:45 2008 +0100
     4.3 @@ -64,6 +64,9 @@ import inspect
     4.4  from xen.xend import XendOptions
     4.5  xoptions = XendOptions.instance()
     4.6  
     4.7 +import signal
     4.8 +signal.signal(signal.SIGINT, signal.SIG_DFL)
     4.9 +
    4.10  # getopt.gnu_getopt is better, but only exists in Python 2.3+.  Use
    4.11  # getopt.getopt if gnu_getopt is not available.  This will mean that options
    4.12  # may only be specified before positional arguments.
    4.13 @@ -97,6 +100,8 @@ SUBCOMMAND_HELP = {
    4.14      
    4.15      'console'     : ('[-q|--quiet] <Domain>',
    4.16                       'Attach to <Domain>\'s console.'),
    4.17 +    'vncviewer'   : ('[--[vncviewer-]autopass] <Domain>',
    4.18 +                     'Attach to <Domain>\'s VNC server.'),
    4.19      'create'      : ('<ConfigFile> [options] [vars]',
    4.20                       'Create a domain based on <ConfigFile>.'),
    4.21      'destroy'     : ('<Domain>',
    4.22 @@ -243,6 +248,10 @@ SUBCOMMAND_OPTIONS = {
    4.23      'console': (
    4.24         ('-q', '--quiet', 'Do not print an error message if the domain does not exist'),
    4.25      ),
    4.26 +    'vncviewer': (
    4.27 +       ('', '--autopass', 'Pass VNC password to viewer via stdin and -autopass'),
    4.28 +       ('', '--vncviewer-autopass', '(consistency alias for --autopass)'),
    4.29 +    ),
    4.30      'dmesg': (
    4.31         ('-c', '--clear', 'Clear dmesg buffer as well as printing it'),
    4.32      ),
    4.33 @@ -260,6 +269,8 @@ SUBCOMMAND_OPTIONS = {
    4.34      'start': (
    4.35         ('-p', '--paused', 'Do not unpause domain after starting it'),
    4.36         ('-c', '--console_autoconnect', 'Connect to the console after the domain is created'),
    4.37 +       ('', '--vncviewer', 'Connect to display via VNC after the domain is created'),
    4.38 +       ('', '--vncviewer-autopass', 'Pass VNC password to viewer via stdin and -autopass'),
    4.39      ),
    4.40      'resume': (
    4.41         ('-p', '--paused', 'Do not unpause domain after resuming it'),
    4.42 @@ -277,6 +288,7 @@ SUBCOMMAND_OPTIONS = {
    4.43  
    4.44  common_commands = [
    4.45      "console",
    4.46 +    "vncviewer",
    4.47      "create",
    4.48      "new",
    4.49      "delete",
    4.50 @@ -304,6 +316,7 @@ common_commands = [
    4.51  
    4.52  domain_commands = [
    4.53      "console",
    4.54 +    "vncviewer",
    4.55      "create",
    4.56      "new",
    4.57      "delete",
    4.58 @@ -1185,14 +1198,20 @@ def xm_start(args):
    4.59  
    4.60      paused = False
    4.61      console_autoconnect = False
    4.62 +    vncviewer = False
    4.63 +    vncviewer_autopass = False
    4.64  
    4.65      try:
    4.66 -        (options, params) = getopt.gnu_getopt(args, 'cp', ['console_autoconnect','paused'])
    4.67 +        (options, params) = getopt.gnu_getopt(args, 'cp', ['console_autoconnect','paused','vncviewer','vncviewer-autopass'])
    4.68          for (k, v) in options:
    4.69              if k in ('-p', '--paused'):
    4.70                  paused = True
    4.71              if k in ('-c', '--console_autoconnect'):
    4.72                  console_autoconnect = True
    4.73 +            if k in ('--vncviewer'):
    4.74 +                vncviewer = True
    4.75 +            if k in ('--vncviewer-autopass'):
    4.76 +                vncviewer_autopass = True
    4.77  
    4.78          if len(params) != 1:
    4.79              raise OptionError("Expects 1 argument")
    4.80 @@ -1205,6 +1224,9 @@ def xm_start(args):
    4.81      if console_autoconnect:
    4.82          start_do_console(dom)
    4.83  
    4.84 +    if console_autoconnect:
    4.85 +        console.runVncViewer(domid, vncviewer_autopass, True)
    4.86 +
    4.87      try:
    4.88          if serverType == SERVER_XEN_API:
    4.89              server.xenapi.VM.start(get_single_vm(dom), paused)
    4.90 @@ -1783,6 +1805,40 @@ def xm_console(args):
    4.91      console.execConsole(domid)
    4.92  
    4.93  
    4.94 +def domain_name_to_domid(domain_name):
    4.95 +    if serverType == SERVER_XEN_API:
    4.96 +        domid = server.xenapi.VM.get_domid(
    4.97 +                   get_single_vm(domain_name))
    4.98 +    else:
    4.99 +        dom = server.xend.domain(domain_name)
   4.100 +        domid = int(sxp.child_value(dom, 'domid', '-1'))
   4.101 +    return domid
   4.102 +
   4.103 +def xm_vncviewer(args):
   4.104 +    autopass = False;
   4.105 +
   4.106 +    try:
   4.107 +        (options, params) = getopt.gnu_getopt(args, '', ['autopass','vncviewer-autopass'])
   4.108 +    except getopt.GetoptError, opterr:
   4.109 +        err(opterr)
   4.110 +        usage('vncviewer')
   4.111 +
   4.112 +    for (k, v) in options:
   4.113 +        if k in ['--autopass','--vncviewer-autopass']:
   4.114 +            autopass = True
   4.115 +        else:
   4.116 +            assert False
   4.117 +
   4.118 +    if len(params) != 1:
   4.119 +        err('No domain given (or several parameters specified)')
   4.120 +        usage('vncviewer')
   4.121 +
   4.122 +    dom = params[0]
   4.123 +    domid = domain_name_to_domid(dom)
   4.124 +
   4.125 +    console.runVncViewer(domid, autopass)
   4.126 +
   4.127 +
   4.128  def xm_uptime(args):
   4.129      short_mode = 0
   4.130  
   4.131 @@ -2617,6 +2673,7 @@ commands = {
   4.132      "event-monitor": xm_event_monitor,
   4.133      # console commands
   4.134      "console": xm_console,
   4.135 +    "vncviewer": xm_vncviewer,
   4.136      # xenstat commands
   4.137      "top": xm_top,
   4.138      # domain commands