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 |
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() |