summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAnthony Young <sleepsonthefloor@gmail.com>2011-03-30 05:59:13 +0000
committerTarmac <>2011-03-30 05:59:13 +0000
commitcda715bf86373d47ad2f2f6655ad26e77f264868 (patch)
tree9a711a93154ba8659e6833ef21cf2329a48c52c0
parent971be24dd38b576f4cf89a2c7d8856593b93c29d (diff)
parent7856df88b22e6ff3bd0f124e3d71f130e3e9c205 (diff)
The VNC Proxy is an OpenStack component that allows users of Nova to access
their instances through a websocket enabled browser (like Google Chrome). A VNC Connection works like so: * User connects over an api and gets a url like http://ip:port/?token=xyz * User pastes url in browser * Browser connects to VNC Proxy though a websocket enabled client like noVNC * VNC Proxy authorizes users token, maps the token to a host and port of an instance's VNC server * VNC Proxy initiates connection to VNC server, and continues proxying until the session ends For more info see vncconsole.rst
-rwxr-xr-xbin/nova-vncproxy101
-rw-r--r--doc/source/runnova/vncconsole.rst76
-rw-r--r--nova/api/ec2/cloud.py7
-rw-r--r--nova/api/openstack/servers.py12
-rw-r--r--nova/compute/api.py19
-rw-r--r--nova/compute/manager.py9
-rw-r--r--nova/tests/test_compute.py10
-rw-r--r--nova/virt/fake.py5
-rw-r--r--nova/virt/libvirt.xml.template3
-rw-r--r--nova/virt/libvirt_conn.py21
-rw-r--r--nova/vnc/__init__.py34
-rw-r--r--nova/vnc/auth.py138
-rw-r--r--nova/vnc/proxy.py131
-rw-r--r--setup.py1
14 files changed, 565 insertions, 2 deletions
diff --git a/bin/nova-vncproxy b/bin/nova-vncproxy
new file mode 100755
index 000000000..ccb97e3a3
--- /dev/null
+++ b/bin/nova-vncproxy
@@ -0,0 +1,101 @@
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2010 Openstack, LLC.
+# 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.
+
+"""VNC Console Proxy Server."""
+
+import eventlet
+import gettext
+import os
+import sys
+
+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
+ os.pardir,
+ os.pardir))
+if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
+ sys.path.insert(0, possible_topdir)
+
+gettext.install('nova', unicode=1)
+
+from nova import flags
+from nova import log as logging
+from nova import service
+from nova import utils
+from nova import wsgi
+from nova import version
+from nova.vnc import auth
+from nova.vnc import proxy
+
+
+LOG = logging.getLogger('nova.vnc-proxy')
+
+
+FLAGS = flags.FLAGS
+flags.DEFINE_string('vncproxy_wwwroot', '/var/lib/nova/noVNC/',
+ 'Full path to noVNC directory')
+flags.DEFINE_boolean('vnc_debug', False,
+ 'Enable debugging features, like token bypassing')
+flags.DEFINE_integer('vncproxy_port', 6080,
+ 'Port that the VNC proxy should bind to')
+flags.DEFINE_string('vncproxy_host', '0.0.0.0',
+ 'Address that the VNC proxy should bind to')
+flags.DEFINE_integer('vnc_token_ttl', 300,
+ 'How many seconds before deleting tokens')
+flags.DEFINE_string('vncproxy_manager', 'nova.vnc.auth.VNCProxyAuthManager',
+ 'Manager for vncproxy auth')
+
+flags.DEFINE_flag(flags.HelpFlag())
+flags.DEFINE_flag(flags.HelpshortFlag())
+flags.DEFINE_flag(flags.HelpXMLFlag())
+
+
+if __name__ == "__main__":
+ utils.default_flagfile()
+ FLAGS(sys.argv)
+ logging.setup()
+
+ LOG.audit(_("Starting nova-vnc-proxy node (version %s)"),
+ version.version_string_with_vcs())
+
+ if not (os.path.exists(FLAGS.vncproxy_wwwroot) and
+ os.path.exists(FLAGS.vncproxy_wwwroot + '/vnc_auto.html')):
+ LOG.info(_("Missing vncproxy_wwwroot (version %s)"),
+ FLAGS.vncproxy_wwwroot)
+ LOG.info(_("You need a slightly modified version of noVNC "
+ "to work with the nova-vnc-proxy"))
+ LOG.info(_("Check out the most recent nova noVNC code: %s"),
+ "git://github.com/sleepsonthefloor/noVNC.git")
+ LOG.info(_("And drop it in %s"), FLAGS.vncproxy_wwwroot)
+ exit(1)
+
+ app = proxy.WebsocketVNCProxy(FLAGS.vncproxy_wwwroot)
+
+ LOG.audit(_("Allowing access to the following files: %s"),
+ app.get_whitelist())
+
+ with_logging = auth.LoggingMiddleware(app)
+
+ if FLAGS.vnc_debug:
+ with_auth = proxy.DebugMiddleware(with_logging)
+ else:
+ with_auth = auth.VNCNovaAuthMiddleware(with_logging)
+
+ service.serve()
+
+ server = wsgi.Server()
+ server.start(with_auth, FLAGS.vncproxy_port, host=FLAGS.vncproxy_host)
+ server.wait()
diff --git a/doc/source/runnova/vncconsole.rst b/doc/source/runnova/vncconsole.rst
new file mode 100644
index 000000000..c1fe9be39
--- /dev/null
+++ b/doc/source/runnova/vncconsole.rst
@@ -0,0 +1,76 @@
+..
+ Copyright 2010-2011 United States Government as represented by the
+ Administrator of the National Aeronautics and Space Administration.
+ 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.
+
+Getting Started with the VNC Proxy
+==================================
+
+The VNC Proxy is an OpenStack component that allows users of Nova to access
+their instances through a websocket enabled browser (like Google Chrome).
+
+A VNC Connection works like so:
+
+* User connects over an api and gets a url like http://ip:port/?token=xyz
+* User pastes url in browser
+* Browser connects to VNC Proxy though a websocket enabled client like noVNC
+* VNC Proxy authorizes users token, maps the token to a host and port of an
+ instance's VNC server
+* VNC Proxy initiates connection to VNC server, and continues proxying until
+ the session ends
+
+
+Configuring the VNC Proxy
+-------------------------
+nova-vncproxy requires a websocket enabled html client to work properly. At
+this time, the only tested client is a slightly modified fork of noVNC, which
+you can at find http://github.com/openstack/noVNC.git
+
+.. todo:: add instruction for installing from package
+
+noVNC must be in the location specified by --vncproxy_wwwroot, which defaults
+to /var/lib/nova/noVNC. nova-vncproxy will fail to launch until this code
+is properly installed.
+
+By default, nova-vncproxy binds 0.0.0.0:6080. This can be configured with:
+
+* --vncproxy_port=[port]
+* --vncproxy_host=[host]
+
+
+Enabling VNC Consoles in Nova
+-----------------------------
+At the moment, VNC support is supported only when using libvirt. To enable VNC
+Console, configure the following flags:
+
+* --vnc_console_proxy_url=http://[proxy_host]:[proxy_port] - proxy_port
+ defaults to 6080. This url must point to nova-vncproxy
+* --vnc_enabled=[True|False] - defaults to True. If this flag is not set your
+ instances will launch without vnc support.
+
+
+Getting an instance's VNC Console
+---------------------------------
+You can access an instance's VNC Console url in the following methods:
+
+* Using the direct api:
+ eg: 'stack --user=admin --project=admin compute get_vnc_console instance_id=1'
+* Support for Dashboard, and the Openstack API will be forthcoming
+
+
+Accessing VNC Consoles without a web browser
+--------------------------------------------
+At the moment, VNC Consoles are only supported through the web browser, but
+more general VNC support is in the works.
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 9e34d3317..7ba8dfbea 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -536,6 +536,13 @@ class CloudController(object):
return self.compute_api.get_ajax_console(context,
instance_id=instance_id)
+ def get_vnc_console(self, context, instance_id, **kwargs):
+ """Returns vnc browser url. Used by OS dashboard."""
+ ec2_id = instance_id
+ instance_id = ec2utils.ec2_id_to_id(ec2_id)
+ return self.compute_api.get_vnc_console(context,
+ instance_id=instance_id)
+
def describe_volumes(self, context, volume_id=None, **kwargs):
if volume_id:
volumes = []
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index 75a305a14..6bd173bb8 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -477,7 +477,7 @@ class Controller(wsgi.Controller):
@scheduler_api.redirect_handler
def get_ajax_console(self, req, id):
- """ Returns a url to an instance's ajaxterm console. """
+ """Returns a url to an instance's ajaxterm console."""
try:
self.compute_api.get_ajax_console(req.environ['nova.context'],
int(id))
@@ -486,6 +486,16 @@ class Controller(wsgi.Controller):
return exc.HTTPAccepted()
@scheduler_api.redirect_handler
+ def get_vnc_console(self, req, id):
+ """Returns a url to an instance's ajaxterm console."""
+ try:
+ self.compute_api.get_vnc_console(req.environ['nova.context'],
+ int(id))
+ except exception.NotFound:
+ return faults.Fault(exc.HTTPNotFound())
+ return exc.HTTPAccepted()
+
+ @scheduler_api.redirect_handler
def diagnostics(self, req, id):
"""Permit Admins to retrieve server diagnostics."""
ctxt = req.environ["nova.context"]
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 266cbe677..1dbd73f8f 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -608,6 +608,25 @@ class API(base.Base):
return {'url': '%s/?token=%s' % (FLAGS.ajax_console_proxy_url,
output['token'])}
+ def get_vnc_console(self, context, instance_id):
+ """Get a url to a VNC Console."""
+ instance = self.get(context, instance_id)
+ output = self._call_compute_message('get_vnc_console',
+ context,
+ instance_id)
+ rpc.call(context, '%s' % FLAGS.vncproxy_topic,
+ {'method': 'authorize_vnc_console',
+ 'args': {'token': output['token'],
+ 'host': output['host'],
+ 'port': output['port']}})
+
+ # hostignore and portignore are compatability params for noVNC
+ return {'url': '%s/vnc_auto.html?token=%s&host=%s&port=%s' % (
+ FLAGS.vncproxy_url,
+ output['token'],
+ 'hostignore',
+ 'portignore')}
+
def get_console_output(self, context, instance_id):
"""Get console output for an an instance"""
return self._call_compute_message('get_console_output',
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index e0a5e2b3f..08b772517 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -723,6 +723,15 @@ class ComputeManager(manager.SchedulerDependentManager):
return self.driver.get_ajax_console(instance_ref)
+ @exception.wrap_exception
+ def get_vnc_console(self, context, instance_id):
+ """Return connection information for an vnc console."""
+ context = context.elevated()
+ LOG.debug(_("instance %s: getting vnc console"), instance_id)
+ instance_ref = self.db.instance_get(context, instance_id)
+
+ return self.driver.get_vnc_console(instance_ref)
+
@checks_instance_lock
def attach_volume(self, context, instance_id, volume_id, mountpoint):
"""Attach a volume to an instance."""
diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py
index d1ef68de4..1b0f426d2 100644
--- a/nova/tests/test_compute.py
+++ b/nova/tests/test_compute.py
@@ -286,6 +286,16 @@ class ComputeTestCase(test.TestCase):
console = self.compute.get_ajax_console(self.context,
instance_id)
+ self.assert_(set(['token', 'host', 'port']).issubset(console.keys()))
+ self.compute.terminate_instance(self.context, instance_id)
+
+ def test_vnc_console(self):
+ """Make sure we can a vnc console for an instance."""
+ instance_id = self._create_instance()
+ self.compute.run_instance(self.context, instance_id)
+
+ console = self.compute.get_vnc_console(self.context,
+ instance_id)
self.assert_(console)
self.compute.terminate_instance(self.context, instance_id)
diff --git a/nova/virt/fake.py b/nova/virt/fake.py
index 030f0a2fa..c3d5230df 100644
--- a/nova/virt/fake.py
+++ b/nova/virt/fake.py
@@ -375,6 +375,11 @@ class FakeConnection(driver.ComputeDriver):
'host': 'fakeajaxconsole.com',
'port': 6969}
+ def get_vnc_console(self, instance):
+ return {'token': 'FAKETOKEN',
+ 'host': 'fakevncconsole.com',
+ 'port': 6969}
+
def get_console_pool_info(self, console_type):
return {'address': '127.0.0.1',
'username': 'fakeuser',
diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template
index 36d18ed95..de2497a76 100644
--- a/nova/virt/libvirt.xml.template
+++ b/nova/virt/libvirt.xml.template
@@ -115,5 +115,8 @@
<target port='0'/>
</serial>
+#if $getVar('vncserver_host', False)
+ <graphics type='vnc' port='-1' autoport='yes' keymap='en-us' listen='${vncserver_host}'/>
+#end if
</devices>
</domain>
diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py
index c144e827e..b28584cb6 100644
--- a/nova/virt/libvirt_conn.py
+++ b/nova/virt/libvirt_conn.py
@@ -60,6 +60,7 @@ from nova import flags
from nova import log as logging
#from nova import test
from nova import utils
+from nova import vnc
from nova.auth import manager
from nova.compute import instance_types
from nova.compute import power_state
@@ -675,7 +676,23 @@ class LibvirtConnection(driver.ComputeDriver):
subprocess.Popen(cmd, shell=True)
return {'token': token, 'host': host, 'port': port}
- _image_sems = {}
+ @exception.wrap_exception
+ def get_vnc_console(self, instance):
+ def get_vnc_port_for_instance(instance_name):
+ virt_dom = self._conn.lookupByName(instance_name)
+ xml = virt_dom.XMLDesc(0)
+ # TODO: use etree instead of minidom
+ dom = minidom.parseString(xml)
+
+ for graphic in dom.getElementsByTagName('graphics'):
+ if graphic.getAttribute('type') == 'vnc':
+ return graphic.getAttribute('port')
+
+ port = get_vnc_port_for_instance(instance['name'])
+ token = str(uuid.uuid4())
+ host = instance['host']
+
+ return {'token': token, 'host': host, 'port': port}
@staticmethod
def _cache_image(fn, target, fname, cow=False, *args, **kwargs):
@@ -949,6 +966,8 @@ class LibvirtConnection(driver.ComputeDriver):
'driver_type': driver_type,
'nics': nics}
+ if FLAGS.vnc_enabled:
+ xml_info['vncserver_host'] = FLAGS.vncserver_host
if not rescue:
if instance['kernel_id']:
xml_info['kernel'] = xml_info['basepath'] + "/kernel"
diff --git a/nova/vnc/__init__.py b/nova/vnc/__init__.py
new file mode 100644
index 000000000..b5b00e44e
--- /dev/null
+++ b/nova/vnc/__init__.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2010 Openstack, LLC.
+# 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.
+
+"""Module for VNC Proxying."""
+
+from nova import flags
+
+
+FLAGS = flags.FLAGS
+flags.DEFINE_string('vncproxy_topic', 'vncproxy',
+ 'the topic vnc proxy nodes listen on')
+flags.DEFINE_string('vncproxy_url',
+ 'http://127.0.0.1:6080',
+ 'location of vnc console proxy, \
+ in the form "http://127.0.0.1:6080"')
+flags.DEFINE_string('vncserver_host', '0.0.0.0',
+ 'the host interface on which vnc server should listen')
+flags.DEFINE_bool('vnc_enabled', True,
+ 'enable vnc related features')
diff --git a/nova/vnc/auth.py b/nova/vnc/auth.py
new file mode 100644
index 000000000..ce5e10388
--- /dev/null
+++ b/nova/vnc/auth.py
@@ -0,0 +1,138 @@
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2010 Openstack, LLC.
+# 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.
+
+"""Auth Components for VNC Console."""
+
+import time
+import urlparse
+import webob
+
+from webob import Request
+
+from nova import context
+from nova import flags
+from nova import log as logging
+from nova import manager
+from nova import rpc
+from nova import utils
+from nova import wsgi
+from nova import vnc
+
+
+LOG = logging.getLogger('nova.vnc-proxy')
+FLAGS = flags.FLAGS
+
+
+class VNCNovaAuthMiddleware(object):
+ """Implementation of Middleware to Handle Nova Auth."""
+
+ def __init__(self, app):
+ self.app = app
+ self.token_cache = {}
+ utils.LoopingCall(self.delete_expired_cache_items).start(1)
+
+ @webob.dec.wsgify
+ def __call__(self, req):
+ token = req.params.get('token')
+
+ if not token:
+ referrer = req.environ.get('HTTP_REFERER')
+ auth_params = urlparse.parse_qs(urlparse.urlparse(referrer).query)
+ if 'token' in auth_params:
+ token = auth_params['token'][0]
+
+ connection_info = self.get_token_info(token)
+ if not connection_info:
+ LOG.audit(_("Unauthorized Access: (%s)"), req.environ)
+ return webob.exc.HTTPForbidden(detail='Unauthorized')
+
+ if req.path == vnc.proxy.WS_ENDPOINT:
+ req.environ['vnc_host'] = connection_info['host']
+ req.environ['vnc_port'] = int(connection_info['port'])
+
+ return req.get_response(self.app)
+
+ def get_token_info(self, token):
+ if token in self.token_cache:
+ return self.token_cache[token]
+
+ rval = rpc.call(context.get_admin_context(),
+ FLAGS.vncproxy_topic,
+ {"method": "check_token", "args": {'token': token}})
+ if rval:
+ self.token_cache[token] = rval
+ return rval
+
+ def delete_expired_cache_items(self):
+ now = time.time()
+ to_delete = []
+ for k, v in self.token_cache.items():
+ if now - v['last_activity_at'] > FLAGS.vnc_token_ttl:
+ to_delete.append(k)
+
+ for k in to_delete:
+ del self.token_cache[k]
+
+
+class LoggingMiddleware(object):
+ """Middleware for basic vnc-specific request logging."""
+
+ def __init__(self, app):
+ self.app = app
+
+ @webob.dec.wsgify
+ def __call__(self, req):
+ if req.path == vnc.proxy.WS_ENDPOINT:
+ LOG.info(_("Received Websocket Request: %s"), req.url)
+ else:
+ LOG.info(_("Received Request: %s"), req.url)
+
+ return req.get_response(self.app)
+
+
+class VNCProxyAuthManager(manager.Manager):
+ """Manages token based authentication."""
+
+ def __init__(self, scheduler_driver=None, *args, **kwargs):
+ super(VNCProxyAuthManager, self).__init__(*args, **kwargs)
+ self.tokens = {}
+ utils.LoopingCall(self._delete_expired_tokens).start(1)
+
+ def authorize_vnc_console(self, context, token, host, port):
+ self.tokens[token] = {'host': host,
+ 'port': port,
+ 'last_activity_at': time.time()}
+ token_dict = self.tokens[token]
+ LOG.audit(_("Received Token: %(token)s, %(token_dict)s)"), locals())
+
+ def check_token(self, context, token):
+ token_valid = token in self.tokens
+ LOG.audit(_("Checking Token: %(token)s, %(token_valid)s)"), locals())
+ if token_valid:
+ return self.tokens[token]
+
+ def _delete_expired_tokens(self):
+ now = time.time()
+ to_delete = []
+ for k, v in self.tokens.items():
+ if now - v['last_activity_at'] > FLAGS.vnc_token_ttl:
+ to_delete.append(k)
+
+ for k in to_delete:
+ LOG.audit(_("Deleting Expired Token: %s)"), k)
+ del self.tokens[k]
diff --git a/nova/vnc/proxy.py b/nova/vnc/proxy.py
new file mode 100644
index 000000000..c4603803b
--- /dev/null
+++ b/nova/vnc/proxy.py
@@ -0,0 +1,131 @@
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2010 Openstack, LLC.
+# 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.
+
+"""Eventlet WSGI Services to proxy VNC. No nova deps."""
+
+import base64
+import os
+
+import eventlet
+from eventlet import wsgi
+from eventlet import websocket
+
+import webob
+
+
+WS_ENDPOINT = '/data'
+
+
+class WebsocketVNCProxy(object):
+ """Class to proxy from websocket to vnc server."""
+
+ def __init__(self, wwwroot):
+ self.wwwroot = wwwroot
+ self.whitelist = {}
+ for root, dirs, files in os.walk(wwwroot):
+ hidden_dirs = []
+ for d in dirs:
+ if d.startswith('.'):
+ hidden_dirs.append(d)
+ for d in hidden_dirs:
+ dirs.remove(d)
+ for name in files:
+ if not str(name).startswith('.'):
+ filename = os.path.join(root, name)
+ self.whitelist[filename] = True
+
+ def get_whitelist(self):
+ return self.whitelist.keys()
+
+ def sock2ws(self, source, dest):
+ try:
+ while True:
+ d = source.recv(32384)
+ if d == '':
+ break
+ d = base64.b64encode(d)
+ dest.send(d)
+ except:
+ source.close()
+ dest.close()
+
+ def ws2sock(self, source, dest):
+ try:
+ while True:
+ d = source.wait()
+ if d is None:
+ break
+ d = base64.b64decode(d)
+ dest.sendall(d)
+ except:
+ source.close()
+ dest.close()
+
+ def proxy_connection(self, environ, start_response):
+ @websocket.WebSocketWSGI
+ def _handle(client):
+ server = eventlet.connect((client.environ['vnc_host'],
+ client.environ['vnc_port']))
+ t1 = eventlet.spawn(self.ws2sock, client, server)
+ t2 = eventlet.spawn(self.sock2ws, server, client)
+ t1.wait()
+ t2.wait()
+ _handle(environ, start_response)
+
+ def __call__(self, environ, start_response):
+ req = webob.Request(environ)
+ if req.path == WS_ENDPOINT:
+ return self.proxy_connection(environ, start_response)
+ else:
+ if req.path == '/':
+ fname = '/vnc_auto.html'
+ else:
+ fname = req.path
+
+ fname = (self.wwwroot + fname).replace('//', '/')
+ if not fname in self.whitelist:
+ start_response('404 Not Found',
+ [('content-type', 'text/html')])
+ return "Not Found"
+
+ base, ext = os.path.splitext(fname)
+ if ext == '.js':
+ mimetype = 'application/javascript'
+ elif ext == '.css':
+ mimetype = 'text/css'
+ elif ext in ['.svg', '.jpg', '.png', '.gif']:
+ mimetype = 'image'
+ else:
+ mimetype = 'text/html'
+
+ start_response('200 OK', [('content-type', mimetype)])
+ return open(os.path.join(fname)).read()
+
+
+class DebugMiddleware(object):
+ """Debug middleware. Skip auth, get vnc connect info from query string."""
+
+ def __init__(self, app):
+ self.app = app
+
+ @webob.dec.wsgify
+ def __call__(self, req):
+ if req.path == WS_ENDPOINT:
+ req.environ['vnc_host'] = req.params.get('host')
+ req.environ['vnc_port'] = int(req.params.get('port'))
+ return req.get_response(self.app)
diff --git a/setup.py b/setup.py
index 3b48990ac..20f4c1947 100644
--- a/setup.py
+++ b/setup.py
@@ -112,4 +112,5 @@ DistUtilsExtra.auto.setup(name='nova',
'bin/nova-spoolsentry',
'bin/stack',
'bin/nova-volume',
+ 'bin/nova-vncproxy',
'tools/nova-debug'])