diff --git a/bin/nova-all b/bin/nova-all index 497195bda..9c9e2bbaa 100755 --- a/bin/nova-all +++ b/bin/nova-all @@ -44,7 +44,6 @@ from nova import flags from nova import log as logging from nova import service from nova import utils -from nova.vnc import server from nova.objectstore import s3server @@ -60,17 +59,12 @@ if __name__ == '__main__': servers.append(service.WSGIService(api)) except (Exception, SystemExit): logging.exception(_('Failed to load %s') % '%s-api' % api) - # nova-vncproxy - try: - servers.append(server.get_wsgi_server()) - except (Exception, SystemExit): - logging.exception(_('Failed to load %s') % 'vncproxy-wsgi') # nova-objectstore try: servers.append(s3server.get_wsgi_server()) except (Exception, SystemExit): logging.exception(_('Failed to load %s') % 'objectstore-wsgi') - for binary in ['nova-vncproxy', 'nova-compute', 'nova-volume', + for binary in ['nova-xvpvncproxy', 'nova-compute', 'nova-volume', 'nova-network', 'nova-scheduler', 'nova-vsa']: try: servers.append(service.Service.create(binary=binary)) diff --git a/bin/nova-consoleauth b/bin/nova-consoleauth new file mode 100755 index 000000000..325a399d7 --- /dev/null +++ b/bin/nova-consoleauth @@ -0,0 +1,48 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2012 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 +eventlet.monkey_patch() + +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) + + +from nova import flags +from nova import log as logging +from nova import service +from nova import utils +from nova.consoleauth import manager + + +if __name__ == "__main__": + utils.default_flagfile() + flags.FLAGS(sys.argv) + logging.setup() + + server = service.Service.create(binary='nova-consoleauth') + service.serve(server) + service.wait() diff --git a/bin/nova-vncproxy b/bin/nova-xvpvncproxy similarity index 86% rename from bin/nova-vncproxy rename to bin/nova-xvpvncproxy index 9b44a95ea..a17d0cbb3 100755 --- a/bin/nova-vncproxy +++ b/bin/nova-xvpvncproxy @@ -16,7 +16,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""VNC Console Proxy Server.""" +"""XVP VNC Console Proxy Server.""" import eventlet eventlet.monkey_patch() @@ -35,7 +35,7 @@ from nova import flags from nova import log as logging from nova import service from nova import utils -from nova.vnc import server +from nova.vnc import xvp_proxy if __name__ == "__main__": @@ -43,7 +43,6 @@ if __name__ == "__main__": flags.FLAGS(sys.argv) logging.setup() - wsgi_server = server.get_wsgi_server() - server = service.Service.create(binary='nova-vncproxy') - service.serve(wsgi_server, server) + wsgi_server = xvp_proxy.get_wsgi_server() + service.serve(wsgi_server) service.wait() diff --git a/doc/source/runnova/vncconsole.rst b/doc/source/runnova/vncconsole.rst index 8bef4eb37..cf69b610b 100644 --- a/doc/source/runnova/vncconsole.rst +++ b/doc/source/runnova/vncconsole.rst @@ -15,68 +15,117 @@ License for the specific language governing permissions and limitations under the License. -Getting Started with the VNC Proxy -================================== +Overview +======== The VNC Proxy is an OpenStack component that allows users of Nova to access -their instances through a websocket enabled browser (like Google Chrome). +their instances through vnc clients. In essex and beyond, there is support +for for both libvirt and XenServer using both java and websocket cleints. -A VNC Connection works like so: +In general, a VNC console 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 +* User connects to api and gets an access_url like http://ip:port/?token=xyz +* User pastes url in browser or as client parameter +* Browser/Client connects to proxy +* 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 +* 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: - -* :option:`--vncproxy_port=[port]` -* :option:`--vncproxy_host=[host]` - -It also binds a separate Flash socket policy listener on 0.0.0.0:843. This -can be configured with: - -* :option:`--vncproxy_flash_socket_policy_port=[port]` -* :option:`--vncproxy_flash_socket_policy_host=[host]` +Note that in general, the vnc proxy performs multiple functions: +* Bridges between public network (where clients live) and private network + (where vncservers live) +* Mediates token authentication +* Transparently deals with hypervisor-specific connection details to provide + a uniform client experience. -Enabling VNC Consoles in Nova ------------------------------ -At the moment, VNC support is supported only when using libvirt. To enable VNC -Console, configure the following flags: +About nova-consoleauth +---------------------- +Both client proxies leverage a shared service to manage token auth called +nova-consoleauth. This service must be running in order for for either proxy +to work. Many proxies of either type can be run against a single +nova-consoleauth service in a cluster configuration. -* :option:`--vnc_console_proxy_url=http://[proxy_host]:[proxy_port]` - - proxy_port defaults to 6080. This url must point to nova-vncproxy +Getting an Access Url +--------------------- +Nova provides the ability to create access_urls through the os-consoles extension. +Support for accessing this url is provided by novaclient: + + # FIXME (sleepsonthefloor) update this branch name once client code merges + git clone https://github.com/cloudbuilders/python-novaclient + git checkout vnc_redux + . openrc # or whatever you use to load standard nova creds + nova get-vnc-console [server_id] [xvpvnc|novnc] + + +Accessing VNC Consoles with a Java client +----------------------------------------- +To enable support for the OpenStack java vnc client in nova, nova provides the +nova-xvpvncproxy service, which you should run to enable this feature. + +* :option:`--xvpvncproxy_baseurl=[base url for client connections]` - + this is the public base url to which clients will connect. "?token=abc" + will be added to this url for the purposes of auth. +* :option:`--xvpvncproxy_port=[port]` - port to bind (defaults to 6081) +* :option:`--xvpvncproxy_host=[host]` - host to bind (defaults to 0.0.0.0) + +As a client, you will need a special Java client, which is +a version of TightVNC slightly modified to support our token auth:: + + git clone https://github.com/cloudbuilders/nova-xvpvncviewer + cd nova-xvpvncviewer + make + +Then, to create a session, first request an access url using python-novaclient +and then run the client like so:: + + # Retrieve access url + nova get-vnc-console [server_id] xvpvnc + # Run client + java -jar VncViewer.jar [access_url] + + +nova-vncproxy replaced with nova-novncproxy +------------------------------------------- +The previous vnc proxy, nova-vncproxy, has been removed from the nova source +tree and replaced with an improved server that can be found externally at +http://github.com/cloudbuilders/noVNC.git (in a branch called vnc_redux while +this patch is in review). + +To use this nova-novncproxy: + git clone http://github.com/cloudbuilders/noVNC.git + git checkout vnc_redux + utils/nova-novncproxy --flagfile=[path to flagfile] + +The --flagfile param should point to your nova config that includes the rabbit +server address and credentials. + +By default, nova-novncproxy binds 0.0.0.0:6080. This can be configured with: + +* :option:`--novncproxy_baseurl=[base url for client connections]` - + this is the public base url to which clients will connect. "?token=abc" + will be added to this url for the purposes of auth. +* :option:`--novncproxy_port=[port]` +* :option:`--novncproxy_host=[host]` + + +Accessing a vnc console through a web browser +--------------------------------------------- +Retrieving an access_url for a web browser is similar to the flow for +the java client: + + # Retrieve access url + nova get-vnc-console [server_id] novnc + # Then, paste the url into your web browser + +Support for a streamlined flow via dashboard will land in essex. + + +Important Options +----------------- * :option:`--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. +* :option:`--vncserver_host=[instance vncserver host]` - defaults to 127.0.0.1 + This is the address that vncservers will bind, and should be overridden in + production deployments as a private address. Applies to libvirt only. diff --git a/nova/exception.py b/nova/exception.py index 23bcf46f4..777d64515 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -679,6 +679,10 @@ class ConsoleNotFoundInPoolForInstance(ConsoleNotFound): "in pool %(pool_id)s could not be found.") +class ConsoleTypeInvalid(Invalid): + message = _("Invalid console type %(console_type)s ") + + class NoInstanceTypesFound(NotFound): message = _("Zero instance types found.") diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 06eaf8ef5..17cf46ceb 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -696,15 +696,40 @@ class ComputeTestCase(BaseTestCase): self.assert_(set(['token', 'host', 'port']).issubset(console.keys())) self.compute.terminate_instance(self.context, instance['uuid']) - def test_vnc_console(self): + def test_novnc_vnc_console(self): """Make sure we can a vnc console for an instance.""" instance = self._create_fake_instance() self.compute.run_instance(self.context, instance['uuid']) - console = self.compute.get_vnc_console(self.context, instance['uuid']) + console = self.compute.get_vnc_console(self.context, + instance['uuid'], + 'novnc') self.assert_(console) self.compute.terminate_instance(self.context, instance['uuid']) + def test_xvpvnc_vnc_console(self): + """Make sure we can a vnc console for an instance.""" + instance = self._create_fake_instance() + self.compute.run_instance(self.context, instance['uuid']) + + console = self.compute.get_vnc_console(self.context, + instance['uuid'], + 'xvpvnc') + self.assert_(console) + self.compute.terminate_instance(self.context, instance['uuid']) + + def test_invalid_vnc_console_type(self): + """Make sure we can a vnc console for an instance.""" + instance = self._create_fake_instance() + self.compute.run_instance(self.context, instance['uuid']) + + self.assertRaises(exception.ConsoleTypeInvalid, + self.compute.get_vnc_console, + self.context, + instance['uuid'], + 'invalid') + self.compute.terminate_instance(self.context, instance['uuid']) + def test_diagnostics(self): """Make sure we can get diagnostics for an instance.""" instance = self._create_fake_instance() @@ -2831,16 +2856,20 @@ class ComputeAPITestCase(BaseTestCase): def test_vnc_console(self): """Make sure we can a vnc console for an instance.""" def vnc_rpc_call_wrapper(*args, **kwargs): - return {'token': 'asdf', 'host': '0.0.0.0', 'port': 8080} + return {'token': 'asdf', 'host': '0.0.0.0', + 'port': 8080, 'access_url': None, + 'internal_access_path': None} self.stubs.Set(rpc, 'call', vnc_rpc_call_wrapper) instance = self._create_fake_instance() - console = self.compute_api.get_vnc_console(self.context, instance) + console = self.compute_api.get_vnc_console(self.context, + instance, + 'novnc') self.compute_api.delete(self.context, instance) def test_ajax_console(self): - """Make sure we can a vnc console for an instance.""" + """Make sure we can an ajax console for an instance.""" def ajax_rpc_call_wrapper(*args, **kwargs): return {'token': 'asdf', 'host': '0.0.0.0', 'port': 8080} diff --git a/nova/tests/test_consoleauth.py b/nova/tests/test_consoleauth.py new file mode 100644 index 000000000..41aefa7fd --- /dev/null +++ b/nova/tests/test_consoleauth.py @@ -0,0 +1,59 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC. +# 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. +""" +Tests for Consoleauth Code. + +""" + +import time + +from nova import context +from nova import db +from nova import flags +from nova import log as logging +from nova import test +from nova import utils +from nova.consoleauth import manager + + +FLAGS = flags.FLAGS +LOG = logging.getLogger('nova.tests.consoleauth') + + +class ConsoleauthTestCase(test.TestCase): + """Test Case for consoleauth.""" + + def setUp(self): + super(ConsoleauthTestCase, self).setUp() + self.manager = utils.import_object(FLAGS.consoleauth_manager) + self.context = context.get_admin_context() + self.old_ttl = FLAGS.console_token_ttl + + def tearDown(self): + super(ConsoleauthTestCase, self).tearDown() + FLAGS.console_token_ttl = self.old_ttl + + def test_tokens_expire(self): + """Test that tokens expire correctly.""" + token = 'mytok' + FLAGS.console_token_ttl = 1 + self.manager.authorize_console(self.context, token, 'novnc', + '127.0.0.1', 'host', '') + self.assertTrue(self.manager.check_token(self.context, token)) + time.sleep(1.1) + self.assertFalse(self.manager.check_token(self.context, token)) diff --git a/nova/tests/test_virt_drivers.py b/nova/tests/test_virt_drivers.py index 9c17b3b0a..adf8f8eb8 100644 --- a/nova/tests/test_virt_drivers.py +++ b/nova/tests/test_virt_drivers.py @@ -294,7 +294,7 @@ class _VirtDriverTestCase(test.TestCase): def test_get_vnc_console(self): instance_ref, network_info = self._get_running_instance() vnc_console = self.connection.get_vnc_console(instance_ref) - self.assertIn('token', vnc_console) + self.assertIn('internal_access_path', vnc_console) self.assertIn('host', vnc_console) self.assertIn('port', vnc_console) diff --git a/nova/wsgi.py b/nova/wsgi.py index b94065b78..e2f17ea78 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -44,7 +44,8 @@ class Server(object): default_pool_size = 1000 - def __init__(self, name, app, host=None, port=None, pool_size=None): + def __init__(self, name, app, host=None, port=None, pool_size=None, + protocol=eventlet.wsgi.HttpProtocol): """Initialize, but do not start, a WSGI server. :param name: Pretty name for logging. @@ -62,6 +63,7 @@ class Server(object): self._server = None self._tcp_server = None self._socket = None + self._protocol = protocol self._pool = eventlet.GreenPool(pool_size or self.default_pool_size) self._logger = logging.getLogger("eventlet.wsgi.server") self._wsgi_logger = logging.WritableLogger(self._logger) @@ -74,6 +76,7 @@ class Server(object): """ eventlet.wsgi.server(self._socket, self.app, + protocol=self._protocol, custom_pool=self._pool, log=self._wsgi_logger) diff --git a/setup.py b/setup.py index f7aec9a36..b63bfde39 100644 --- a/setup.py +++ b/setup.py @@ -91,6 +91,7 @@ setup(name='nova', 'bin/nova-api-os-volume', 'bin/nova-compute', 'bin/nova-console', + 'bin/nova-consoleauth', 'bin/nova-dhcpbridge', 'bin/nova-direct-api', 'bin/nova-logspool', @@ -100,9 +101,9 @@ setup(name='nova', 'bin/nova-rootwrap', 'bin/nova-scheduler', 'bin/nova-spoolsentry', - 'bin/nova-vncproxy', 'bin/nova-volume', 'bin/nova-vsa', + 'bin/nova-xvpvncproxy', 'bin/stack', 'tools/nova-debug'], py_modules=[])