21038378 Nova VNC console not accessible in multi-node and multi-network configuration
21197115 Potential startup race conditition in zone console SMF method zone-vnc-console
20931076 nova doesn't build on s12-72
21197138 Nova VNC console should not allow popup menu access
20662640 console should show scrollbar and provide longer scrollback
--- a/components/openstack/nova/Makefile Fri Jul 10 10:09:35 2015 -0700
+++ b/components/openstack/nova/Makefile Fri Jul 10 15:45:53 2015 -0700
@@ -115,6 +115,5 @@
REQUIRED_PACKAGES += web/novnc
REQUIRED_PACKAGES += x11/diagnostic/x11-info-clients
REQUIRED_PACKAGES += x11/modeline-utilities
-REQUIRED_PACKAGES += x11/server/xorg
REQUIRED_PACKAGES += x11/server/xvnc
REQUIRED_PACKAGES += x11/x11-server-utilities
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/components/openstack/nova/files/Xresources Fri Jul 10 15:45:53 2015 -0700
@@ -0,0 +1,17 @@
+! WARNING: THIS FILE IS INSTALLED BY pkg(1)
+! DO NOT EDIT THIS FILE. EDITS WILL BE LOST.
+! Override xterm(1) defaults appropriately for zone VNC console
+! scrollback buffer
+xterm*saveLines: 1000
+! double-click to select whole URLs
+xterm*charClass: 33:48,36-47:48,58-59:48,61:48,63-64:48,95:48,126:48
+! fix that annoying input method error message on startup
+xterm.VT100.openIm: false
+! display right scroll bar
+xterm.VT100.scrollBar: true
+xterm.VT100.rightScrollBar: true
+! prevent xterm popup menu access from Ctrl + mouse buttons 1-3
+xterm.VT100.translations: #override \
+ Ctrl <Btn1Down>: ignore() \n\
+ Ctrl <Btn2Down>: ignore() \n\
+ Ctrl <Btn3Down>: ignore()
--- a/components/openstack/nova/files/solariszones/driver.py Fri Jul 10 10:09:35 2015 -0700
+++ b/components/openstack/nova/files/solariszones/driver.py Fri Jul 10 15:45:53 2015 -0700
@@ -38,7 +38,7 @@
from eventlet import greenthread
from lxml import etree
-from oslo.config import cfg
+from oslo_config import cfg
from nova.compute import power_state
from nova.compute import task_states
@@ -76,6 +76,7 @@
CONF = cfg.CONF
CONF.register_opts(solariszones_opts)
+CONF.import_opt('vncserver_proxyclient_address', 'nova.vnc')
LOG = logging.getLogger(__name__)
# These should match the strings returned by the zone_state_str()
@@ -1220,7 +1221,8 @@
# TODO(npower): investigate using RAD instead of CLI invocation
try:
out, err = utils.execute('/usr/sbin/svccfg', '-s',
- VNC_CONSOLE_BASE_FMRI, 'delete', name)
+ VNC_CONSOLE_BASE_FMRI, 'delete', '-f',
+ name)
except processutils.ProcessExecutionError as err:
if not self._has_vnc_console_service(instance):
LOG.debug(_("Ignoring attempt to delete a non-existent zone "
@@ -1240,11 +1242,18 @@
console_fmri = VNC_CONSOLE_BASE_FMRI + ':' + name
# TODO(npower): investigate using RAD instead of CLI invocation
try:
+ # The console SMF service exits with SMF_TEMP_DISABLE to prevent
+ # unnecessarily coming online at boot. Tell it to really bring
+ # it online.
+ out, err = utils.execute('/usr/sbin/svccfg', '-s', console_fmri,
+ 'setprop', 'vnc/nova-enabled=true')
+ out, err = utils.execute('/usr/sbin/svccfg', '-s', console_fmri,
+ 'refresh')
out, err = utils.execute('/usr/sbin/svcadm', 'enable',
console_fmri)
except processutils.ProcessExecutionError as err:
if not self._has_vnc_console_service(instance):
- LOG.error(_("Ignoring attempt to enable a non-existent zone "
+ LOG.debug(_("Ignoring attempt to enable a non-existent zone "
"VNC console SMF service for instance '%s'")
% name)
LOG.error(_("Unable to start zone VNC console SMF service "
@@ -1273,6 +1282,19 @@
LOG.error(_("Error querying state of zone VNC console SMF "
"service '%s': %s") % (console_fmri, err))
raise
+ # TODO(npower): investigate using RAD instead of CLI invocation
+ try:
+ # The console SMF service exits with SMF_TEMP_DISABLE to prevent
+ # unnecessarily coming online at boot. Make that happen.
+ out, err = utils.execute('/usr/sbin/svccfg', '-s', console_fmri,
+ 'setprop', 'vnc/nova-enabled=false')
+ out, err = utils.execute('/usr/sbin/svccfg', '-s', console_fmri,
+ 'refresh')
+ except processutils.ProcessExecutionError as err:
+ LOG.error(_("Unable to update 'vnc/nova-enabled' property for "
+ "zone VNC console SMF service "
+ "'%s': %s") % (console_fmri, err))
+ raise
def _disable_vnc_console_service(self, instance):
"""Disable a zone VNC console SMF service"""
@@ -1677,11 +1699,12 @@
"'%s': %s" % (console_fmri, err)))
raise
+ host = CONF.vncserver_proxyclient_address
try:
out, err = utils.execute('/usr/bin/svcprop', '-p', 'vnc/port',
console_fmri)
port = int(out.strip())
- return ctype.ConsoleVNC(host='127.0.0.1',
+ return ctype.ConsoleVNC(host=host,
port=port,
internal_access_path=None)
except processutils.ProcessExecutionError as err:
--- a/components/openstack/nova/files/zone-vnc-console Fri Jul 10 10:09:35 2015 -0700
+++ b/components/openstack/nova/files/zone-vnc-console Fri Jul 10 15:45:53 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()
--- a/components/openstack/nova/files/zone-vnc-console.xml Fri Jul 10 10:09:35 2015 -0700
+++ b/components/openstack/nova/files/zone-vnc-console.xml Fri Jul 10 15:45:53 2015 -0700
@@ -70,6 +70,8 @@
override='true'/>
<propval name='port' type='integer' value='0'
override='true'/>
+ <propval name='nova-enabled' type='boolean' value='false'
+ override='true'/>
<propval name='action_authorization' type='astring'
value='solaris.smf.manage.nova' />
<propval name='value_authorization' type='astring'
--- a/components/openstack/nova/nova.p5m Fri Jul 10 10:09:35 2015 -0700
+++ b/components/openstack/nova/nova.p5m Fri Jul 10 15:45:53 2015 -0700
@@ -850,6 +850,7 @@
file path=usr/lib/python$(PYVER)/vendor-packages/nova/weights.py
file path=usr/lib/python$(PYVER)/vendor-packages/nova/wsgi.py
dir path=var/lib/nova owner=nova group=nova mode=0700
+file files/Xresources path=var/lib/nova/.Xresources owner=nova group=nova
#
group groupname=nova gid=85
user username=nova ftpuser=false gcos-field="OpenStack Nova" group=nova \