Implements blueprint vnc-console-cleanup

* Creates a unified way to access vnc consoles for xenserver and libvirt
 * Now supports both java and websocket clients
 * Removes nova-vncproxy - a replacement version of this (nova-novncproxy) can be found as described in vncconsole.rst
 * Adds nova-xvpvncproxy, which supports a java vnc client
 * Adds api extension to access java and novnc access_urls
 * Fixes proxy server to close/shutdown sockets more cleanly
 * Address style feedback
 * Use new-style extension format
 * Fix setup.py
 * utils.gen_uuid must be wrapped like str(utils.gen_uuid()) or it can't be serialized

Change-Id: I5e42e2f160e8e3476269bd64b0e8aa77e66c918c
This commit is contained in:
Anthony Young
2011-12-22 21:39:21 +00:00
parent 24111cbe22
commit 6bcc351b2a
10 changed files with 259 additions and 73 deletions

View File

@@ -44,7 +44,6 @@ from nova import flags
from nova import log as logging from nova import log as logging
from nova import service from nova import service
from nova import utils from nova import utils
from nova.vnc import server
from nova.objectstore import s3server from nova.objectstore import s3server
@@ -60,17 +59,12 @@ if __name__ == '__main__':
servers.append(service.WSGIService(api)) servers.append(service.WSGIService(api))
except (Exception, SystemExit): except (Exception, SystemExit):
logging.exception(_('Failed to load %s') % '%s-api' % api) 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 # nova-objectstore
try: try:
servers.append(s3server.get_wsgi_server()) servers.append(s3server.get_wsgi_server())
except (Exception, SystemExit): except (Exception, SystemExit):
logging.exception(_('Failed to load %s') % 'objectstore-wsgi') 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']: 'nova-network', 'nova-scheduler', 'nova-vsa']:
try: try:
servers.append(service.Service.create(binary=binary)) servers.append(service.Service.create(binary=binary))

48
bin/nova-consoleauth Executable file
View File

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

View File

@@ -16,7 +16,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
"""VNC Console Proxy Server.""" """XVP VNC Console Proxy Server."""
import eventlet import eventlet
eventlet.monkey_patch() eventlet.monkey_patch()
@@ -35,7 +35,7 @@ from nova import flags
from nova import log as logging from nova import log as logging
from nova import service from nova import service
from nova import utils from nova import utils
from nova.vnc import server from nova.vnc import xvp_proxy
if __name__ == "__main__": if __name__ == "__main__":
@@ -43,7 +43,6 @@ if __name__ == "__main__":
flags.FLAGS(sys.argv) flags.FLAGS(sys.argv)
logging.setup() logging.setup()
wsgi_server = server.get_wsgi_server() wsgi_server = xvp_proxy.get_wsgi_server()
server = service.Service.create(binary='nova-vncproxy') service.serve(wsgi_server)
service.serve(wsgi_server, server)
service.wait() service.wait()

View File

@@ -15,68 +15,117 @@
License for the specific language governing permissions and limitations License for the specific language governing permissions and limitations
under the License. under the License.
Getting Started with the VNC Proxy
==================================
Overview
========
The VNC Proxy is an OpenStack component that allows users of Nova to access 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 connects to api and gets an access_url like http://ip:port/?token=xyz
* User pastes url in browser * User pastes url in browser or as client parameter
* Browser connects to VNC Proxy though a websocket enabled client like noVNC * Browser/Client connects to proxy
* VNC Proxy authorizes users token, maps the token to a host and port of an * Proxy authorizes users token, maps the token to a host and port of an
instance's VNC server 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 the session ends
Note that in general, the vnc proxy performs multiple functions:
Configuring the VNC Proxy * Bridges between public network (where clients live) and private network
------------------------- (where vncservers live)
nova-vncproxy requires a websocket enabled html client to work properly. At * Mediates token authentication
this time, the only tested client is a slightly modified fork of noVNC, which * Transparently deals with hypervisor-specific connection details to provide
you can at find http://github.com/openstack/noVNC.git a uniform client experience.
.. 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]`
Enabling VNC Consoles in Nova About nova-consoleauth
----------------------------- ----------------------
At the moment, VNC support is supported only when using libvirt. To enable VNC Both client proxies leverage a shared service to manage token auth called
Console, configure the following flags: 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]` - Getting an Access Url
proxy_port defaults to 6080. This url must point to nova-vncproxy ---------------------
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 * :option:`--vnc_enabled=[True|False]` - defaults to True. If this flag is
not set your instances will launch without vnc support. not set your instances will launch without vnc support.
* :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
Getting an instance's VNC Console production deployments as a private address. Applies to libvirt only.
---------------------------------
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.

