summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorAnthony Young <sleepsonthefloor@gmail.com>2011-03-23 17:23:04 -0700
committerAnthony Young <sleepsonthefloor@gmail.com>2011-03-23 17:23:04 -0700
commit61b52830cdce9ac5ec75d744f07764e535bcae35 (patch)
treeef57d0b805be6fa91522f863988ea420f911da56 /nova
parent6912b0e1efd6ba3814d3b29beef236bfe4da52ea (diff)
parent85ad729e4448bb4211b79e325cef897fc4e2b0bb (diff)
Merge branch 'vnc_console' of git://github.com/sleepsonthefloor/nova into vnc_console
Conflicts: nova/virt/libvirt_conn.py
Diffstat (limited to 'nova')
-rw-r--r--nova/api/ec2/cloud.py6
-rw-r--r--nova/compute/api.py17
-rw-r--r--nova/compute/manager.py9
-rw-r--r--nova/flags.py10
-rw-r--r--nova/virt/libvirt.xml.template3
-rw-r--r--nova/virt/libvirt_conn.py21
-rw-r--r--nova/vnc/__init__.py0
-rw-r--r--nova/vnc/auth.py112
-rw-r--r--nova/vnc/proxy.py131
9 files changed, 309 insertions, 0 deletions
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index e257e44e7..c63d59382 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -536,6 +536,12 @@ class CloudController(object):
return self.compute_api.get_ajax_console(context,
instance_id=instance_id)
+ def get_vnc_console(self, context, instance_id, **kwargs):
+ ec2_id = instance_id
+ instance_id = 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/compute/api.py b/nova/compute/api.py
index 01eead4ac..5ba7d7585 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -584,6 +584,23 @@ 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 an AJAX Console"""
+ instance = self.get(context, instance_id)
+ output = self._call_compute_message('get_vnc_console',
+ context,
+ instance_id)
+ rpc.cast(context, '%s' % FLAGS.vnc_console_proxy_topic,
+ {'method': 'authorize_vnc_console',
+ 'args': {'token': output['token'], 'host': output['host'],
+ 'port': output['port']}})
+
+ time.sleep(1)
+
+ return {'url': '%s/vnc_auto.html?token=%s&host=%s&port=%s' %
+ (FLAGS.vnc_console_proxy_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 ac63f68ea..24ab2f510 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -714,6 +714,15 @@ class ComputeManager(manager.Manager):
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/flags.py b/nova/flags.py
index 9123e9ac7..904373a0b 100644
--- a/nova/flags.py
+++ b/nova/flags.py
@@ -281,6 +281,16 @@ DEFINE_string('ajax_console_proxy_url',
in the form "http://127.0.0.1:8000"')
DEFINE_string('ajax_console_proxy_port',
8000, 'port that ajax_console_proxy binds')
+DEFINE_string('vnc_console_proxy_topic', 'vnc_proxy',
+ 'the topic vnc proxy nodes listen on')
+DEFINE_string('vnc_console_proxy_url',
+ 'http://127.0.0.1:6080',
+ 'location of vnc console proxy, \
+ in the form "http://127.0.0.1:6080"')
+DEFINE_string('vnc_compute_host_iface', '0.0.0.0',
+ 'the compute host interface on which vnc server should listen')
+DEFINE_bool('vnc_enabled', True,
+ 'enable vnc related features')
DEFINE_bool('verbose', False, 'show debug output')
DEFINE_boolean('fake_rabbit', False, 'use a fake rabbit')
DEFINE_bool('fake_network', False,
diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template
index ef2d2cd6b..c6e7d0a45 100644
--- a/nova/virt/libvirt.xml.template
+++ b/nova/virt/libvirt.xml.template
@@ -101,5 +101,8 @@
<target port='0'/>
</serial>
+#if $getVar('vnc_compute_host_iface', False)
+ <graphics type='vnc' port='-1' autoport='yes' keymap='en-us' listen='${vnc_host_iface}'/>
+#end if
</devices>
</domain>
diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py
index f264cf619..75d438bb6 100644
--- a/nova/virt/libvirt_conn.py
+++ b/nova/virt/libvirt_conn.py
@@ -533,6 +533,23 @@ class LibvirtConnection(object):
subprocess.Popen(cmd, shell=True)
return {'token': token, 'host': host, 'port': port}
+ @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)
+ 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}
+
_image_sems = {}
@staticmethod
@@ -761,6 +778,10 @@ class LibvirtConnection(object):
if gateway_v6:
xml_info['gateway_v6'] = gateway_v6 + "/128"
+ if FLAGS.vnc_enabled:
+ xml_info['vnc_compute_host_iface'] = FLAGS.vnc_compute_host_iface
+ if ra_server:
+ xml_info['ra_server'] = ra_server + "/128"
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..e69de29bb
--- /dev/null
+++ b/nova/vnc/__init__.py
diff --git a/nova/vnc/auth.py b/nova/vnc/auth.py
new file mode 100644
index 000000000..676cb2360
--- /dev/null
+++ b/nova/vnc/auth.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python
+# pylint: disable-msg=C0103
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 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.
+
+"""Auth Components for VNC Console"""
+
+import time
+import urlparse
+import webob
+from webob import Request
+
+from nova import flags
+from nova import log as logging
+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 NovaAuthMiddleware(object):
+ """Implementation of Middleware to Handle Nova Auth"""
+
+ def __init__(self, app):
+ self.app = app
+ self.register_listeners()
+
+ @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]
+
+ if not token in self.tokens:
+ LOG.audit(_("Unauthorized Access: (%s)"), req.environ)
+ return webob.exc.HTTPForbidden(detail='Unauthorized')
+
+ if req.path == vnc.proxy.WS_ENDPOINT:
+ req.environ['vnc_host'] = self.tokens[token]['args']['host']
+ req.environ['vnc_port'] = int(self.tokens[token]['args']['port'])
+
+ return req.get_response(self.app)
+
+ def register_listeners(self):
+ middleware = self
+ middleware.tokens = {}
+
+ class Callback:
+ def __call__(self, data, message):
+ if data['method'] == 'authorize_vnc_console':
+ token = data['args']['token']
+ LOG.audit(_("Received Token: %s)"), token)
+ middleware.tokens[token] = \
+ {'args': data['args'], 'last_activity_at': time.time()}
+
+ def delete_expired_tokens():
+ now = time.time()
+ to_delete = []
+ for k, v in middleware.tokens.items():
+ if now - v['last_activity_at'] > FLAGS.vnc_token_ttl:
+ to_delete.append(k)
+
+ for k in to_delete:
+ LOG.audit(_("Deleting Token: %s)"), k)
+ del middleware.tokens[k]
+
+ conn = rpc.Connection.instance(new=True)
+ consumer = rpc.TopicConsumer(
+ connection=conn,
+ topic=FLAGS.vnc_console_proxy_topic)
+ consumer.register_callback(Callback())
+
+ utils.LoopingCall(consumer.fetch, auto_ack=True,
+ enable_callbacks=True).start(0.1)
+ utils.LoopingCall(delete_expired_tokens).start(1)
+
+
+class LoggingMiddleware(object):
+ 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)
diff --git a/nova/vnc/proxy.py b/nova/vnc/proxy.py
new file mode 100644
index 000000000..dea838e3d
--- /dev/null
+++ b/nova/vnc/proxy.py
@@ -0,0 +1,131 @@
+#!/usr/bin/env python
+# pylint: disable-msg=C0103
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 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.
+
+"""Eventlet WSGI Services to proxy VNC. No nova deps."""
+
+from base64 import b64encode, b64decode
+import eventlet
+from eventlet import wsgi
+from eventlet import websocket
+import os
+from webob import Request
+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 = 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 = 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 = 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 port and host 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)