From cbc263f6bc741734c4da11f8725e38a59c02f0e7 Mon Sep 17 00:00:00 2001 From: Michael Still Date: Sat, 22 Feb 2025 08:25:38 +1100 Subject: [PATCH] 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//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/ Which returns something like this: { "console": { "instance_uuid": , "host": , "port": , "tls_port": , "internal_access_path": null } } APIImpact Change-Id: I1e701cbabc0e2c435685e31465159eec09e3b1a0 --- .zuul.yaml | 2 +- api-ref/source/parameters.yaml | 11 ++- api-ref/source/servers-remote-consoles.inc | 21 ++++++ .../create-spice-direct-console-req.json | 6 ++ .../get-console-connect-info-get-resp.json | 9 +++ .../create-spice-direct-console-req.json | 6 ++ .../create-spice-direct-console-resp.json | 8 +++ .../versions/v21-version-get-resp.json | 4 +- .../versions/versions-get-resp.json | 4 +- nova/api/openstack/api_version_request.py | 3 +- .../openstack/compute/console_auth_tokens.py | 31 ++++++--- nova/api/openstack/compute/remote_consoles.py | 3 +- .../compute/rest_api_version_history.rst | 9 +++ .../compute/schemas/remote_consoles.py | 24 +++++++ nova/compute/manager.py | 30 +++++--- nova/conf/spice.py | 18 ++++- .../create-spice-direct-console-req.json.tpl | 6 ++ ...get-console-connect-info-get-resp.json.tpl | 9 +++ .../create-spice-direct-console-req.json.tpl | 7 ++ .../create-spice-direct-console-resp.json.tpl | 8 +++ .../test_console_auth_tokens.py | 35 ++++++++++ .../api_sample_tests/test_remote_consoles.py | 20 ++++++ .../compute/test_console_auth_tokens.py | 68 ++++++++++++++++--- .../openstack/compute/test_remote_consoles.py | 32 +++++++++ ...pice-direct-consoles-4bee40633633c971.yaml | 12 ++++ 25 files changed, 348 insertions(+), 38 deletions(-) create mode 100644 doc/api_samples/os-console-auth-tokens/v2.99/create-spice-direct-console-req.json create mode 100644 doc/api_samples/os-console-auth-tokens/v2.99/get-console-connect-info-get-resp.json create mode 100644 doc/api_samples/os-remote-consoles/v2.99/create-spice-direct-console-req.json create mode 100644 doc/api_samples/os-remote-consoles/v2.99/create-spice-direct-console-resp.json create mode 100644 nova/tests/functional/api_sample_tests/api_samples/os-console-auth-tokens/v2.99/create-spice-direct-console-req.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/os-console-auth-tokens/v2.99/get-console-connect-info-get-resp.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/os-remote-consoles/v2.99/create-spice-direct-console-req.json.tpl create mode 100644 nova/tests/functional/api_sample_tests/api_samples/os-remote-consoles/v2.99/create-spice-direct-console-resp.json.tpl diff --git a/.zuul.yaml b/.zuul.yaml index bcbeb56f73..88e2cfe459 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -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 diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index efc046fe76..05066afd00 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -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. diff --git a/api-ref/source/servers-remote-consoles.inc b/api-ref/source/servers-remote-consoles.inc index 9393b31eb3..a773ea60c4 100644 --- a/api-ref/source/servers-remote-consoles.inc +++ b/api-ref/source/servers-remote-consoles.inc @@ -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 diff --git a/doc/api_samples/os-console-auth-tokens/v2.99/create-spice-direct-console-req.json b/doc/api_samples/os-console-auth-tokens/v2.99/create-spice-direct-console-req.json new file mode 100644 index 0000000000..6a04d759fc --- /dev/null +++ b/doc/api_samples/os-console-auth-tokens/v2.99/create-spice-direct-console-req.json @@ -0,0 +1,6 @@ +{ + "remote_console": { + "protocol": "spice", + "type": "spice-direct" + } +} \ No newline at end of file diff --git a/doc/api_samples/os-console-auth-tokens/v2.99/get-console-connect-info-get-resp.json b/doc/api_samples/os-console-auth-tokens/v2.99/get-console-connect-info-get-resp.json new file mode 100644 index 0000000000..396c23cd9f --- /dev/null +++ b/doc/api_samples/os-console-auth-tokens/v2.99/get-console-connect-info-get-resp.json @@ -0,0 +1,9 @@ +{ + "console": { + "host": "fakespiceconsole.com", + "instance_uuid": "16802173-4e67-44f9-ba84-6d99080b81b5", + "internal_access_path": null, + "port": 6969, + "tls_port": 6970 + } +} \ No newline at end of file diff --git a/doc/api_samples/os-remote-consoles/v2.99/create-spice-direct-console-req.json b/doc/api_samples/os-remote-consoles/v2.99/create-spice-direct-console-req.json new file mode 100644 index 0000000000..552ab30e15 --- /dev/null +++ b/doc/api_samples/os-remote-consoles/v2.99/create-spice-direct-console-req.json @@ -0,0 +1,6 @@ +{ + "remote_console": { + "protocol": "spice", + "type": "spice-direct" + } +} diff --git a/doc/api_samples/os-remote-consoles/v2.99/create-spice-direct-console-resp.json b/doc/api_samples/os-remote-consoles/v2.99/create-spice-direct-console-resp.json new file mode 100644 index 0000000000..2a9e1ffb93 --- /dev/null +++ b/doc/api_samples/os-remote-consoles/v2.99/create-spice-direct-console-resp.json @@ -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" + } +} + diff --git a/doc/api_samples/versions/v21-version-get-resp.json b/doc/api_samples/versions/v21-version-get-resp.json index 0f7713fe29..0ad3022d82 100644 --- a/doc/api_samples/versions/v21-version-get-resp.json +++ b/doc/api_samples/versions/v21-version-get-resp.json @@ -19,8 +19,8 @@ } ], "status": "CURRENT", - "version": "2.98", + "version": "2.99", "min_version": "2.1", "updated": "2013-07-23T11:33:21Z" } -} +} \ No newline at end of file diff --git a/doc/api_samples/versions/versions-get-resp.json b/doc/api_samples/versions/versions-get-resp.json index 50f7be5071..3de8614ce6 100644 --- a/doc/api_samples/versions/versions-get-resp.json +++ b/doc/api_samples/versions/versions-get-resp.json @@ -22,9 +22,9 @@ } ], "status": "CURRENT", - "version": "2.98", + "version": "2.99", "min_version": "2.1", "updated": "2013-07-23T11:33:21Z" } ] -} +} \ No newline at end of file diff --git a/nova/api/openstack/api_version_request.py b/nova/api/openstack/api_version_request.py index 765dfeb2bd..7ceed28deb 100644 --- a/nova/api/openstack/api_version_request.py +++ b/nova/api/openstack/api_version_request.py @@ -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 diff --git a/nova/api/openstack/compute/console_auth_tokens.py b/nova/api/openstack/compute/console_auth_tokens.py index f891849af3..894bf279e0 100644 --- a/nova/api/openstack/compute/console_auth_tokens.py +++ b/nova/api/openstack/compute/console_auth_tokens.py @@ -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) diff --git a/nova/api/openstack/compute/remote_consoles.py b/nova/api/openstack/compute/remote_consoles.py index 408b11836a..acbe4d83fd 100644 --- a/nova/api/openstack/compute/remote_consoles.py +++ b/nova/api/openstack/compute/remote_consoles.py @@ -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) diff --git a/nova/api/openstack/compute/rest_api_version_history.rst b/nova/api/openstack/compute/rest_api_version_history.rst index c9f4a41f87..c445075e15 100644 --- a/nova/api/openstack/compute/rest_api_version_history.rst +++ b/nova/api/openstack/compute/rest_api_version_history.rst @@ -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}``. \ No newline at end of file diff --git a/nova/api/openstack/compute/schemas/remote_consoles.py b/nova/api/openstack/compute/schemas/remote_consoles.py index 1f57e95775..1e5b3a58a0 100644 --- a/nova/api/openstack/compute/schemas/remote_consoles.py +++ b/nova/api/openstack/compute/schemas/remote_consoles.py @@ -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': { diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 2af2ce563c..6556b2d286 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -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) diff --git a/nova/conf/spice.py b/nova/conf/spice.py index d01a83c3b9..4c457c8707 100644 --- a/nova/conf/spice.py +++ b/nova/conf/spice.py @@ -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', diff --git a/nova/tests/functional/api_sample_tests/api_samples/os-console-auth-tokens/v2.99/create-spice-direct-console-req.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/os-console-auth-tokens/v2.99/create-spice-direct-console-req.json.tpl new file mode 100644 index 0000000000..552ab30e15 --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/os-console-auth-tokens/v2.99/create-spice-direct-console-req.json.tpl @@ -0,0 +1,6 @@ +{ + "remote_console": { + "protocol": "spice", + "type": "spice-direct" + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/os-console-auth-tokens/v2.99/get-console-connect-info-get-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/os-console-auth-tokens/v2.99/get-console-connect-info-get-resp.json.tpl new file mode 100644 index 0000000000..ebf9220b91 --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/os-console-auth-tokens/v2.99/get-console-connect-info-get-resp.json.tpl @@ -0,0 +1,9 @@ +{ + "console": { + "host": "%(host)s", + "instance_uuid": "%(id)s", + "internal_access_path": null, + "port": %(port)s, + "tls_port": %(tls_port)s + } +} \ No newline at end of file diff --git a/nova/tests/functional/api_sample_tests/api_samples/os-remote-consoles/v2.99/create-spice-direct-console-req.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/os-remote-consoles/v2.99/create-spice-direct-console-req.json.tpl new file mode 100644 index 0000000000..b424d7d594 --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/os-remote-consoles/v2.99/create-spice-direct-console-req.json.tpl @@ -0,0 +1,7 @@ +{ + "remote_console": { + "protocol": "spice", + "type": "spice-direct" + } +} + diff --git a/nova/tests/functional/api_sample_tests/api_samples/os-remote-consoles/v2.99/create-spice-direct-console-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/os-remote-consoles/v2.99/create-spice-direct-console-resp.json.tpl new file mode 100644 index 0000000000..27e6c0ad3f --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/os-remote-consoles/v2.99/create-spice-direct-console-resp.json.tpl @@ -0,0 +1,8 @@ +{ + "remote_console": { + "protocol": "spice", + "type": "spice-direct", + "url": "http://127.0.0.1:13002/nova?token=%(uuid)s" + } +} + diff --git a/nova/tests/functional/api_sample_tests/test_console_auth_tokens.py b/nova/tests/functional/api_sample_tests/test_console_auth_tokens.py index 7c482fecc9..8a785095fb 100644 --- a/nova/tests/functional/api_sample_tests/test_console_auth_tokens.py +++ b/nova/tests/functional/api_sample_tests/test_console_auth_tokens.py @@ -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) diff --git a/nova/tests/functional/api_sample_tests/test_remote_consoles.py b/nova/tests/functional/api_sample_tests/test_remote_consoles.py index ae02ad3281..75d19547d7 100644 --- a/nova/tests/functional/api_sample_tests/test_remote_consoles.py +++ b/nova/tests/functional/api_sample_tests/test_remote_consoles.py @@ -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) diff --git a/nova/tests/unit/api/openstack/compute/test_console_auth_tokens.py b/nova/tests/unit/api/openstack/compute/test_console_auth_tokens.py index 2c08c99a3a..09050b25ba 100644 --- a/nova/tests/unit/api/openstack/compute/test_console_auth_tokens.py +++ b/nova/tests/unit/api/openstack/compute/test_console_auth_tokens.py @@ -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) diff --git a/nova/tests/unit/api/openstack/compute/test_remote_consoles.py b/nova/tests/unit/api/openstack/compute/test_remote_consoles.py index 150bd76001..4b53d76b3a 100644 --- a/nova/tests/unit/api/openstack/compute/test_remote_consoles.py +++ b/nova/tests/unit/api/openstack/compute/test_remote_consoles.py @@ -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') diff --git a/releasenotes/notes/spice-direct-consoles-4bee40633633c971.yaml b/releasenotes/notes/spice-direct-consoles-4bee40633633c971.yaml index 0149fa051d..03b67a4378 100644 --- a/releasenotes/notes/spice-direct-consoles-4bee40633633c971.yaml +++ b/releasenotes/notes/spice-direct-consoles-4bee40633633c971.yaml @@ -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.