View File

@@ -679,6 +679,10 @@ class ConsoleNotFoundInPoolForInstance(ConsoleNotFound):
"in pool %(pool_id)s could not be found.") "in pool %(pool_id)s could not be found.")
class ConsoleTypeInvalid(Invalid):
message = _("Invalid console type %(console_type)s ")
class NoInstanceTypesFound(NotFound): class NoInstanceTypesFound(NotFound):
message = _("Zero instance types found.") message = _("Zero instance types found.")

View File

@@ -696,15 +696,40 @@ class ComputeTestCase(BaseTestCase):
self.assert_(set(['token', 'host', 'port']).issubset(console.keys())) self.assert_(set(['token', 'host', 'port']).issubset(console.keys()))
self.compute.terminate_instance(self.context, instance['uuid']) 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.""" """Make sure we can a vnc console for an instance."""
instance = self._create_fake_instance() instance = self._create_fake_instance()
self.compute.run_instance(self.context, instance['uuid']) 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.assert_(console)
self.compute.terminate_instance(self.context, instance['uuid']) 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): def test_diagnostics(self):
"""Make sure we can get diagnostics for an instance.""" """Make sure we can get diagnostics for an instance."""
instance = self._create_fake_instance() instance = self._create_fake_instance()
@@ -2831,16 +2856,20 @@ class ComputeAPITestCase(BaseTestCase):
def test_vnc_console(self): def test_vnc_console(self):
"""Make sure we can a vnc console for an instance.""" """Make sure we can a vnc console for an instance."""
def vnc_rpc_call_wrapper(*args, **kwargs): 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) self.stubs.Set(rpc, 'call', vnc_rpc_call_wrapper)
instance = self._create_fake_instance() 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) self.compute_api.delete(self.context, instance)
def test_ajax_console(self): 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): def ajax_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}

View File

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

View File

@@ -294,7 +294,7 @@ class _VirtDriverTestCase(test.TestCase):
def test_get_vnc_console(self): def test_get_vnc_console(self):
instance_ref, network_info = self._get_running_instance() instance_ref, network_info = self._get_running_instance()
vnc_console = self.connection.get_vnc_console(instance_ref) 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('host', vnc_console)
self.assertIn('port', vnc_console) self.assertIn('port', vnc_console)

View File

@@ -44,7 +44,8 @@ class Server(object):
default_pool_size = 1000 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. """Initialize, but do not start, a WSGI server.
:param name: Pretty name for logging. :param name: Pretty name for logging.
@@ -62,6 +63,7 @@ class Server(object):
self._server = None self._server = None
self._tcp_server = None self._tcp_server = None
self._socket = None self._socket = None
self._protocol = protocol
self._pool = eventlet.GreenPool(pool_size or self.default_pool_size) self._pool = eventlet.GreenPool(pool_size or self.default_pool_size)
self._logger = logging.getLogger("eventlet.wsgi.server") self._logger = logging.getLogger("eventlet.wsgi.server")
self._wsgi_logger = logging.WritableLogger(self._logger) self._wsgi_logger = logging.WritableLogger(self._logger)
@@ -74,6 +76,7 @@ class Server(object):
""" """
eventlet.wsgi.server(self._socket, eventlet.wsgi.server(self._socket,
self.app, self.app,
protocol=self._protocol,
custom_pool=self._pool, custom_pool=self._pool,
log=self._wsgi_logger) log=self._wsgi_logger)

View File

@@ -91,6 +91,7 @@ setup(name='nova',
'bin/nova-api-os-volume', 'bin/nova-api-os-volume',
'bin/nova-compute', 'bin/nova-compute',
'bin/nova-console', 'bin/nova-console',
'bin/nova-consoleauth',
'bin/nova-dhcpbridge', 'bin/nova-dhcpbridge',
'bin/nova-direct-api', 'bin/nova-direct-api',
'bin/nova-logspool', 'bin/nova-logspool',
@@ -100,9 +101,9 @@ setup(name='nova',
'bin/nova-rootwrap', 'bin/nova-rootwrap',
'bin/nova-scheduler', 'bin/nova-scheduler',
'bin/nova-spoolsentry', 'bin/nova-spoolsentry',
'bin/nova-vncproxy',
'bin/nova-volume', 'bin/nova-volume',
'bin/nova-vsa', 'bin/nova-vsa',
'bin/nova-xvpvncproxy',
'bin/stack', 'bin/stack',
'tools/nova-debug'], 'tools/nova-debug'],
py_modules=[]) py_modules=[])