components/openstack/nova/files/zone-vnc-console
changeset 3998 5bd484384122
parent 3652 7e731a1b0b39
child 4046 47a996abe340
--- a/components/openstack/nova/files/zone-vnc-console	Fri Mar 20 03:13:26 2015 -0700
+++ b/components/openstack/nova/files/zone-vnc-console	Thu Mar 19 14:41:20 2015 -0700
@@ -1,23 +1,43 @@
 #!/usr/bin/python2.6
 
+# Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
 import errno
 import os
 import pwd
 import smf_include
 import subprocess
 import sys
+import time
 
 from subprocess import CalledProcessError, check_call, Popen
 from tempfile import mkstemp
 
+GTF = "/usr/bin/gtf"
 SVCCFG = "/usr/sbin/svccfg"
 SVCPROP = "/usr/bin/svcprop"
 VNCSERVER = "/usr/bin/vncserver"
+XRANDR = "/usr/bin/xrandr"
 XSTARTUPHDR = "# WARNING: THIS FILE GENERATED BY SMF.\n" + \
               "#   DO NOT EDIT THIS FILE.  EDITS WILL BE LOST.\n"
 XTERM = "/usr/bin/xterm"
-# Monospsce font, point size 15, white foreground on black background"
-XTERMOPTS = ' -fa Monospace -fs 15 -fg white -bg black'
+# Borderless, Monospsce font, point size 14, white foreground on black
+# background are reasonable defaults.
+XTERMOPTS = ' -b 0 -fa Monospace -fs 14 -fg white -bg black -title ' + \
+            '"Zone Console: $ZONENAME"'
+XWININFO = "/usr/bin/xwininfo"
 # Enclose command in comments to prevent xterm consuming zlogin opts
 ZLOGINOPTS = ' -e "/usr/bin/pfexec /usr/sbin/zlogin -C -E $ZONENAME"\n'
 XSTARTUP = XSTARTUPHDR + XTERM + XTERMOPTS + ZLOGINOPTS
@@ -38,8 +58,8 @@
         desktop_name = zonename + ' console'
         # NOTE: 'geometry' below is that which matches the size of standard
         # 80 character undecorated xterm window using font style specified in
-        # XTERMOPTS. Update this geometry whenever XTERMOPTS are changed.
-        # Avoids exposing X root window within noVNC canvas widget.
+        # XTERMOPTS. The geometry doesn't matter too much because the display
+        # will be resized using xrandr once the xterm geometry is established.
         cmd = [VNCSERVER, "-name", desktop_name, "-SecurityTypes=None",
                "-geometry", "964x580", "-localhost", "-autokill"]
         vnc = Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
@@ -72,6 +92,8 @@
             if retcode != 0:
                 print "Error updating 'vnc/port' property: " + err
                 return smf_include.SMF_EXIT_ERR_FATAL
+    resize_xserver(display, zonename)
+
     return smf_include.SMF_EXIT_OK
 
 
@@ -135,6 +157,121 @@
     os.rename(stemp[1], xstartup_path)
 
 
+def resize_xserver(display, zonename):
+    """ Try to determine xterm window geometry and resize the Xvnc display
+        to match using XRANDR. Treat failure as non-fatal since an
+        incorrectly sized console is arguably better than none.
+    """
+    class UnmappedWindowError(Exception):
+        pass
+
+    def _get_window_geometry(display, windowname):
+        """ Find the xterm xwindow by name/title and extract its geometry
+            Returns: tuple of window [width, height]
+            Raises: UnmappedWindowError if window is not viewable/unmapped
+        """
+        cmd = [XWININFO, '-d', display, '-name', windowname]
+        xwininfo = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+                                    stderr=subprocess.PIPE)
+        out, err = xwininfo.communicate()
+        retcode = xwininfo.wait()
+        if retcode != 0:
+            print "Error finding console xwindow info: " + err
+            return
+
+        width = None
+        height = None
+        mapped = False
+        for line in out.splitlines():
+            line = line.strip()
+            if line.startswith("Map State:"):
+                if line.split()[-1] != "IsViewable":
+                    # Window is not mapped yet.
+                    raise UnmappedWindowError
+                else:
+                    mapped = True
+            if line.startswith("Width:"):
+                width = int(line.split()[1])
+            elif line.startswith("Height:"):
+                height = int(line.split()[1])
+            if width and height and mapped:
+                return [width, height]
+        else:
+            # What, no width and height???
+            print "No window geometry info returned by " + XWINFINFO
+            raise UnmappedWindowError
+
+    retries = 5
+    width = 0
+    height = 0
+    for tries in range(retries):
+        try:
+            width, height = _get_window_geometry(display,
+                                                 'Zone Console: ' + zonename)
+            print "Discovered xterm geometry: %d x %d" % (width, height)
+            break
+        except UnmappedWindowError:
+            if tries < retries:
+                print "Discovered xterm not mapped yet. Retrying in 0.5s"
+                time.sleep(0.5)
+                continue
+            else:
+                print "Discovered xterm window is taking too long to map"
+                return
+    else:
+        print "Too many failed attempts to discover xterm window geometry"
+        return
+
+    # Generate a mode line for width and height, with a refresh of 60.0Hz
+    cmd = [GTF, str(width), str(height), '60.0', '-x']
+    gtf = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+                           stderr=subprocess.PIPE)
+    out, err = gtf.communicate()
+    retcode = gtf.wait()
+    if retcode != 0:
+        print "Error creating new modeline for VNC display: " + err
+        return
+
+    for line in out.splitlines():
+        line = line.strip()
+        if line.startswith('Modeline'):
+            modeline = line.split('Modeline')[1]
+            print "New optimal modeline for Xvnc server: " + modeline
+            mode = modeline.split()
+            break
+
+    # Create a new mode for the Xvnc server using the modeline generated by gtf
+    cmd = [XRANDR, '-d', display, '--newmode']
+    cmd.extend(mode)
+    newmode = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+                               stderr=subprocess.PIPE)
+    out, err = newmode.communicate()
+    retcode = newmode.wait()
+    if retcode != 0:
+        print "Error creating new xrandr modeline for VNC display: " + err
+        return
+
+    # Add the new mode to the default display output
+    modename = mode[0]
+    cmd = [XRANDR, '-d', display, '--addmode', 'default', modename]
+    addmode = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+                               stderr=subprocess.PIPE)
+    out, err = addmode.communicate()
+    retcode = addmode.wait()
+    if retcode != 0:
+        print "Error adding new xrandr modeline for VNC display: " + err
+        return
+
+    # Activate the new mode on the default display output
+    cmd = [XRANDR, '-d', display, '--output', 'default', '--mode', modename]
+    addmode = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+                               stderr=subprocess.PIPE)
+    out, err = addmode.communicate()
+    retcode = addmode.wait()
+    if retcode != 0:
+        print "Error setting new xrandr modeline for VNC display: " + err
+        return
+
 if __name__ == "__main__":
     os.putenv("LC_ALL", "C")
     smf_include.smf_main()