--- a/components/openstack/nova/files/zone-vnc-console Thu Jun 25 08:10:54 2015 -0700
+++ b/components/openstack/nova/files/zone-vnc-console Thu Jun 25 15:41:31 2015 -0700
@@ -14,22 +14,28 @@
# License for the specific language governing permissions and limitations
# under the License.
+import ConfigParser
+import contextlib
import errno
+import fcntl
import os
import pwd
import smf_include
+import socket
import subprocess
import sys
+import tempfile
import time
-from subprocess import CalledProcessError, check_call, Popen
-from tempfile import mkstemp
+from oslo_config import cfg
GTF = "/usr/bin/gtf"
+SVCADM = "/usr/sbin/svcadm"
SVCCFG = "/usr/sbin/svccfg"
SVCPROP = "/usr/bin/svcprop"
VNCSERVER = "/usr/bin/vncserver"
XRANDR = "/usr/bin/xrandr"
+NOVACFG = "/etc/nova/nova.conf"
XSTARTUPHDR = "# WARNING: THIS FILE GENERATED BY SMF.\n" + \
"# DO NOT EDIT THIS FILE. EDITS WILL BE LOST.\n"
XRESOURCES = "[[ -f ~/.Xresources ]] && /usr/bin/xrdb -merge ~/.Xresources\n"
@@ -39,34 +45,80 @@
XTERMOPTS = ' -b 0 -fa Monospace -fs 14 -fg white -bg black -title ' + \
'"Zone Console: $ZONENAME"'
XWININFO = "/usr/bin/xwininfo"
+
+# Port ranges allocated for VNC and X11 sockets.
+VNCPORT_START = 5900
+VNCPORT_END = 5999
+X11PORT_START = 6000
+
# Enclose command in comments to prevent xterm consuming zlogin opts
ZLOGINOPTS = ' -e "/usr/bin/pfexec /usr/sbin/zlogin -C -E $ZONENAME"\n'
XSTARTUP = XSTARTUPHDR + XRESOURCES + XTERM + XTERMOPTS + ZLOGINOPTS
+CONF = cfg.CONF
+CONF.import_opt('vncserver_listen', 'nova.vnc')
+
def start():
+ fmri = os.environ['SMF_FMRI']
+ # This is meant to be an on-demand service.
+ # Determine if nova-compute requested enablement of this instance.
+ # Exit with SMF_EXIT_TEMP_DISABLE if not true.
+ cmd = [SVCPROP, '-p', 'vnc/nova-enabled', fmri]
+ svcprop = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out, err = svcprop.communicate()
+ retcode = svcprop.wait()
+ if retcode != 0:
+ print "Error reading 'vnc/nova-enabled' property: " + err
+ return smf_include.SMF_EXIT_ERR_FATAL
+ enabled = out.strip() == 'true'
+ if not enabled:
+ smf_include.smf_method_exit(
+ smf_include.SMF_EXIT_TEMP_DISABLE,
+ "nova_enabled",
+ "nova-compute starts this service on demand")
+
check_vncserver()
homedir = os.environ.get('HOME')
if not homedir:
homedir = pwd.getpwuid(os.getuid()).pw_dir
os.putenv("HOME", homedir)
set_xstartup(homedir)
+ display = None
+ vncport = None
try:
- fmri = os.environ['SMF_FMRI']
zonename = fmri.rsplit(':', 1)[1]
os.putenv("ZONENAME", zonename)
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. 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,
- env=None)
- out, err = vnc.communicate()
- vncret = vnc.wait()
+ novacfg = ConfigParser.RawConfigParser()
+ novacfg.readfp(open(NOVACFG))
+ try:
+ vnc_listenip = novacfg.get("DEFAULT", "vncserver_listen")
+ except ConfigParser.NoOptionError:
+ vnc_listenip = CONF.vncserver_listen
+
+ with lock_available_port(vnc_listenip, VNCPORT_START, VNCPORT_END,
+ homedir) as n:
+ # NOTE: 'geometry' is that which matches the size of standard
+ # 80 character undecorated xterm window using font style specified
+ # in XTERMOPTS. The geometry doesn't matter too much because the
+ # display will be resized using xrandr once the xterm geometry is
+ # established.
+ display = ":%d" % n
+ cmd = [VNCSERVER, display, "-name", desktop_name,
+ "-SecurityTypes=None", "-geometry", "964x580",
+ "-interface", vnc_listenip, "-autokill"]
+
+ vncport = VNCPORT_START + n
+ x11port = X11PORT_START + n
+ print "Using VNC server port: " + str(vncport)
+ print "Using X11 server port: %d, display %s" % (x11port, display)
+ vnc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE, env=None)
+ out, err = vnc.communicate()
+ vncret = vnc.wait()
if vncret != 0:
print "Error starting VNC server: " + err
return smf_include.SMF_EXIT_ERR_FATAL
@@ -74,25 +126,17 @@
print e
return smf_include.SMF_EXIT_ERR_FATAL
- output = err.splitlines()
- for line in output:
- if line.startswith("New '%s' desktop is" % desktop_name):
- display = line.rpartition(' ')[2]
- host, display_num = display.split(':', 1)
- # set host prop
- port = 5900 + int(display_num)
- print "VNC port: %d" % port
- # set port num prop
- cmd = [SVCCFG, '-s', fmri, 'setprop', 'vnc/port', '=', 'integer:',
- str(port)]
+ # set SMF instance port num prop
+ cmd = [SVCCFG, '-s', fmri, 'setprop', 'vnc/port', '=', 'integer:',
+ str(vncport)]
- svccfg = subprocess.Popen(cmd, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- out, err = svccfg.communicate()
- retcode = svccfg.wait()
- if retcode != 0:
- print "Error updating 'vnc/port' property: " + err
- return smf_include.SMF_EXIT_ERR_FATAL
+ svccfg = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out, err = svccfg.communicate()
+ retcode = svccfg.wait()
+ 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
@@ -101,8 +145,8 @@
def stop():
try:
# first kill the SMF contract
- check_call(["/usr/bin/pkill", "-c", sys.argv[2]])
- except CalledProcessError as cpe:
+ subprocess.check_call(["/usr/bin/pkill", "-c", sys.argv[2]])
+ except subprocess.CalledProcessError as cpe:
# 1 is returncode if no SMF contract processes were matched,
# meaning they have already terminated.
if cpe.returncode != 1:
@@ -151,7 +195,7 @@
# Always clobber xstartup
# stemp tuple = [fd, path]
- stemp = mkstemp(dir=vncdir)
+ stemp = tempfile.mkstemp(dir=vncdir)
os.write(stemp[0], XSTARTUP)
os.close(stemp[0])
os.chmod(stemp[1], 0700)
@@ -293,6 +337,64 @@
print "Error setting new xrandr modeline for VNC display: " + err
return
+
[email protected]
+def lock_available_port(address, port_start, port_end, lockdir):
+ """Ensures instance exclusive use of VNC, X11 service ports
+ and related resources.
+ Generator yields an integer of the port relative to port_start to use.
+ eg. 32: VNC port 5932, X11 port 6032, X11 display :32
+ lockfile is port specific and prevents multiple instances from
+ attempting to use the same port number during SMF start method
+ execution.
+ Socket binding on address:port establishes that the port is not
+ already in use by another Xvnc process
+ """
+ for n in range(port_end - port_start):
+ vncport = port_start + n
+ x11port = X11PORT_START + n
+ lockfile = os.path.join(lockdir, '.port-%d.lock' % vncport)
+ try:
+ # Acquire port file lock first to lock out other instances trying
+ # to come online in parallel. They will grab the next available
+ # port lock.
+ lock = open(lockfile, 'w')
+ fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
+
+ try:
+ # Check the VNC/RFB and X11 ports.
+ for testport in [vncport, x11port]:
+ sock = socket.socket(socket.AF_INET)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ try:
+ sock.bind((address, testport))
+ finally:
+ sock.close()
+
+ # Ensure the standard X11 locking files are not present
+ # /tmp/.X<n>-lock
+ # /tmp/X11-unix/X<n>
+ xfiles = ['/tmp/.X%d-lock' % n,
+ '/tmp/X11-unix/X%d' % n]
+ for xfile in xfiles:
+ if os.path.exists(xfile):
+ print ("Warning: X11 display :{0} is taken because of "
+ "{1}\nRemove this file if there is no X "
+ "server on display :{0}".format(str(n), xfile))
+ raise Exception
+
+ except (socket.error, Exception):
+ lock.close()
+ os.remove(lockfile)
+ continue
+ # Yay, we found a free VNC/X11 port pair.
+ yield n
+ lock.close()
+ os.remove(lockfile)
+ break
+ except IOError:
+ print "Port %d already reserved, skipping" % vncport
+
if __name__ == "__main__":
os.putenv("LC_ALL", "C")
smf_include.smf_main()