components/openstack/nova/files/zone-vnc-console
branchs11-update
changeset 4628 21e8147a2b1e
parent 4625 18adb92d4193
equal deleted inserted replaced
4627:2101fdb9d9aa 4628:21e8147a2b1e
    12 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    12 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    13 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    13 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    14 #    License for the specific language governing permissions and limitations
    14 #    License for the specific language governing permissions and limitations
    15 #    under the License.
    15 #    under the License.
    16 
    16 
       
    17 import ConfigParser
       
    18 import contextlib
    17 import errno
    19 import errno
       
    20 import fcntl
    18 import os
    21 import os
    19 import pwd
    22 import pwd
    20 import smf_include
    23 import smf_include
       
    24 import socket
    21 import subprocess
    25 import subprocess
    22 import sys
    26 import sys
       
    27 import tempfile
    23 import time
    28 import time
    24 
    29 
    25 from subprocess import CalledProcessError, check_call, Popen
    30 from oslo_config import cfg
    26 from tempfile import mkstemp
       
    27 
    31 
    28 GTF = "/usr/bin/gtf"
    32 GTF = "/usr/bin/gtf"
       
    33 SVCADM = "/usr/sbin/svcadm"
    29 SVCCFG = "/usr/sbin/svccfg"
    34 SVCCFG = "/usr/sbin/svccfg"
    30 SVCPROP = "/usr/bin/svcprop"
    35 SVCPROP = "/usr/bin/svcprop"
    31 VNCSERVER = "/usr/bin/vncserver"
    36 VNCSERVER = "/usr/bin/vncserver"
    32 XRANDR = "/usr/bin/xrandr"
    37 XRANDR = "/usr/bin/xrandr"
       
    38 NOVACFG = "/etc/nova/nova.conf"
    33 XSTARTUPHDR = "# WARNING: THIS FILE GENERATED BY SMF.\n" + \
    39 XSTARTUPHDR = "# WARNING: THIS FILE GENERATED BY SMF.\n" + \
    34               "#   DO NOT EDIT THIS FILE.  EDITS WILL BE LOST.\n"
    40               "#   DO NOT EDIT THIS FILE.  EDITS WILL BE LOST.\n"
    35 XRESOURCES = "[[ -f ~/.Xresources ]] && /usr/bin/xrdb -merge ~/.Xresources\n"
    41 XRESOURCES = "[[ -f ~/.Xresources ]] && /usr/bin/xrdb -merge ~/.Xresources\n"
    36 XTERM = "/usr/bin/xterm"
    42 XTERM = "/usr/bin/xterm"
    37 # Borderless, Monospsce font, point size 14, white foreground on black
    43 # Borderless, Monospsce font, point size 14, white foreground on black
    38 # background are reasonable defaults.
    44 # background are reasonable defaults.
    39 XTERMOPTS = ' -b 0 -fa Monospace -fs 14 -fg white -bg black -title ' + \
    45 XTERMOPTS = ' -b 0 -fa Monospace -fs 14 -fg white -bg black -title ' + \
    40             '"Zone Console: $ZONENAME"'
    46             '"Zone Console: $ZONENAME"'
    41 XWININFO = "/usr/bin/xwininfo"
    47 XWININFO = "/usr/bin/xwininfo"
       
    48 
       
    49 # Port ranges allocated for VNC and X11 sockets.
       
    50 VNCPORT_START = 5900
       
    51 VNCPORT_END = 5999
       
    52 X11PORT_START = 6000
       
    53 
    42 # Enclose command in comments to prevent xterm consuming zlogin opts
    54 # Enclose command in comments to prevent xterm consuming zlogin opts
    43 ZLOGINOPTS = ' -e "/usr/bin/pfexec /usr/sbin/zlogin -C -E $ZONENAME"\n'
    55 ZLOGINOPTS = ' -e "/usr/bin/pfexec /usr/sbin/zlogin -C -E $ZONENAME"\n'
    44 XSTARTUP = XSTARTUPHDR + XRESOURCES + XTERM + XTERMOPTS + ZLOGINOPTS
    56 XSTARTUP = XSTARTUPHDR + XRESOURCES + XTERM + XTERMOPTS + ZLOGINOPTS
    45 
    57 
       
    58 CONF = cfg.CONF
       
    59 CONF.import_opt('vncserver_listen', 'nova.vnc')
       
    60 
    46 
    61 
    47 def start():
    62 def start():
       
    63     fmri = os.environ['SMF_FMRI']
       
    64     # This is meant to be an on-demand service.
       
    65     # Determine if nova-compute requested enablement of this instance.
       
    66     # Exit with SMF_EXIT_TEMP_DISABLE if not true.
       
    67     cmd = [SVCPROP, '-p', 'vnc/nova-enabled', fmri]
       
    68     svcprop = subprocess.Popen(cmd, stdout=subprocess.PIPE,
       
    69                                stderr=subprocess.PIPE)
       
    70     out, err = svcprop.communicate()
       
    71     retcode = svcprop.wait()
       
    72     if retcode != 0:
       
    73         print "Error reading 'vnc/nova-enabled' property: " + err
       
    74         return smf_include.SMF_EXIT_ERR_FATAL
       
    75     enabled = out.strip() == 'true'
       
    76     if not enabled:
       
    77         smf_include.smf_method_exit(
       
    78             smf_include.SMF_EXIT_TEMP_DISABLE,
       
    79             "nova_enabled",
       
    80             "nova-compute starts this service on demand")
       
    81 
    48     check_vncserver()
    82     check_vncserver()
    49     homedir = os.environ.get('HOME')
    83     homedir = os.environ.get('HOME')
    50     if not homedir:
    84     if not homedir:
    51         homedir = pwd.getpwuid(os.getuid()).pw_dir
    85         homedir = pwd.getpwuid(os.getuid()).pw_dir
    52         os.putenv("HOME", homedir)
    86         os.putenv("HOME", homedir)
    53     set_xstartup(homedir)
    87     set_xstartup(homedir)
       
    88     display = None
       
    89     vncport = None
    54 
    90 
    55     try:
    91     try:
    56         fmri = os.environ['SMF_FMRI']
       
    57         zonename = fmri.rsplit(':', 1)[1]
    92         zonename = fmri.rsplit(':', 1)[1]
    58         os.putenv("ZONENAME", zonename)
    93         os.putenv("ZONENAME", zonename)
    59         desktop_name = zonename + ' console'
    94         desktop_name = zonename + ' console'
    60         # NOTE: 'geometry' below is that which matches the size of standard
    95         novacfg = ConfigParser.RawConfigParser()
    61         # 80 character undecorated xterm window using font style specified in
    96         novacfg.readfp(open(NOVACFG))
    62         # XTERMOPTS. The geometry doesn't matter too much because the display
    97         try:
    63         # will be resized using xrandr once the xterm geometry is established.
    98             vnc_listenip = novacfg.get("DEFAULT", "vncserver_listen")
    64         cmd = [VNCSERVER, "-name", desktop_name, "-SecurityTypes=None",
    99         except ConfigParser.NoOptionError:
    65                "-geometry", "964x580", "-localhost", "-autokill"]
   100             vnc_listenip = CONF.vncserver_listen
    66         vnc = Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
   101 
    67                     env=None)
   102         with lock_available_port(vnc_listenip, VNCPORT_START, VNCPORT_END,
    68         out, err = vnc.communicate()
   103                                  homedir) as n:
    69         vncret = vnc.wait()
   104             # NOTE: 'geometry' is that which matches the size of standard
       
   105             # 80 character undecorated xterm window using font style specified
       
   106             # in XTERMOPTS. The geometry doesn't matter too much because the
       
   107             # display will be resized using xrandr once the xterm geometry is
       
   108             # established.
       
   109             display = ":%d" % n
       
   110             cmd = [VNCSERVER, display, "-name", desktop_name,
       
   111                    "-SecurityTypes=None", "-geometry", "964x580",
       
   112                    "-interface", vnc_listenip, "-autokill"]
       
   113 
       
   114             vncport = VNCPORT_START + n
       
   115             x11port = X11PORT_START + n
       
   116             print "Using VNC server port: " + str(vncport)
       
   117             print "Using X11 server port: %d, display %s" % (x11port, display)
       
   118             vnc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
       
   119                                    stderr=subprocess.PIPE, env=None)
       
   120             out, err = vnc.communicate()
       
   121             vncret = vnc.wait()
    70         if vncret != 0:
   122         if vncret != 0:
    71             print "Error starting VNC server: " + err
   123             print "Error starting VNC server: " + err
    72             return smf_include.SMF_EXIT_ERR_FATAL
   124             return smf_include.SMF_EXIT_ERR_FATAL
    73     except Exception as e:
   125     except Exception as e:
    74         print e
   126         print e
    75         return smf_include.SMF_EXIT_ERR_FATAL
   127         return smf_include.SMF_EXIT_ERR_FATAL
    76 
   128 
    77     output = err.splitlines()
   129     # set SMF instance port num prop
    78     for line in output:
   130     cmd = [SVCCFG, '-s', fmri, 'setprop', 'vnc/port', '=', 'integer:',
    79         if line.startswith("New '%s' desktop is" % desktop_name):
   131            str(vncport)]
    80             display = line.rpartition(' ')[2]
   132 
    81             host, display_num = display.split(':', 1)
   133     svccfg = subprocess.Popen(cmd, stdout=subprocess.PIPE,
    82             # set host prop
   134                               stderr=subprocess.PIPE)
    83             port = 5900 + int(display_num)
   135     out, err = svccfg.communicate()
    84             print "VNC port: %d" % port
   136     retcode = svccfg.wait()
    85             # set port num prop
   137     if retcode != 0:
    86             cmd = [SVCCFG, '-s', fmri, 'setprop', 'vnc/port', '=', 'integer:',
   138         print "Error updating 'vnc/port' property: " + err
    87                    str(port)]
   139         return smf_include.SMF_EXIT_ERR_FATAL
    88 
       
    89             svccfg = subprocess.Popen(cmd, stdout=subprocess.PIPE,
       
    90                                       stderr=subprocess.PIPE)
       
    91             out, err = svccfg.communicate()
       
    92             retcode = svccfg.wait()
       
    93             if retcode != 0:
       
    94                 print "Error updating 'vnc/port' property: " + err
       
    95                 return smf_include.SMF_EXIT_ERR_FATAL
       
    96     resize_xserver(display, zonename)
   140     resize_xserver(display, zonename)
    97 
   141 
    98     return smf_include.SMF_EXIT_OK
   142     return smf_include.SMF_EXIT_OK
    99 
   143 
   100 
   144 
   101 def stop():
   145 def stop():
   102     try:
   146     try:
   103         # first kill the SMF contract
   147         # first kill the SMF contract
   104         check_call(["/usr/bin/pkill", "-c", sys.argv[2]])
   148         subprocess.check_call(["/usr/bin/pkill", "-c", sys.argv[2]])
   105     except CalledProcessError as cpe:
   149     except subprocess.CalledProcessError as cpe:
   106         # 1 is returncode if no SMF contract processes were matched,
   150         # 1 is returncode if no SMF contract processes were matched,
   107         # meaning they have already terminated.
   151         # meaning they have already terminated.
   108         if cpe.returncode != 1:
   152         if cpe.returncode != 1:
   109                 print "failed to kill the SMF contract: %s" % cpe
   153                 print "failed to kill the SMF contract: %s" % cpe
   110                 return smf_include.SMF_EXIT_ERR_FATAL
   154                 return smf_include.SMF_EXIT_ERR_FATAL
   149         if ose.errno != errno.EEXIST:
   193         if ose.errno != errno.EEXIST:
   150             raise
   194             raise
   151 
   195 
   152     # Always clobber xstartup
   196     # Always clobber xstartup
   153     # stemp tuple = [fd, path]
   197     # stemp tuple = [fd, path]
   154     stemp = mkstemp(dir=vncdir)
   198     stemp = tempfile.mkstemp(dir=vncdir)
   155     os.write(stemp[0], XSTARTUP)
   199     os.write(stemp[0], XSTARTUP)
   156     os.close(stemp[0])
   200     os.close(stemp[0])
   157     os.chmod(stemp[1], 0700)
   201     os.chmod(stemp[1], 0700)
   158     os.rename(stemp[1], xstartup_path)
   202     os.rename(stemp[1], xstartup_path)
   159 
   203 
   291     retcode = addmode.wait()
   335     retcode = addmode.wait()
   292     if retcode != 0:
   336     if retcode != 0:
   293         print "Error setting new xrandr modeline for VNC display: " + err
   337         print "Error setting new xrandr modeline for VNC display: " + err
   294         return
   338         return
   295 
   339 
       
   340 
       
   341 @contextlib.contextmanager
       
   342 def lock_available_port(address, port_start, port_end, lockdir):
       
   343     """Ensures instance exclusive use of VNC, X11 service ports
       
   344        and related resources.
       
   345        Generator yields an integer of the port relative to port_start to use.
       
   346        eg. 32: VNC port 5932, X11 port 6032, X11 display :32
       
   347        lockfile is port specific and prevents multiple instances from
       
   348        attempting to use the same port number during SMF start method
       
   349        execution.
       
   350        Socket binding on address:port establishes that the port is not
       
   351        already in use by another Xvnc process
       
   352     """
       
   353     for n in range(port_end - port_start):
       
   354         vncport = port_start + n
       
   355         x11port = X11PORT_START + n
       
   356         lockfile = os.path.join(lockdir, '.port-%d.lock' % vncport)
       
   357         try:
       
   358             # Acquire port file lock first to lock out other instances trying
       
   359             # to come online in parallel. They will grab the next available
       
   360             # port lock.
       
   361             lock = open(lockfile, 'w')
       
   362             fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
       
   363 
       
   364             try:
       
   365                 # Check the VNC/RFB and X11 ports.
       
   366                 for testport in [vncport, x11port]:
       
   367                     sock = socket.socket(socket.AF_INET)
       
   368                     sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
       
   369                     try:
       
   370                         sock.bind((address, testport))
       
   371                     finally:
       
   372                         sock.close()
       
   373 
       
   374                 # Ensure the standard X11 locking files are not present
       
   375                 # /tmp/.X<n>-lock
       
   376                 # /tmp/X11-unix/X<n>
       
   377                 xfiles = ['/tmp/.X%d-lock' % n,
       
   378                           '/tmp/X11-unix/X%d' % n]
       
   379                 for xfile in xfiles:
       
   380                     if os.path.exists(xfile):
       
   381                         print ("Warning: X11 display :{0} is taken because of "
       
   382                                "{1}\nRemove this file if there is no X "
       
   383                                "server on display :{0}".format(str(n), xfile))
       
   384                         raise Exception
       
   385 
       
   386             except (socket.error, Exception):
       
   387                 lock.close()
       
   388                 os.remove(lockfile)
       
   389                 continue
       
   390             # Yay, we found a free VNC/X11 port pair.
       
   391             yield n
       
   392             lock.close()
       
   393             os.remove(lockfile)
       
   394             break
       
   395         except IOError:
       
   396             print "Port %d already reserved, skipping" % vncport
       
   397 
   296 if __name__ == "__main__":
   398 if __name__ == "__main__":
   297     os.putenv("LC_ALL", "C")
   399     os.putenv("LC_ALL", "C")
   298     smf_include.smf_main()
   400     smf_include.smf_main()