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
|
tox_envlist: all
|
||||||
# bug #1940425 only affect ml2/ovn so we execute
|
# bug #1940425 only affect ml2/ovn so we execute
|
||||||
# test_live_migration_with_trunk in this job to keep
|
# 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:
|
devstack_localrc:
|
||||||
Q_AGENT: openvswitch
|
Q_AGENT: openvswitch
|
||||||
Q_ML2_TENANT_NETWORK_TYPE: vxlan
|
Q_ML2_TENANT_NETWORK_TYPE: vxlan
|
||||||
|
@@ -5993,8 +5993,9 @@ remote_console_protocol:
|
|||||||
remote_console_type:
|
remote_console_type:
|
||||||
description: |
|
description: |
|
||||||
The type of remote console. The valid values are ``novnc``,
|
The type of remote console. The valid values are ``novnc``,
|
||||||
``spice-html5``, ``serial``, and ``webmks``. The type
|
``spice-html5``, ``spice-direct``, ``serial``, and ``webmks``. The type
|
||||||
``webmks`` is added since Microversion ``2.8``.
|
``webmks`` was added in Microversion ``2.8``, and the type
|
||||||
|
``spice-direct`` was added in Microversion ``2.99``.
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
@@ -7102,6 +7103,12 @@ tenant_usages:
|
|||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: array
|
type: array
|
||||||
|
tls_port_number:
|
||||||
|
description: |
|
||||||
|
The port number of a port requiring a TLS connection.
|
||||||
|
in: body
|
||||||
|
required: false
|
||||||
|
type: integer
|
||||||
to_port:
|
to_port:
|
||||||
description: |
|
description: |
|
||||||
The port at end of range.
|
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
|
.. literalinclude:: ../../doc/api_samples/os-remote-consoles/v2.6/create-vnc-console-req.json
|
||||||
:language: javascript
|
: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
|
Response
|
||||||
--------
|
--------
|
||||||
|
|
||||||
@@ -55,6 +62,12 @@ Response
|
|||||||
.. literalinclude:: ../../doc/api_samples/os-remote-consoles/v2.6/create-vnc-console-resp.json
|
.. literalinclude:: ../../doc/api_samples/os-remote-consoles/v2.6/create-vnc-console-resp.json
|
||||||
:language: javascript
|
: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
|
Show Console Connection Information
|
||||||
===================================
|
===================================
|
||||||
@@ -90,9 +103,17 @@ Response
|
|||||||
- instance_uuid: instance_id_body
|
- instance_uuid: instance_id_body
|
||||||
- host: console_host
|
- host: console_host
|
||||||
- port: port_number
|
- port: port_number
|
||||||
|
- tls_port: tls_port_number
|
||||||
- internal_access_path: internal_access_path
|
- internal_access_path: internal_access_path
|
||||||
|
|
||||||
**Example Show Console Authentication Token**
|
**Example Show Console Authentication Token**
|
||||||
|
|
||||||
.. literalinclude:: ../../doc/api_samples/os-console-auth-tokens/v2.31/get-console-connect-info-get-resp.json
|
.. literalinclude:: ../../doc/api_samples/os-console-auth-tokens/v2.31/get-console-connect-info-get-resp.json
|
||||||
:language: javascript
|
: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,7 +19,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"status": "CURRENT",
|
"status": "CURRENT",
|
||||||
"version": "2.98",
|
"version": "2.99",
|
||||||
"min_version": "2.1",
|
"min_version": "2.1",
|
||||||
"updated": "2013-07-23T11:33:21Z"
|
"updated": "2013-07-23T11:33:21Z"
|
||||||
}
|
}
|
||||||
|
@@ -22,7 +22,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"status": "CURRENT",
|
"status": "CURRENT",
|
||||||
"version": "2.98",
|
"version": "2.99",
|
||||||
"min_version": "2.1",
|
"min_version": "2.1",
|
||||||
"updated": "2013-07-23T11:33:21Z"
|
"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
|
* 2.98 - Add support for returning embedded image properties in
|
||||||
``server show`` and ``server list --long`` and in the ``server
|
``server show`` and ``server list --long`` and in the ``server
|
||||||
rebuild`` responses.
|
rebuild`` responses.
|
||||||
|
* 2.99 - Add the spice-direct console type to the spice console protocol.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# The minimum and maximum versions of the API supported
|
# 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
|
# Note(cyeoh): This only applies for the v2.1 API once microversions
|
||||||
# support is fully merged. It does not affect the V2 API.
|
# support is fully merged. It does not affect the V2 API.
|
||||||
_MIN_API_VERSION = '2.1'
|
_MIN_API_VERSION = '2.1'
|
||||||
_MAX_API_VERSION = '2.98'
|
_MAX_API_VERSION = '2.99'
|
||||||
DEFAULT_API_VERSION = _MIN_API_VERSION
|
DEFAULT_API_VERSION = _MIN_API_VERSION
|
||||||
|
|
||||||
# Almost all proxy APIs which are related to network, images and baremetal
|
# Almost all proxy APIs which are related to network, images and baremetal
|
||||||
|
@@ -29,7 +29,7 @@ CONF = nova.conf.CONF
|
|||||||
|
|
||||||
class ConsoleAuthTokensController(wsgi.Controller):
|
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."""
|
"""Checks a console auth token and returns the related connect info."""
|
||||||
context = req.environ['nova.context']
|
context = req.environ['nova.context']
|
||||||
context.can(cat_policies.BASE_POLICY_NAME)
|
context.can(cat_policies.BASE_POLICY_NAME)
|
||||||
@@ -57,12 +57,19 @@ class ConsoleAuthTokensController(wsgi.Controller):
|
|||||||
if not connect_info:
|
if not connect_info:
|
||||||
raise webob.exc.HTTPNotFound(explanation=_("Token not found"))
|
raise webob.exc.HTTPNotFound(explanation=_("Token not found"))
|
||||||
|
|
||||||
return {'console': {
|
retval = {
|
||||||
|
'console': {
|
||||||
'instance_uuid': connect_info.instance_uuid,
|
'instance_uuid': connect_info.instance_uuid,
|
||||||
'host': connect_info.host,
|
'host': connect_info.host,
|
||||||
'port': connect_info.port,
|
'port': connect_info.port,
|
||||||
'internal_access_path': connect_info.internal_access_path,
|
'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.Controller.api_version("2.1", "2.30")
|
||||||
@wsgi.expected_errors((400, 401, 404))
|
@wsgi.expected_errors((400, 401, 404))
|
||||||
@@ -83,8 +90,14 @@ class ConsoleAuthTokensController(wsgi.Controller):
|
|||||||
raise webob.exc.HTTPBadRequest()
|
raise webob.exc.HTTPBadRequest()
|
||||||
|
|
||||||
@wsgi.Controller.api_version("2.31") # noqa
|
@wsgi.Controller.api_version("2.31") # noqa
|
||||||
|
@wsgi.Controller.api_version("2.31", "2.98") # noqa
|
||||||
@wsgi.expected_errors((400, 404))
|
@wsgi.expected_errors((400, 404))
|
||||||
@validation.query_schema(schema.show_query)
|
@validation.query_schema(schema.show_query)
|
||||||
@validation.response_body_schema(schema.show_response)
|
|
||||||
def show(self, req, id): # noqa
|
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.Controller.api_version("2.6")
|
||||||
@wsgi.expected_errors((400, 404, 409, 501))
|
@wsgi.expected_errors((400, 404, 409, 501))
|
||||||
@validation.schema(schema.create_v26, "2.6", "2.7")
|
@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):
|
def create(self, req, server_id, body):
|
||||||
context = req.environ['nova.context']
|
context = req.environ['nova.context']
|
||||||
instance = common.get_instance(self.compute_api, context, server_id)
|
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``
|
``GET /servers/{server_id}`` (server show), ``GET /servers/detail``
|
||||||
(list server --long) and in the rebuild case of
|
(list server --long) and in the rebuild case of
|
||||||
``POST /server/{server_id}/action`` (server rebuild) API response.
|
``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,
|
'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 = {
|
get_vnc_console_response = {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': {
|
'properties': {
|
||||||
|
@@ -7837,22 +7837,30 @@ class ComputeManager(manager.Manager):
|
|||||||
if not CONF.spice.enabled:
|
if not CONF.spice.enabled:
|
||||||
raise exception.ConsoleTypeUnavailable(console_type=console_type)
|
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)
|
raise exception.ConsoleTypeInvalid(console_type=console_type)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Retrieve connect info from driver, and then decorate with our
|
# Retrieve connect info from driver, and then decorate with our
|
||||||
# access info token
|
# access info token
|
||||||
console = self.driver.get_spice_console(context, instance)
|
console = self.driver.get_spice_console(context, instance)
|
||||||
console_auth = objects.ConsoleAuthToken(
|
fields = {
|
||||||
context=context,
|
'context': context,
|
||||||
console_type=console_type,
|
'console_type': console_type,
|
||||||
host=console.host,
|
'host': console.host,
|
||||||
port=console.port,
|
'port': console.port,
|
||||||
internal_access_path=console.internal_access_path,
|
'tls_port': console.tlsPort,
|
||||||
instance_uuid=instance.uuid,
|
'instance_uuid': instance.uuid
|
||||||
access_url_base=CONF.spice.html5proxy_base_url,
|
}
|
||||||
)
|
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)
|
console_auth.authorize(CONF.consoleauth.token_ttl)
|
||||||
connect_info = console.get_connection_info(
|
connect_info = console.get_connection_info(
|
||||||
console_auth.token, console_auth.access_url)
|
console_auth.token, console_auth.access_url)
|
||||||
@@ -7974,7 +7982,7 @@ class ComputeManager(manager.Manager):
|
|||||||
@wrap_exception()
|
@wrap_exception()
|
||||||
@wrap_instance_fault
|
@wrap_instance_fault
|
||||||
def validate_console_port(self, ctxt, instance, port, console_type):
|
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)
|
console_info = self.driver.get_spice_console(ctxt, instance)
|
||||||
elif console_type == "serial":
|
elif console_type == "serial":
|
||||||
console_info = self.driver.get_serial_console(ctxt, instance)
|
console_info = self.driver.get_serial_console(ctxt, instance)
|
||||||
|
@@ -161,6 +161,22 @@ Related options:
|
|||||||
* This option depends on ``html5proxy_host`` and ``html5proxy_port`` options.
|
* This option depends on ``html5proxy_host`` and ``html5proxy_port`` options.
|
||||||
The access URL returned by the compute node must have the host
|
The access URL returned by the compute node must have the host
|
||||||
and port where the ``nova-spicehtml5proxy`` service is listening.
|
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',
|
cfg.StrOpt('server_listen',
|
||||||
default='127.0.0.1',
|
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"] = ".*"
|
subs["internal_access_path"] = ".*"
|
||||||
self._verify_response('get-console-connect-info-get-resp', subs,
|
self._verify_response('get-console-connect-info-get-resp', subs,
|
||||||
response, 200)
|
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)
|
'create-mks-console-req', body)
|
||||||
self._verify_response('create-mks-console-resp', {'url': HTTP_RE},
|
self._verify_response('create-mks-console-resp', {'url': HTTP_RE},
|
||||||
response, 200)
|
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)
|
||||||
|
@@ -30,17 +30,34 @@ from nova.tests.unit.api.openstack import fakes
|
|||||||
class ConsoleAuthTokensExtensionTestV21(test.NoDBTestCase):
|
class ConsoleAuthTokensExtensionTestV21(test.NoDBTestCase):
|
||||||
controller_class = console_auth_tokens_v21
|
controller_class = console_auth_tokens_v21
|
||||||
|
|
||||||
_EXPECTED_OUTPUT = {'console': {'instance_uuid': fakes.FAKE_UUID,
|
_EXPECTED_OUTPUT = {
|
||||||
|
'console': {
|
||||||
|
'instance_uuid': fakes.FAKE_UUID,
|
||||||
'host': 'fake_host',
|
'host': 'fake_host',
|
||||||
'port': '1234',
|
'port': '1234',
|
||||||
'internal_access_path': fakes.FAKE_UUID}}
|
'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
|
# The database backend returns a ConsoleAuthToken.to_dict() and o.vo
|
||||||
# StringField are unicode. And the port is an IntegerField.
|
# StringField are unicode. And the port is an IntegerField.
|
||||||
_EXPECTED_OUTPUT_DB = copy.deepcopy(_EXPECTED_OUTPUT)
|
_EXPECTED_OUTPUT_DB = copy.deepcopy(_EXPECTED_OUTPUT)
|
||||||
_EXPECTED_OUTPUT_DB['console'].update(
|
_EXPECTED_OUTPUT_DB['console'].update(
|
||||||
{'host': 'fake_host', 'port': 1234,
|
{'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):
|
def setUp(self):
|
||||||
super(ConsoleAuthTokensExtensionTestV21, self).setUp()
|
super(ConsoleAuthTokensExtensionTestV21, self).setUp()
|
||||||
@@ -63,9 +80,9 @@ class ConsoleAuthTokensExtensionTestV231(ConsoleAuthTokensExtensionTestV21):
|
|||||||
'2.31')
|
'2.31')
|
||||||
|
|
||||||
@mock.patch('nova.objects.ConsoleAuthToken.validate',
|
@mock.patch('nova.objects.ConsoleAuthToken.validate',
|
||||||
return_value = objects.ConsoleAuthToken(
|
return_value=objects.ConsoleAuthToken(
|
||||||
instance_uuid=fakes.FAKE_UUID, host='fake_host',
|
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',
|
console_type='webmks',
|
||||||
token=fakes.FAKE_UUID))
|
token=fakes.FAKE_UUID))
|
||||||
def test_get_console_connect_info(self, mock_validate):
|
def test_get_console_connect_info(self, mock_validate):
|
||||||
@@ -79,3 +96,38 @@ class ConsoleAuthTokensExtensionTestV231(ConsoleAuthTokensExtensionTestV21):
|
|||||||
self.assertRaises(webob.exc.HTTPNotFound,
|
self.assertRaises(webob.exc.HTTPNotFound,
|
||||||
self.controller.show, self.req, fakes.FAKE_UUID)
|
self.controller.show, self.req, fakes.FAKE_UUID)
|
||||||
mock_validate.assert_called_once_with(self.context, 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)
|
'url': 'http://fake'}}, output)
|
||||||
mock_handler.assert_called_once_with(self.context, self.instance,
|
mock_handler.assert_called_once_with(self.context, self.instance,
|
||||||
'webmks')
|
'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
|
behavior, if set to true the SPICE consoles will require TLS
|
||||||
protected connections. Unencrypted connections will be gracefully
|
protected connections. Unencrypted connections will be gracefully
|
||||||
redirected to the TLS port via the SPICE protocol.
|
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