libvirt: allow direct SPICE connections to qemu
This patch adds a new console type, "spice-direct", which provides the connection information required to talk the native SPICE protocol directly to qemu on the hypervisor. This is intended to be fronted by a proxy which will handle authentication separately. A new microversion is introduced which adds the type "spice-direct" to the existing "spice" protocol. An example request: POST /servers/<uuid>/remote-consoles { "remote_console": { "protocol": "spice", "type": "spice-direct" } } An example response: { "remote_console": { "protocol": "spice", "type": "spice-direct", "url": "http://localhost:13200/nova?token=XXX"; } } This token can then be used to lookup connection details for the console using a request like this: GET /os-console-auth-tokens/<consoletoken> Which returns something like this: { "console": { "instance_uuid": <uuid>, "host": <hypervisor>, "port": <a TCP port number>, "tls_port": <another TCP port number>, "internal_access_path": null } } APIImpact Change-Id: I1e701cbabc0e2c435685e31465159eec09e3b1a0
This commit is contained in:
@ -170,7 +170,7 @@
|
||||
tox_envlist: all
|
||||
# bug #1940425 only affect ml2/ovn so we execute
|
||||
# test_live_migration_with_trunk in this job to keep
|
||||
tempest_test_regex: (^tempest\..*compute\..*(migration|resize|reboot).*)
|
||||
tempest_test_regex: (^tempest\..*compute\..*(migration|resize|reboot|spice).*)
|
||||
devstack_localrc:
|
||||
Q_AGENT: openvswitch
|
||||
Q_ML2_TENANT_NETWORK_TYPE: vxlan
|
||||
|
@ -5993,8 +5993,9 @@ remote_console_protocol:
|
||||
remote_console_type:
|
||||
description: |
|
||||
The type of remote console. The valid values are ``novnc``,
|
||||
``spice-html5``, ``serial``, and ``webmks``. The type
|
||||
``webmks`` is added since Microversion ``2.8``.
|
||||
``spice-html5``, ``spice-direct``, ``serial``, and ``webmks``. The type
|
||||
``webmks`` was added in Microversion ``2.8``, and the type
|
||||
``spice-direct`` was added in Microversion ``2.99``.
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
@ -7102,6 +7103,12 @@ tenant_usages:
|
||||
in: body
|
||||
required: true
|
||||
type: array
|
||||
tls_port_number:
|
||||
description: |
|
||||
The port number of a port requiring a TLS connection.
|
||||
in: body
|
||||
required: false
|
||||
type: integer
|
||||
to_port:
|
||||
description: |
|
||||
The port at end of range.
|
||||
|
@ -40,6 +40,13 @@ Request
|
||||
.. literalinclude:: ../../doc/api_samples/os-remote-consoles/v2.6/create-vnc-console-req.json
|
||||
:language: javascript
|
||||
|
||||
**Example Get Remote spice-direct Console**
|
||||
|
||||
*``spice-direct`` consoles were added in microversion 2.99.*
|
||||
|
||||
.. literalinclude:: ../../doc/api_samples/os-remote-consoles/v2.99/create-spice-direct-console-req.json
|
||||
:language: javascript
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
@ -55,6 +62,12 @@ Response
|
||||
.. literalinclude:: ../../doc/api_samples/os-remote-consoles/v2.6/create-vnc-console-resp.json
|
||||
:language: javascript
|
||||
|
||||
**Example Get Remote spice-direct Console**
|
||||
|
||||
*``spice-direct`` consoles were added in microversion 2.99.*
|
||||
|
||||
.. literalinclude:: ../../doc/api_samples/os-remote-consoles/v2.99/create-spice-direct-console-resp.json
|
||||
:language: javascript
|
||||
|
||||
Show Console Connection Information
|
||||
===================================
|
||||
@ -90,9 +103,17 @@ Response
|
||||
- instance_uuid: instance_id_body
|
||||
- host: console_host
|
||||
- port: port_number
|
||||
- tls_port: tls_port_number
|
||||
- internal_access_path: internal_access_path
|
||||
|
||||
**Example Show Console Authentication Token**
|
||||
|
||||
.. literalinclude:: ../../doc/api_samples/os-console-auth-tokens/v2.31/get-console-connect-info-get-resp.json
|
||||
:language: javascript
|
||||
|
||||
**Example Console Connection Information for a spice-direct Console**
|
||||
|
||||
*``spice-direct`` consoles were added in microversion 2.99.*
|
||||
|
||||
.. literalinclude:: ../../doc/api_samples/os-console-auth-tokens/v2.99/get-console-connect-info-get-resp.json
|
||||
:language: javascript
|
||||
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"remote_console": {
|
||||
"protocol": "spice",
|
||||
"type": "spice-direct"
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"console": {
|
||||
"host": "fakespiceconsole.com",
|
||||
"instance_uuid": "16802173-4e67-44f9-ba84-6d99080b81b5",
|
||||
"internal_access_path": null,
|
||||
"port": 6969,
|
||||
"tls_port": 6970
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"remote_console": {
|
||||
"protocol": "spice",
|
||||
"type": "spice-direct"
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"remote_console": {
|
||||
"protocol": "spice",
|
||||
"type": "spice-direct",
|
||||
"url": "http://127.0.0.1:13002/nova?token=aeabd4ec-3acb-4898-9130-10521ccbe5f3"
|
||||
}
|
||||
}
|
||||
|
@ -19,8 +19,8 @@
|
||||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.98",
|
||||
"version": "2.99",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
}
|
||||
}
|
@ -22,9 +22,9 @@
|
||||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.98",
|
||||
"version": "2.99",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -268,6 +268,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
|
||||
* 2.98 - Add support for returning embedded image properties in
|
||||
``server show`` and ``server list --long`` and in the ``server
|
||||
rebuild`` responses.
|
||||
* 2.99 - Add the spice-direct console type to the spice console protocol.
|
||||
"""
|
||||
|
||||
# The minimum and maximum versions of the API supported
|
||||
@ -276,7 +277,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
|
||||
# Note(cyeoh): This only applies for the v2.1 API once microversions
|
||||
# support is fully merged. It does not affect the V2 API.
|
||||
_MIN_API_VERSION = '2.1'
|
||||
_MAX_API_VERSION = '2.98'
|
||||
_MAX_API_VERSION = '2.99'
|
||||
DEFAULT_API_VERSION = _MIN_API_VERSION
|
||||
|
||||
# Almost all proxy APIs which are related to network, images and baremetal
|
||||
|
@ -29,7 +29,7 @@ CONF = nova.conf.CONF
|
||||
|
||||
class ConsoleAuthTokensController(wsgi.Controller):
|
||||
|
||||
def _show(self, req, id):
|
||||
def _show(self, req, id, include_tls_port=False):
|
||||
"""Checks a console auth token and returns the related connect info."""
|
||||
context = req.environ['nova.context']
|
||||
context.can(cat_policies.BASE_POLICY_NAME)
|
||||
@ -57,12 +57,19 @@ class ConsoleAuthTokensController(wsgi.Controller):
|
||||
if not connect_info:
|
||||
raise webob.exc.HTTPNotFound(explanation=_("Token not found"))
|
||||
|
||||
return {'console': {
|
||||
'instance_uuid': connect_info.instance_uuid,
|
||||
'host': connect_info.host,
|
||||
'port': connect_info.port,
|
||||
'internal_access_path': connect_info.internal_access_path,
|
||||
}}
|
||||
retval = {
|
||||
'console': {
|
||||
'instance_uuid': connect_info.instance_uuid,
|
||||
'host': connect_info.host,
|
||||
'port': connect_info.port,
|
||||
'internal_access_path': connect_info.internal_access_path,
|
||||
}
|
||||
}
|
||||
|
||||
if connect_info.console_type == 'spice-direct' and include_tls_port:
|
||||
retval['console']['tls_port'] = connect_info.tls_port
|
||||
|
||||
return retval
|
||||
|
||||
@wsgi.Controller.api_version("2.1", "2.30")
|
||||
@wsgi.expected_errors((400, 401, 404))
|
||||
@ -83,8 +90,14 @@ class ConsoleAuthTokensController(wsgi.Controller):
|
||||
raise webob.exc.HTTPBadRequest()
|
||||
|
||||
@wsgi.Controller.api_version("2.31") # noqa
|
||||
@wsgi.Controller.api_version("2.31", "2.98") # noqa
|
||||
@wsgi.expected_errors((400, 404))
|
||||
@validation.query_schema(schema.show_query)
|
||||
@validation.response_body_schema(schema.show_response)
|
||||
def show(self, req, id): # noqa
|
||||
return self._show(req, id)
|
||||
return self._show(req, id, include_tls_port=False)
|
||||
|
||||
@wsgi.Controller.api_version("2.99") # noqa
|
||||
@wsgi.expected_errors((400, 404))
|
||||
@validation.query_schema(schema.show_query)
|
||||
def show(self, req, id): # noqa
|
||||
return self._show(req, id, include_tls_port=True)
|
||||
|
@ -143,7 +143,8 @@ class RemoteConsolesController(wsgi.Controller):
|
||||
@wsgi.Controller.api_version("2.6")
|
||||
@wsgi.expected_errors((400, 404, 409, 501))
|
||||
@validation.schema(schema.create_v26, "2.6", "2.7")
|
||||
@validation.schema(schema.create_v28, "2.8")
|
||||
@validation.schema(schema.create_v28, "2.8", "2.98")
|
||||
@validation.schema(schema.create_v299, "2.99")
|
||||
def create(self, req, server_id, body):
|
||||
context = req.environ['nova.context']
|
||||
instance = common.get_instance(self.compute_api, context, server_id)
|
||||
|
@ -1279,3 +1279,12 @@ under the struct at the existing ``image`` key in the response for
|
||||
``GET /servers/{server_id}`` (server show), ``GET /servers/detail``
|
||||
(list server --long) and in the rebuild case of
|
||||
``POST /server/{server_id}/action`` (server rebuild) API response.
|
||||
|
||||
.. _microversion 2.99:
|
||||
|
||||
2.99
|
||||
----
|
||||
|
||||
Add the ``spice-direct`` console type to the spice console protocol. Also
|
||||
add a ``tls_port`` field to the return value from
|
||||
``GET /os-console-auth-tokens/{console_token}``.
|
@ -120,6 +120,30 @@ create_v28 = {
|
||||
'additionalProperties': False,
|
||||
}
|
||||
|
||||
create_v299 = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'remote_console': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'protocol': {
|
||||
'type': 'string',
|
||||
'enum': ['vnc', 'spice', 'rdp', 'serial', 'mks'],
|
||||
},
|
||||
'type': {
|
||||
'type': 'string',
|
||||
'enum': ['novnc', 'xvpvnc', 'spice-html5', 'spice-direct',
|
||||
'serial', 'webmks'],
|
||||
},
|
||||
},
|
||||
'required': ['protocol', 'type'],
|
||||
'additionalProperties': False,
|
||||
},
|
||||
},
|
||||
'required': ['remote_console'],
|
||||
'additionalProperties': False,
|
||||
}
|
||||
|
||||
get_vnc_console_response = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
|
@ -7837,22 +7837,30 @@ class ComputeManager(manager.Manager):
|
||||
if not CONF.spice.enabled:
|
||||
raise exception.ConsoleTypeUnavailable(console_type=console_type)
|
||||
|
||||
if console_type != 'spice-html5':
|
||||
if console_type not in ['spice-html5', 'spice-direct']:
|
||||
raise exception.ConsoleTypeInvalid(console_type=console_type)
|
||||
|
||||
try:
|
||||
# Retrieve connect info from driver, and then decorate with our
|
||||
# access info token
|
||||
console = self.driver.get_spice_console(context, instance)
|
||||
console_auth = objects.ConsoleAuthToken(
|
||||
context=context,
|
||||
console_type=console_type,
|
||||
host=console.host,
|
||||
port=console.port,
|
||||
internal_access_path=console.internal_access_path,
|
||||
instance_uuid=instance.uuid,
|
||||
access_url_base=CONF.spice.html5proxy_base_url,
|
||||
)
|
||||
fields = {
|
||||
'context': context,
|
||||
'console_type': console_type,
|
||||
'host': console.host,
|
||||
'port': console.port,
|
||||
'tls_port': console.tlsPort,
|
||||
'instance_uuid': instance.uuid
|
||||
}
|
||||
if console_type == 'spice-html5':
|
||||
fields['internal_access_path'] = console.internal_access_path
|
||||
fields['access_url_base'] = CONF.spice.html5proxy_base_url
|
||||
if console_type == 'spice-direct':
|
||||
fields['internal_access_path'] = None
|
||||
fields['access_url_base'] = \
|
||||
CONF.spice.spice_direct_proxy_base_url
|
||||
|
||||
console_auth = objects.ConsoleAuthToken(**fields)
|
||||
console_auth.authorize(CONF.consoleauth.token_ttl)
|
||||
connect_info = console.get_connection_info(
|
||||
console_auth.token, console_auth.access_url)
|
||||
@ -7974,7 +7982,7 @@ class ComputeManager(manager.Manager):
|
||||
@wrap_exception()
|
||||
@wrap_instance_fault
|
||||
def validate_console_port(self, ctxt, instance, port, console_type):
|
||||
if console_type == "spice-html5":
|
||||
if console_type in ["spice-html5", "spice-direct"]:
|
||||
console_info = self.driver.get_spice_console(ctxt, instance)
|
||||
elif console_type == "serial":
|
||||
console_info = self.driver.get_serial_console(ctxt, instance)
|
||||
|
@ -151,7 +151,7 @@ running. This service is typically launched on the controller node.
|
||||
|
||||
Possible values:
|
||||
|
||||
* Must be a valid URL of the form: ``http://host:port/spice_auto.html``
|
||||
* Must be a valid URL of the form: ``http://host:port/spice_auto.html``
|
||||
where host is the node running ``nova-spicehtml5proxy`` and the port is
|
||||
typically 6082. Consider not using default value as it is not well defined
|
||||
for any real deployment.
|
||||
@ -161,6 +161,22 @@ Related options:
|
||||
* This option depends on ``html5proxy_host`` and ``html5proxy_port`` options.
|
||||
The access URL returned by the compute node must have the host
|
||||
and port where the ``nova-spicehtml5proxy`` service is listening.
|
||||
"""),
|
||||
cfg.URIOpt('spice_direct_proxy_base_url',
|
||||
default='http://127.0.0.1:13002/nova',
|
||||
help="""
|
||||
Location of a SPICE protocol native console proxy.
|
||||
|
||||
A user can retrieve a virt-viewer style .vv connection configuration file by
|
||||
accessing this URL with the attached token when a console is created.
|
||||
|
||||
Possible values:
|
||||
|
||||
* Must be a valid URL of the form: ``http://host:port/nova`` where host is the
|
||||
node running the SPICE protocol native proxy and the port is typically 13002.
|
||||
Note that the port component is optional if you are using the default port
|
||||
for HTTP or HTTPS. Consider not using the default value as it is not well
|
||||
defined for any real deployment.
|
||||
"""),
|
||||
cfg.StrOpt('server_listen',
|
||||
default='127.0.0.1',
|
||||
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"remote_console": {
|
||||
"protocol": "spice",
|
||||
"type": "spice-direct"
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"console": {
|
||||
"host": "%(host)s",
|
||||
"instance_uuid": "%(id)s",
|
||||
"internal_access_path": null,
|
||||
"port": %(port)s,
|
||||
"tls_port": %(tls_port)s
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"remote_console": {
|
||||
"protocol": "spice",
|
||||
"type": "spice-direct"
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"remote_console": {
|
||||
"protocol": "spice",
|
||||
"type": "spice-direct",
|
||||
"url": "http://127.0.0.1:13002/nova?token=%(uuid)s"
|
||||
}
|
||||
}
|
||||
|
@ -52,3 +52,38 @@ class ConsoleAuthTokensSampleJsonTests(test_servers.ServersSampleBase):
|
||||
subs["internal_access_path"] = ".*"
|
||||
self._verify_response('get-console-connect-info-get-resp', subs,
|
||||
response, 200)
|
||||
|
||||
|
||||
class ConsoleV299AuthTokensSampleJsonTests(test_servers.ServersSampleBase):
|
||||
ADMIN_API = True
|
||||
sample_dir = "os-console-auth-tokens"
|
||||
microversion = '2.99'
|
||||
scenarios = [('v2_99', {'api_major_version': 'v2.1'})]
|
||||
|
||||
def _get_console_url(self, data):
|
||||
return jsonutils.loads(data)["remote_console"]["url"]
|
||||
|
||||
def _get_console_token(self, uuid):
|
||||
body = {'protocol': 'spice', 'type': 'spice-direct'}
|
||||
response = self._do_post('servers/%s/remote-consoles' % uuid,
|
||||
'create-spice-direct-console-req', body)
|
||||
|
||||
url = self._get_console_url(response.content)
|
||||
return re.match('.+?token=([^&]+)', url).groups()[0]
|
||||
|
||||
def test_get_console_connect_info(self):
|
||||
self.flags(enabled=True, group='spice')
|
||||
|
||||
uuid = self._post_server()
|
||||
token = self._get_console_token(uuid)
|
||||
|
||||
response = self._do_get('os-console-auth-tokens/%s' % token)
|
||||
|
||||
subs = {}
|
||||
subs["uuid"] = uuid
|
||||
subs["host"] = r"[\w\.\-]+"
|
||||
subs["port"] = "[0-9]+"
|
||||
subs["tls_port"] = "[0-9]+"
|
||||
subs["internal_access_path"] = ".*"
|
||||
self._verify_response('get-console-connect-info-get-resp', subs,
|
||||
response, 200)
|
||||
|
@ -125,3 +125,23 @@ class ConsolesV28SampleJsonTests(test_servers.ServersSampleBase):
|
||||
'create-mks-console-req', body)
|
||||
self._verify_response('create-mks-console-resp', {'url': HTTP_RE},
|
||||
response, 200)
|
||||
|
||||
|
||||
class ConsolesV299SampleJsonTests(test_servers.ServersSampleBase):
|
||||
sample_dir = "os-remote-consoles"
|
||||
microversion = '2.99'
|
||||
scenarios = [('v2_99', {'api_major_version': 'v2.1'})]
|
||||
|
||||
def setUp(self):
|
||||
super(ConsolesV299SampleJsonTests, self).setUp()
|
||||
self.flags(enabled=True, group='spice')
|
||||
|
||||
def test_create_spice_direct_console(self):
|
||||
uuid = self._post_server()
|
||||
|
||||
body = {'protocol': 'spice', 'type': 'spice-direct'}
|
||||
response = self._do_post('servers/%s/remote-consoles' % uuid,
|
||||
'create-spice-direct-console-req', body)
|
||||
self._verify_response(
|
||||
'create-spice-direct-console-resp', {'url': HTTP_RE},
|
||||
response, 200)
|
||||
|
@ -20,7 +20,7 @@ import webob
|
||||
|
||||
from nova.api.openstack import api_version_request
|
||||
from nova.api.openstack.compute import console_auth_tokens \
|
||||
as console_auth_tokens_v21
|
||||
as console_auth_tokens_v21
|
||||
from nova import exception
|
||||
from nova import objects
|
||||
from nova import test
|
||||
@ -30,17 +30,34 @@ from nova.tests.unit.api.openstack import fakes
|
||||
class ConsoleAuthTokensExtensionTestV21(test.NoDBTestCase):
|
||||
controller_class = console_auth_tokens_v21
|
||||
|
||||
_EXPECTED_OUTPUT = {'console': {'instance_uuid': fakes.FAKE_UUID,
|
||||
'host': 'fake_host',
|
||||
'port': '1234',
|
||||
'internal_access_path': fakes.FAKE_UUID}}
|
||||
_EXPECTED_OUTPUT = {
|
||||
'console': {
|
||||
'instance_uuid': fakes.FAKE_UUID,
|
||||
'host': 'fake_host',
|
||||
'port': '1234',
|
||||
'internal_access_path': 'fake_access_path'
|
||||
}
|
||||
}
|
||||
_EXPECTED_OUTPUT_SPICE = {
|
||||
'console': {
|
||||
'instance_uuid': fakes.FAKE_UUID,
|
||||
'host': 'fake_host',
|
||||
'port': '5900',
|
||||
'tls_port': '5901',
|
||||
'internal_access_path': None
|
||||
}
|
||||
}
|
||||
|
||||
# The database backend returns a ConsoleAuthToken.to_dict() and o.vo
|
||||
# StringField are unicode. And the port is an IntegerField.
|
||||
_EXPECTED_OUTPUT_DB = copy.deepcopy(_EXPECTED_OUTPUT)
|
||||
_EXPECTED_OUTPUT_DB['console'].update(
|
||||
{'host': 'fake_host', 'port': 1234,
|
||||
'internal_access_path': fakes.FAKE_UUID})
|
||||
'internal_access_path': 'fake_access_path'})
|
||||
|
||||
_EXPECTED_OUTPUT_DB_SPICE = copy.deepcopy(_EXPECTED_OUTPUT_SPICE)
|
||||
_EXPECTED_OUTPUT_DB_SPICE['console'].update(
|
||||
{'host': u'fake_host', 'port': 5900, 'tls_port': 5901})
|
||||
|
||||
def setUp(self):
|
||||
super(ConsoleAuthTokensExtensionTestV21, self).setUp()
|
||||
@ -63,9 +80,9 @@ class ConsoleAuthTokensExtensionTestV231(ConsoleAuthTokensExtensionTestV21):
|
||||
'2.31')
|
||||
|
||||
@mock.patch('nova.objects.ConsoleAuthToken.validate',
|
||||
return_value = objects.ConsoleAuthToken(
|
||||
return_value=objects.ConsoleAuthToken(
|
||||
instance_uuid=fakes.FAKE_UUID, host='fake_host',
|
||||
port='1234', internal_access_path=fakes.FAKE_UUID,
|
||||
port='1234', internal_access_path='fake_access_path',
|
||||
console_type='webmks',
|
||||
token=fakes.FAKE_UUID))
|
||||
def test_get_console_connect_info(self, mock_validate):
|
||||
@ -79,3 +96,38 @@ class ConsoleAuthTokensExtensionTestV231(ConsoleAuthTokensExtensionTestV21):
|
||||
self.assertRaises(webob.exc.HTTPNotFound,
|
||||
self.controller.show, self.req, fakes.FAKE_UUID)
|
||||
mock_validate.assert_called_once_with(self.context, fakes.FAKE_UUID)
|
||||
|
||||
|
||||
class ConsoleAuthTokensExtensionTestV299(ConsoleAuthTokensExtensionTestV21):
|
||||
|
||||
def setUp(self):
|
||||
super(ConsoleAuthTokensExtensionTestV299, self).setUp()
|
||||
self.req.api_version_request = api_version_request.APIVersionRequest(
|
||||
'2.99')
|
||||
|
||||
@mock.patch('nova.objects.ConsoleAuthToken.validate',
|
||||
return_value=objects.ConsoleAuthToken(
|
||||
instance_uuid=fakes.FAKE_UUID, host='fake_host',
|
||||
port='1234', internal_access_path='fake_access_path',
|
||||
console_type='webmks', token=fakes.FAKE_UUID))
|
||||
def test_get_console_connect_info(self, mock_validate):
|
||||
output = self.controller.show(self.req, fakes.FAKE_UUID)
|
||||
self.assertEqual(self._EXPECTED_OUTPUT_DB, output)
|
||||
mock_validate.assert_called_once_with(self.context, fakes.FAKE_UUID)
|
||||
|
||||
@mock.patch('nova.objects.ConsoleAuthToken.validate',
|
||||
return_value=objects.ConsoleAuthToken(
|
||||
instance_uuid=fakes.FAKE_UUID, host='fake_host',
|
||||
port='5900', tls_port='5901', internal_access_path=None,
|
||||
console_type='spice-direct', token=fakes.FAKE_UUID))
|
||||
def test_get_console_connect_info_spice(self, mock_validate):
|
||||
output = self.controller.show(self.req, fakes.FAKE_UUID)
|
||||
self.assertEqual(self._EXPECTED_OUTPUT_DB_SPICE, output)
|
||||
mock_validate.assert_called_once_with(self.context, fakes.FAKE_UUID)
|
||||
|
||||
@mock.patch('nova.objects.ConsoleAuthToken.validate',
|
||||
side_effect=exception.InvalidToken(token='***'))
|
||||
def test_get_console_connect_info_token_not_found(self, mock_validate):
|
||||
self.assertRaises(webob.exc.HTTPNotFound,
|
||||
self.controller.show, self.req, fakes.FAKE_UUID)
|
||||
mock_validate.assert_called_once_with(self.context, fakes.FAKE_UUID)
|
||||
|
@ -474,3 +474,35 @@ class ConsolesExtensionTestV28(ConsolesExtensionTestV26):
|
||||
'url': 'http://fake'}}, output)
|
||||
mock_handler.assert_called_once_with(self.context, self.instance,
|
||||
'webmks')
|
||||
|
||||
|
||||
class ConsolesExtensionTestV299(ConsolesExtensionTestV26):
|
||||
def setUp(self):
|
||||
super(ConsolesExtensionTestV299, self).setUp()
|
||||
self.req = fakes.HTTPRequest.blank('')
|
||||
self.context = self.req.environ['nova.context']
|
||||
self.req.api_version_request = api_version_request.APIVersionRequest(
|
||||
'2.99')
|
||||
self.controller = console_v21.RemoteConsolesController()
|
||||
|
||||
def test_create_spice_direct_console(self):
|
||||
mock_handler = mock.MagicMock()
|
||||
mock_handler.return_value = {'url': 'http://fake'}
|
||||
self.controller.handlers['spice'] = mock_handler
|
||||
|
||||
body = {
|
||||
'remote_console': {
|
||||
'protocol': 'spice',
|
||||
'type': 'spice-direct'
|
||||
}
|
||||
}
|
||||
output = self.controller.create(self.req, fakes.FAKE_UUID, body=body)
|
||||
self.assertEqual({
|
||||
'remote_console': {
|
||||
'protocol': 'spice',
|
||||
'type': 'spice-direct',
|
||||
'url': 'http://fake'
|
||||
}
|
||||
}, output)
|
||||
mock_handler.assert_called_once_with(self.context, self.instance,
|
||||
'spice-direct')
|
||||
|
@ -6,3 +6,15 @@ features:
|
||||
behavior, if set to true the SPICE consoles will require TLS
|
||||
protected connections. Unencrypted connections will be gracefully
|
||||
redirected to the TLS port via the SPICE protocol.
|
||||
- |
|
||||
This release adds a new console type, ``spice-direct`` which provides
|
||||
the connection information required to talk the native SPICE
|
||||
protocol directly to qemu on the hypervisor. This is intended to
|
||||
be fronted by a proxy which will handle authentication separately.
|
||||
This new console type is exposed in the Compute API v2.99
|
||||
microversion. To facilitate this proxying, a new config option
|
||||
``spice_direct_proxy_base_url`` is added to the spice configuration group.
|
||||
This option is used to construct a URL containing an access token for
|
||||
the console, and that access token can be turned into hypervisor
|
||||
connection information using the pre-existing
|
||||
os-console-auth-tokens API.
|
||||
|
Reference in New Issue
Block a user