Support for both microversion headers

In this change the new OpenStack-API-Version headers is allowed,
but not required, for requesting a microversion.

Both headers are accepted in the request and both headers are sent in
the response (both the header and its value, and the addition to the Vary
header).

Many tests which explicitly use a microversion header have been
updated to use both. This change is not 100% as most of the tests
are testing the handling of the value of the header, not which
header is involved.

Partially-Implements: blueprint modern-microversions
Change-Id: I68da13b5ba0c2f3357523e765a5b9db81899daf1
This commit is contained in:
Chris Dent
2016-03-31 17:16:51 +01:00
parent 14d6a424ff
commit bd199e3f9b
16 changed files with 190 additions and 68 deletions

View File

@@ -107,17 +107,28 @@ HTTP header::
X-OpenStack-Nova-API-Version: 2.4 X-OpenStack-Nova-API-Version: 2.4
Starting with microversion `2.27` it is also correct to use the
following header to specify the microversion::
OpenStack-API-Version: compute 2.27
.. note:: For more detail on this newer form see the `Microversion Specification
<http://specs.openstack.org/openstack/api-wg/guidelines/microversion_specification.html>`_.
This acts conceptually like the "Accept" header. Semantically this means: This acts conceptually like the "Accept" header. Semantically this means:
* If `X-OpenStack-Nova-API-Version` is not provided, act as if the minimum * If neither `X-OpenStack-Nova-API-Version` nor `OpenStack-API-Version`
supported microversion was specified. (specifying `compute`) is provided, act as if the minimum supported
microversion was specified.
* If `X-OpenStack-Nova-API-Version` is provided, respond with the API at * If both headers are provided, `OpenStack-API-Version` will be preferred.
that microversion. If that's outside of the range of microversions supported,
return 406 Not Acceptable.
* If `X-OpenStack-Nova-API-Version` is ``latest`` (special keyword), act as * If `X-OpenStack-Nova-API-Version` or `OpenStack-API-Version` is provided,
if maximum was specified. respond with the API at that microversion. If that's outside of the range
of microversions supported, return 406 Not Acceptable.
* If `X-OpenStack-Nova-API-Version` or `OpenStack-API-Version` has a value
of ``latest`` (special keyword), act as if maximum was specified.
.. warning:: The ``latest`` value is mostly meant for integration testing and .. warning:: The ``latest`` value is mostly meant for integration testing and
would be dangerous to rely on in client code since microversions are not would be dangerous to rely on in client code since microversions are not
@@ -129,14 +140,21 @@ This means that out of the box, an old client without any knowledge of
microversions can work with an OpenStack installation with microversions microversions can work with an OpenStack installation with microversions
support. support.
Two extra headers are always returned in the response: In microversions prior to `2.27` two extra headers are always returned in
the response::
* X-OpenStack-Nova-API-Version: microversion_number X-OpenStack-Nova-API-Version: microversion_number
* Vary: X-OpenStack-Nova-API-Version Vary: X-OpenStack-Nova-API-Version
The first header specifies the microversion number of the API which was The first header specifies the microversion number of the API which was
executed. executed.
The second header is used as a hint to caching proxies that the response The `Vary` header is used as a hint to caching proxies that the response
is also dependent on the X-OpenStack-Nova-API-Version and not just is also dependent on the microversion and not just the body and query
the body and query parameters. See :rfc:`2616` section 14.44 for details. parameters. See :rfc:`2616` section 14.44 for details.
From microversion `2.27` two additional headers are added to the
response::
OpenStack-API-Version: compute microversion_number
Vary: OpenStack-API-Version

View File

@@ -12,7 +12,8 @@ supports versioning. There are two kinds of versions in Nova.
- ''major versions'', which have dedicated urls - ''major versions'', which have dedicated urls
- ''microversions'', which can be requested through the use of the - ''microversions'', which can be requested through the use of the
``X-OpenStack-Nova-API-Version`` header ``X-OpenStack-Nova-API-Version`` header or since microversion 2.27
the ``OpenStack-API-Version`` header may also be used.
For more detail about Microversion, please reference: For more detail about Microversion, please reference:
`Microversions `Microversions

View File

@@ -19,7 +19,7 @@
} }
], ],
"status": "CURRENT", "status": "CURRENT",
"version": "2.26", "version": "2.27",
"min_version": "2.1", "min_version": "2.1",
"updated": "2013-07-23T11:33:21Z" "updated": "2013-07-23T11:33:21Z"
} }

View File

@@ -22,7 +22,7 @@
} }
], ],
"status": "CURRENT", "status": "CURRENT",
"version": "2.26", "version": "2.27",
"min_version": "2.1", "min_version": "2.1",
"updated": "2013-07-23T11:33:21Z" "updated": "2013-07-23T11:33:21Z"
} }

View File

@@ -9,9 +9,12 @@ to the API while preserving backward compatibility. The basic idea is
that a user has to explicitly ask for their request to be treated with that a user has to explicitly ask for their request to be treated with
a particular version of the API. So breaking changes can be added to a particular version of the API. So breaking changes can be added to
the API without breaking users who don't specifically ask for it. This the API without breaking users who don't specifically ask for it. This
is done with an HTTP header ``X-OpenStack-Nova-API-Version`` which is done with an HTTP header ``OpenStack-API-Version`` which has as its
is a monotonically increasing semantic version number starting from value a string containing the name of the service, ``compute``, and a
``2.1``. monotonically increasing semantic version number starting from ``2.1``.
The full form of the header takes the form::
OpenStack-API-Version: compute 2.1
If a user makes a request without specifying a version, they will get If a user makes a request without specifying a version, they will get
the ``DEFAULT_API_VERSION`` as defined in the ``DEFAULT_API_VERSION`` as defined in
@@ -29,8 +32,21 @@ responses from the server.
microversion but limit what is acceptable to the version range that it microversion but limit what is acceptable to the version range that it
understands at the time. understands at the time.
.. warning:: To maintain compatibility, an earlier form of the microversion
header is acceptable. It takes the form::
X-OpenStack-Nova-API-Version: 2.1
This form will continue to be supported until the ``DEFAULT_API_VERSION``
is raised to version ``2.27`` or higher.
Clients accessing deployments of the Nova API which are not yet
providing microversion ``2.27`` must use the older form.
For full details please read the `Kilo spec for microversions For full details please read the `Kilo spec for microversions
<http://git.openstack.org/cgit/openstack/nova-specs/tree/specs/kilo/implemented/api-microversions.rst>`_ <http://git.openstack.org/cgit/openstack/nova-specs/tree/specs/kilo/implemented/api-microversions.rst>`_
and `Microversion Specification
<http://specs.openstack.org/openstack/api-wg/guidelines/microversion_specification.html>`_.
When do I need a new Microversion? When do I need a new Microversion?
---------------------------------- ----------------------------------
@@ -217,7 +233,7 @@ In the controller class::
.... ....
This method would only be available if the caller had specified an This method would only be available if the caller had specified an
``X-OpenStack-Nova-API-Version`` of >= ``2.4``. If they had specified a ``OpenStack-API-Version`` of >= ``2.4``. If they had specified a
lower version (or not specified it and received the default of ``2.1``) lower version (or not specified it and received the default of ``2.1``)
the server would respond with ``HTTP/404``. the server would respond with ``HTTP/404``.
@@ -231,7 +247,7 @@ In the controller class::
.... ....
This method would only be available if the caller had specified an This method would only be available if the caller had specified an
``X-OpenStack-Nova-API-Version`` of <= ``2.4``. If ``2.5`` or later ``OpenStack-API-Version`` of <= ``2.4``. If ``2.5`` or later
is specified the server will respond with ``HTTP/404``. is specified the server will respond with ``HTTP/404``.
Changing a method's behavior Changing a method's behavior
@@ -333,9 +349,9 @@ necessary to add changes to other places which describe your change:
* Update the expected versions in affected tests, for example in * Update the expected versions in affected tests, for example in
``nova/tests/unit/api/openstack/compute/test_versions.py``. ``nova/tests/unit/api/openstack/compute/test_versions.py``.
* Update the get versions api sample files: * Update the get versions api sample file:
``doc/api_samples/versions/versions-get-resp.json`` and ``doc/api_samples/versions/versions-get-resp.json`` and
``nova/tests/functional/api_samples/versions/versions-get-resp.json.tpl``. ``doc/api_samples/versions/v21-version-get-resp.json``.
* Make a new commit to python-novaclient and update corresponding * Make a new commit to python-novaclient and update corresponding
files to enable the newly added microversion API. files to enable the newly added microversion API.
@@ -361,11 +377,11 @@ Testing Microversioned API Methods
---------------------------------- ----------------------------------
Testing a microversioned API method is very similar to a normal controller Testing a microversioned API method is very similar to a normal controller
method test, you just need to add the ``X-OpenStack-Nova-API-Version`` method test, you just need to add the ``OpenStack-API-Version``
header, for example:: header, for example::
req = fakes.HTTPRequest.blank('/testable/url/endpoint') req = fakes.HTTPRequest.blank('/testable/url/endpoint')
req.headers = {'X-OpenStack-Nova-API-Version': '2.2'} req.headers = {'OpenStack-API-Version': 'compute 2.28'}
req.api_version_request = api_version.APIVersionRequest('2.6') req.api_version_request = api_version.APIVersionRequest('2.6')
controller = controller.TestableController() controller = controller.TestableController()

View File

@@ -114,11 +114,14 @@ class LegacyV2CompatibleWrapper(base_wsgi.Middleware):
def _filter_request_headers(self, req): def _filter_request_headers(self, req):
"""For keeping same behavior with v2 API, ignores microversions """For keeping same behavior with v2 API, ignores microversions
HTTP header X-OpenStack-Nova-API-Version in the request. HTTP headers X-OpenStack-Nova-API-Version and OpenStack-API-Version
in the request.
""" """
if wsgi.API_VERSION_REQUEST_HEADER in req.headers: if wsgi.API_VERSION_REQUEST_HEADER in req.headers:
del req.headers[wsgi.API_VERSION_REQUEST_HEADER] del req.headers[wsgi.API_VERSION_REQUEST_HEADER]
if wsgi.LEGACY_API_VERSION_REQUEST_HEADER in req.headers:
del req.headers[wsgi.LEGACY_API_VERSION_REQUEST_HEADER]
return req return req
def _filter_response_headers(self, response): def _filter_response_headers(self, response):
@@ -128,13 +131,16 @@ class LegacyV2CompatibleWrapper(base_wsgi.Middleware):
if wsgi.API_VERSION_REQUEST_HEADER in response.headers: if wsgi.API_VERSION_REQUEST_HEADER in response.headers:
del response.headers[wsgi.API_VERSION_REQUEST_HEADER] del response.headers[wsgi.API_VERSION_REQUEST_HEADER]
if wsgi.LEGACY_API_VERSION_REQUEST_HEADER in response.headers:
del response.headers[wsgi.LEGACY_API_VERSION_REQUEST_HEADER]
if 'Vary' in response.headers: if 'Vary' in response.headers:
vary_headers = response.headers['Vary'].split(',') vary_headers = response.headers['Vary'].split(',')
filtered_vary = [] filtered_vary = []
for vary in vary_headers: for vary in vary_headers:
vary = vary.strip() vary = vary.strip()
if vary == wsgi.API_VERSION_REQUEST_HEADER: if (vary == wsgi.API_VERSION_REQUEST_HEADER or
vary == wsgi.LEGACY_API_VERSION_REQUEST_HEADER):
continue continue
filtered_vary.append(vary) filtered_vary.append(vary)
if filtered_vary: if filtered_vary:

View File

@@ -72,6 +72,8 @@ REST_API_VERSION_HISTORY = """REST API Version History:
* 2.25 - Make block_migration support 'auto' and remove * 2.25 - Make block_migration support 'auto' and remove
disk_over_commit for os-migrateLive. disk_over_commit for os-migrateLive.
* 2.26 - Adds support of server tags * 2.26 - Adds support of server tags
* 2.27 - Adds support for new-style microversion headers while
keeping support for the original style.
""" """
# The minimum and maximum versions of the API supported # The minimum and maximum versions of the API supported
@@ -80,7 +82,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.26" _MAX_API_VERSION = "2.27"
DEFAULT_API_VERSION = _MIN_API_VERSION DEFAULT_API_VERSION = _MIN_API_VERSION

View File

@@ -283,3 +283,11 @@ user documentation.
These filters can be combined. Also user can use more than one string tags These filters can be combined. Also user can use more than one string tags
for each filter. In this case string tags for each filter must be separated for each filter. In this case string tags for each filter must be separated
by comma: GET /servers?tags=red&tags-any=green,orange by comma: GET /servers?tags=red&tags-any=green,orange
2.27
----
Added support for the new form of microversion headers described in the
`Microversion Specification
<http://specs.openstack.org/openstack/api-wg/guidelines/microversion_specification.html>`_.
Both the original form of header and the new form is supported.

View File

@@ -71,9 +71,10 @@ DEFAULT_API_VERSION = "2.1"
# name of attribute to keep version method information # name of attribute to keep version method information
VER_METHOD_ATTR = 'versioned_methods' VER_METHOD_ATTR = 'versioned_methods'
# Name of header used by clients to request a specific version # Names of headers used by clients to request a specific version
# of the REST API # of the REST API
API_VERSION_REQUEST_HEADER = 'X-OpenStack-Nova-API-Version' API_VERSION_REQUEST_HEADER = 'OpenStack-API-Version'
LEGACY_API_VERSION_REQUEST_HEADER = 'X-OpenStack-Nova-API-Version'
ENV_LEGACY_V2 = 'openstack.legacy_v2' ENV_LEGACY_V2 = 'openstack.legacy_v2'
@@ -230,7 +231,7 @@ class Request(wsgi.Request):
"""Set API version request based on the request header information.""" """Set API version request based on the request header information."""
hdr_string = microversion_parse.get_version( hdr_string = microversion_parse.get_version(
self.headers, service_type='compute', self.headers, service_type='compute',
legacy_headers=[API_VERSION_REQUEST_HEADER]) legacy_headers=[LEGACY_API_VERSION_REQUEST_HEADER])
if hdr_string is None: if hdr_string is None:
self.api_version_request = api_version.APIVersionRequest( self.api_version_request = api_version.APIVersionRequest(
@@ -767,8 +768,11 @@ class Resource(wsgi.Application):
if not request.api_version_request.is_null(): if not request.api_version_request.is_null():
response.headers[API_VERSION_REQUEST_HEADER] = \ response.headers[API_VERSION_REQUEST_HEADER] = \
'compute ' + request.api_version_request.get_string()
response.headers[LEGACY_API_VERSION_REQUEST_HEADER] = \
request.api_version_request.get_string() request.api_version_request.get_string()
response.headers['Vary'] = API_VERSION_REQUEST_HEADER response.headers.add('Vary', API_VERSION_REQUEST_HEADER)
response.headers.add('Vary', LEGACY_API_VERSION_REQUEST_HEADER)
return response return response
@@ -1121,9 +1125,12 @@ class Fault(webob.exc.HTTPException):
if not req.api_version_request.is_null(): if not req.api_version_request.is_null():
self.wrapped_exc.headers[API_VERSION_REQUEST_HEADER] = \ self.wrapped_exc.headers[API_VERSION_REQUEST_HEADER] = \
'compute ' + req.api_version_request.get_string()
self.wrapped_exc.headers[LEGACY_API_VERSION_REQUEST_HEADER] = \
req.api_version_request.get_string() req.api_version_request.get_string()
self.wrapped_exc.headers['Vary'] = \ self.wrapped_exc.headers.add('Vary', API_VERSION_REQUEST_HEADER)
API_VERSION_REQUEST_HEADER self.wrapped_exc.headers.add('Vary',
LEGACY_API_VERSION_REQUEST_HEADER)
self.wrapped_exc.content_type = 'application/json' self.wrapped_exc.content_type = 'application/json'
self.wrapped_exc.charset = 'UTF-8' self.wrapped_exc.charset = 'UTF-8'

View File

@@ -171,11 +171,13 @@ class TestOpenStackClient(object):
headers = kwargs.setdefault('headers', {}) headers = kwargs.setdefault('headers', {})
headers['X-Auth-Token'] = auth_result['x-auth-token'] headers['X-Auth-Token'] = auth_result['x-auth-token']
if 'X-OpenStack-Nova-API-Version' in headers: if ('X-OpenStack-Nova-API-Version' in headers or
raise Exception('X-OpenStack-Nova-API-Version should be set on ' 'OpenStack-API-Version' in headers):
raise Exception('Microversion should be set via '
'microversion attribute in API client.') 'microversion attribute in API client.')
elif self.microversion: elif self.microversion:
headers['X-OpenStack-Nova-API-Version'] = self.microversion headers['X-OpenStack-Nova-API-Version'] = self.microversion
headers['OpenStack-API-Version'] = 'compute %s' % self.microversion
response = self.request(full_uri, **kwargs) response = self.request(full_uri, **kwargs)

View File

@@ -34,6 +34,8 @@ class LegacyV2CompatibleTestBase(test_servers.ServersTestBase):
response = self.api.api_post('os-keypairs', response = self.api.api_post('os-keypairs',
{"keypair": {"name": "test"}}) {"keypair": {"name": "test"}})
self.assertNotIn(wsgi.API_VERSION_REQUEST_HEADER, response.headers) self.assertNotIn(wsgi.API_VERSION_REQUEST_HEADER, response.headers)
self.assertNotIn(wsgi.LEGACY_API_VERSION_REQUEST_HEADER,
response.headers)
self.assertNotIn('Vary', response.headers) self.assertNotIn('Vary', response.headers)
self.assertNotIn('type', response.body["keypair"]) self.assertNotIn('type', response.body["keypair"])
@@ -42,6 +44,8 @@ class LegacyV2CompatibleTestBase(test_servers.ServersTestBase):
response = self.api.api_post('os-keypairs', response = self.api.api_post('os-keypairs',
{"keypair": {"name": "test", "foooooo": "barrrrrr"}}) {"keypair": {"name": "test", "foooooo": "barrrrrr"}})
self.assertNotIn(wsgi.API_VERSION_REQUEST_HEADER, response.headers) self.assertNotIn(wsgi.API_VERSION_REQUEST_HEADER, response.headers)
self.assertNotIn(wsgi.LEGACY_API_VERSION_REQUEST_HEADER,
response.headers)
self.assertNotIn('Vary', response.headers) self.assertNotIn('Vary', response.headers)
self.assertNotIn('type', response.body["keypair"]) self.assertNotIn('type', response.body["keypair"])

View File

@@ -90,7 +90,7 @@ class ExtendedServerAttributesTestV21(test.TestCase):
req = fakes.HTTPRequest.blank(url) req = fakes.HTTPRequest.blank(url)
req.headers['Accept'] = self.content_type req.headers['Accept'] = self.content_type
req.headers = {os_wsgi.API_VERSION_REQUEST_HEADER: req.headers = {os_wsgi.API_VERSION_REQUEST_HEADER:
self.wsgi_api_version} 'compute %s' % self.wsgi_api_version}
res = req.get_response( res = req.get_response(
fakes.wsgi_app_v21(init_only=('servers', fakes.wsgi_app_v21(init_only=('servers',
'os-extended-server-attributes'))) 'os-extended-server-attributes')))

View File

@@ -117,7 +117,7 @@ class ExtendedVolumesTestV21(test.TestCase):
req = webob.Request.blank('/v2/fake/servers' + url) req = webob.Request.blank('/v2/fake/servers' + url)
req.headers['Accept'] = self.content_type req.headers['Accept'] = self.content_type
req.headers = {os_wsgi.API_VERSION_REQUEST_HEADER: req.headers = {os_wsgi.API_VERSION_REQUEST_HEADER:
self.wsgi_api_version} 'compute %s' % self.wsgi_api_version}
if body: if body:
req.body = jsonutils.dump_as_bytes(body) req.body = jsonutils.dump_as_bytes(body)
req.method = 'POST' req.method = 'POST'

View File

@@ -20,7 +20,7 @@ from nova import test
from nova.tests.unit.api.openstack import fakes from nova.tests.unit.api.openstack import fakes
class MicroversionsTest(test.NoDBTestCase): class LegacyMicroversionsTest(test.NoDBTestCase):
header_name = 'X-OpenStack-Nova-API-Version' header_name = 'X-OpenStack-Nova-API-Version'
@@ -30,10 +30,19 @@ class MicroversionsTest(test.NoDBTestCase):
res = req.get_response(app) res = req.get_response(app)
self.assertEqual(ret_code, res.status_int) self.assertEqual(ret_code, res.status_int)
if ret_header: if ret_header:
if 'nova' not in self.header_name.lower():
ret_header = 'compute %s' % ret_header
self.assertEqual(ret_header, self.assertEqual(ret_header,
res.headers[self.header_name]) res.headers[self.header_name])
return res return res
def _make_header(self, req_header):
if 'nova' in self.header_name.lower():
headers = {self.header_name: req_header}
else:
headers = {self.header_name: 'compute %s' % req_header}
return headers
@mock.patch("nova.api.openstack.APIRouterV21.api_extension_namespace", @mock.patch("nova.api.openstack.APIRouterV21.api_extension_namespace",
return_value='nova.api.v21.test_extensions') return_value='nova.api.v21.test_extensions')
def test_microversions_no_header(self, mock_namespace): def test_microversions_no_header(self, mock_namespace):
@@ -53,8 +62,11 @@ class MicroversionsTest(test.NoDBTestCase):
self.assertEqual(200, res.status_int) self.assertEqual(200, res.status_int)
resp_json = jsonutils.loads(res.body) resp_json = jsonutils.loads(res.body)
self.assertEqual('val', resp_json['param']) self.assertEqual('val', resp_json['param'])
self.assertEqual("2.1", res.headers[self.header_name]) if 'nova' in self.header_name.lower():
self.assertEqual(self.header_name, res.headers['Vary']) self.assertEqual("2.1", res.headers[self.header_name])
else:
self.assertEqual("compute 2.1", res.headers[self.header_name])
self.assertIn(self.header_name, res.headers.getall('Vary'))
@mock.patch("nova.api.openstack.api_version_request.max_api_version") @mock.patch("nova.api.openstack.api_version_request.max_api_version")
@mock.patch("nova.api.openstack.APIRouterV21.api_extension_namespace", @mock.patch("nova.api.openstack.APIRouterV21.api_extension_namespace",
@@ -65,13 +77,16 @@ class MicroversionsTest(test.NoDBTestCase):
app = fakes.wsgi_app_v21(init_only='test-microversions') app = fakes.wsgi_app_v21(init_only='test-microversions')
req = fakes.HTTPRequest.blank('/v2/fake/microversions') req = fakes.HTTPRequest.blank('/v2/fake/microversions')
req.headers = {self.header_name: '2.3'} req.headers = self._make_header('2.3')
res = req.get_response(app) res = req.get_response(app)
self.assertEqual(200, res.status_int) self.assertEqual(200, res.status_int)
resp_json = jsonutils.loads(res.body) resp_json = jsonutils.loads(res.body)
self.assertEqual('val2', resp_json['param']) self.assertEqual('val2', resp_json['param'])
self.assertEqual("2.3", res.headers[self.header_name]) if 'nova' in self.header_name.lower():
self.assertEqual(self.header_name, res.headers['Vary']) self.assertEqual("2.3", res.headers[self.header_name])
else:
self.assertEqual("compute 2.3", res.headers[self.header_name])
self.assertIn(self.header_name, res.headers.getall('Vary'))
@mock.patch("nova.api.openstack.api_version_request.max_api_version") @mock.patch("nova.api.openstack.api_version_request.max_api_version")
@mock.patch("nova.api.openstack.APIRouterV21.api_extension_namespace", @mock.patch("nova.api.openstack.APIRouterV21.api_extension_namespace",
@@ -82,11 +97,14 @@ class MicroversionsTest(test.NoDBTestCase):
app = fakes.wsgi_app_v21(init_only='test-microversions') app = fakes.wsgi_app_v21(init_only='test-microversions')
req = fakes.HTTPRequest.blank('/v2/fake/microversions') req = fakes.HTTPRequest.blank('/v2/fake/microversions')
req.headers = {self.header_name: '3.0'} req.headers = self._make_header('3.0')
res = req.get_response(app) res = req.get_response(app)
self.assertEqual(400, res.status_int) self.assertEqual(400, res.status_int)
self.assertEqual("3.0", res.headers[self.header_name]) if 'nova' in self.header_name.lower():
self.assertEqual(self.header_name, res.headers['Vary']) self.assertEqual("3.0", res.headers[self.header_name])
else:
self.assertEqual("compute 3.0", res.headers[self.header_name])
self.assertIn(self.header_name, res.headers.getall('Vary'))
@mock.patch("nova.api.openstack.api_version_request.max_api_version") @mock.patch("nova.api.openstack.api_version_request.max_api_version")
@mock.patch("nova.api.openstack.APIRouterV21.api_extension_namespace", @mock.patch("nova.api.openstack.APIRouterV21.api_extension_namespace",
@@ -97,7 +115,7 @@ class MicroversionsTest(test.NoDBTestCase):
app = fakes.wsgi_app_v21(init_only='test-microversions') app = fakes.wsgi_app_v21(init_only='test-microversions')
req = fakes.HTTPRequest.blank(url) req = fakes.HTTPRequest.blank(url)
req.headers = {self.header_name: req_version} req.headers = self._make_header(req_version)
res = req.get_response(app) res = req.get_response(app)
self.assertEqual(200, res.status_int) self.assertEqual(200, res.status_int)
resp_json = jsonutils.loads(res.body) resp_json = jsonutils.loads(res.body)
@@ -123,7 +141,7 @@ class MicroversionsTest(test.NoDBTestCase):
app = fakes.wsgi_app_v21(init_only='test-microversions') app = fakes.wsgi_app_v21(init_only='test-microversions')
req = fakes.HTTPRequest.blank('/v2/fake/microversions2') req = fakes.HTTPRequest.blank('/v2/fake/microversions2')
req.headers = {self.header_name: '3.0'} req.headers = self._make_header('3.0')
res = req.get_response(app) res = req.get_response(app)
self.assertEqual(202, res.status_int) self.assertEqual(202, res.status_int)
resp_json = jsonutils.loads(res.body) resp_json = jsonutils.loads(res.body)
@@ -160,7 +178,7 @@ class MicroversionsTest(test.NoDBTestCase):
app = fakes.wsgi_app_v21(init_only='test-microversions') app = fakes.wsgi_app_v21(init_only='test-microversions')
req = fakes.HTTPRequest.blank('/v2/fake/microversions2') req = fakes.HTTPRequest.blank('/v2/fake/microversions2')
req.headers = {self.header_name: '3.7'} req.headers = self._make_header('3.7')
res = req.get_response(app) res = req.get_response(app)
self.assertEqual(406, res.status_int) self.assertEqual(406, res.status_int)
res_json = jsonutils.loads(res.body) res_json = jsonutils.loads(res.body)
@@ -177,7 +195,7 @@ class MicroversionsTest(test.NoDBTestCase):
app = fakes.wsgi_app_v21(init_only='test-microversions') app = fakes.wsgi_app_v21(init_only='test-microversions')
req = fakes.HTTPRequest.blank('/v2/fake/microversions3') req = fakes.HTTPRequest.blank('/v2/fake/microversions3')
req.method = 'POST' req.method = 'POST'
req.headers = {self.header_name: '2.2'} req.headers = self._make_header('2.2')
req.environ['CONTENT_TYPE'] = "application/json" req.environ['CONTENT_TYPE'] = "application/json"
req.body = jsonutils.dump_as_bytes({'dummy': {'val': 'foo'}}) req.body = jsonutils.dump_as_bytes({'dummy': {'val': 'foo'}})
@@ -185,8 +203,11 @@ class MicroversionsTest(test.NoDBTestCase):
self.assertEqual(200, res.status_int) self.assertEqual(200, res.status_int)
resp_json = jsonutils.loads(res.body) resp_json = jsonutils.loads(res.body)
self.assertEqual('create_val1', resp_json['param']) self.assertEqual('create_val1', resp_json['param'])
self.assertEqual("2.2", res.headers[self.header_name]) if 'nova' in self.header_name.lower():
self.assertEqual(self.header_name, res.headers['Vary']) self.assertEqual("2.2", res.headers[self.header_name])
else:
self.assertEqual("compute 2.2", res.headers[self.header_name])
self.assertIn(self.header_name, res.headers.getall('Vary'))
@mock.patch("nova.api.openstack.api_version_request.max_api_version") @mock.patch("nova.api.openstack.api_version_request.max_api_version")
@mock.patch("nova.api.openstack.APIRouterV21.api_extension_namespace", @mock.patch("nova.api.openstack.APIRouterV21.api_extension_namespace",
@@ -217,7 +238,7 @@ class MicroversionsTest(test.NoDBTestCase):
app = fakes.wsgi_app_v21(init_only='test-microversions') app = fakes.wsgi_app_v21(init_only='test-microversions')
req = fakes.HTTPRequest.blank('/v2/fake/microversions3/1') req = fakes.HTTPRequest.blank('/v2/fake/microversions3/1')
req.method = 'PUT' req.method = 'PUT'
req.headers = {self.header_name: '2.2'} req.headers = self._make_header('2.2')
req.body = jsonutils.dump_as_bytes({'dummy': {'inv_val': 'foo'}}) req.body = jsonutils.dump_as_bytes({'dummy': {'inv_val': 'foo'}})
req.environ['CONTENT_TYPE'] = "application/json" req.environ['CONTENT_TYPE'] = "application/json"
@@ -225,7 +246,10 @@ class MicroversionsTest(test.NoDBTestCase):
self.assertEqual(200, res.status_int) self.assertEqual(200, res.status_int)
resp_json = jsonutils.loads(res.body) resp_json = jsonutils.loads(res.body)
self.assertEqual('update_val1', resp_json['param']) self.assertEqual('update_val1', resp_json['param'])
self.assertEqual("2.2", res.headers[self.header_name]) if 'nova' in self.header_name.lower():
self.assertEqual("2.2", res.headers[self.header_name])
else:
self.assertEqual("compute 2.2", res.headers[self.header_name])
@mock.patch("nova.api.openstack.api_version_request.max_api_version") @mock.patch("nova.api.openstack.api_version_request.max_api_version")
@mock.patch("nova.api.openstack.APIRouterV21.api_extension_namespace", @mock.patch("nova.api.openstack.APIRouterV21.api_extension_namespace",
@@ -236,7 +260,7 @@ class MicroversionsTest(test.NoDBTestCase):
app = fakes.wsgi_app_v21(init_only='test-microversions') app = fakes.wsgi_app_v21(init_only='test-microversions')
req = fakes.HTTPRequest.blank('/v2/fake/microversions3/1') req = fakes.HTTPRequest.blank('/v2/fake/microversions3/1')
req.headers = {self.header_name: '2.10'} req.headers = self._make_header('2.10')
req.environ['CONTENT_TYPE'] = "application/json" req.environ['CONTENT_TYPE'] = "application/json"
req.method = 'PUT' req.method = 'PUT'
req.body = jsonutils.dump_as_bytes({'dummy': {'val2': 'foo'}}) req.body = jsonutils.dump_as_bytes({'dummy': {'val2': 'foo'}})
@@ -245,7 +269,10 @@ class MicroversionsTest(test.NoDBTestCase):
self.assertEqual(200, res.status_int) self.assertEqual(200, res.status_int)
resp_json = jsonutils.loads(res.body) resp_json = jsonutils.loads(res.body)
self.assertEqual('update_val1', resp_json['param']) self.assertEqual('update_val1', resp_json['param'])
self.assertEqual("2.10", res.headers[self.header_name]) if 'nova' in self.header_name.lower():
self.assertEqual("2.10", res.headers[self.header_name])
else:
self.assertEqual("compute 2.10", res.headers[self.header_name])
@mock.patch("nova.api.openstack.api_version_request.max_api_version") @mock.patch("nova.api.openstack.api_version_request.max_api_version")
@mock.patch("nova.api.openstack.APIRouterV21.api_extension_namespace", @mock.patch("nova.api.openstack.APIRouterV21.api_extension_namespace",
@@ -256,7 +283,7 @@ class MicroversionsTest(test.NoDBTestCase):
mock_maxver.return_value = api_version.APIVersionRequest("2.2") mock_maxver.return_value = api_version.APIVersionRequest("2.2")
app = fakes.wsgi_app_v21(init_only='test-microversions') app = fakes.wsgi_app_v21(init_only='test-microversions')
req = fakes.HTTPRequest.blank('/v2/fake/microversions4') req = fakes.HTTPRequest.blank('/v2/fake/microversions4')
req.headers = {self.header_name: version} req.headers = self._make_header(version)
req.environ['CONTENT_TYPE'] = "application/json" req.environ['CONTENT_TYPE'] = "application/json"
req.method = 'POST' req.method = 'POST'
@@ -264,6 +291,8 @@ class MicroversionsTest(test.NoDBTestCase):
self.assertEqual(200, res.status_int) self.assertEqual(200, res.status_int)
resp_json = jsonutils.loads(res.body) resp_json = jsonutils.loads(res.body)
self.assertEqual(expected_resp, resp_json['param']) self.assertEqual(expected_resp, resp_json['param'])
if 'nova' not in self.header_name.lower():
version = 'compute %s' % version
self.assertEqual(version, res.headers[self.header_name]) self.assertEqual(version, res.headers[self.header_name])
def test_microversions_inner_function_v22(self): def test_microversions_inner_function_v22(self):
@@ -306,7 +335,7 @@ class MicroversionsTest(test.NoDBTestCase):
app = fakes.wsgi_app_v21(init_only='test-microversions') app = fakes.wsgi_app_v21(init_only='test-microversions')
req = fakes.HTTPRequest.blank('/v2/fake/microversions3/1/action') req = fakes.HTTPRequest.blank('/v2/fake/microversions3/1/action')
if req_header: if req_header:
req.headers = {self.header_name: req_header} req.headers = self._make_header(req_header)
req.method = 'POST' req.method = 'POST'
req.body = jsonutils.dump_as_bytes({'foo': None}) req.body = jsonutils.dump_as_bytes({'foo': None})
@@ -324,3 +353,8 @@ class MicroversionsTest(test.NoDBTestCase):
def test_microversions_actions_no_header(self): def test_microversions_actions_no_header(self):
self._test_microversions_actions(202, "2.1", None) self._test_microversions_actions(202, "2.1", None)
class MicroversionsTest(LegacyMicroversionsTest):
header_name = 'OpenStack-API-Version'

View File

@@ -14,6 +14,7 @@ import inspect
import mock import mock
import six import six
import testscenarios
import webob import webob
from nova.api.openstack import api_version_request as api_version from nova.api.openstack import api_version_request as api_version
@@ -29,8 +30,25 @@ from nova.tests.unit import utils
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
class RequestTest(test.NoDBTestCase): class MicroversionedTest(testscenarios.WithScenarios, test.NoDBTestCase):
header_name = 'X-OpenStack-Nova-API-Version'
scenarios = [
('legacy-microverison', {
'header_name': 'X-OpenStack-Nova-API-Version',
}),
('modern-microversion', {
'header_name': 'OpenStack-API-Version',
})
]
def _make_microversion_header(self, value):
if 'nova' in self.header_name.lower():
return {self.header_name: value}
else:
return {self.header_name: 'compute %s' % value}
class RequestTest(MicroversionedTest):
def test_content_type_missing(self): def test_content_type_missing(self):
request = wsgi.Request.blank('/tests/123', method='POST') request = wsgi.Request.blank('/tests/123', method='POST')
@@ -165,7 +183,7 @@ class RequestTest(test.NoDBTestCase):
mock_maxver.return_value = api_version.APIVersionRequest("2.14") mock_maxver.return_value = api_version.APIVersionRequest("2.14")
request = wsgi.Request.blank('/') request = wsgi.Request.blank('/')
request.headers = {self.header_name: '2.14'} request.headers = self._make_microversion_header('2.14')
request.set_api_version_request() request.set_api_version_request()
self.assertEqual(api_version.APIVersionRequest("2.14"), self.assertEqual(api_version.APIVersionRequest("2.14"),
request.api_version_request) request.api_version_request)
@@ -175,14 +193,14 @@ class RequestTest(test.NoDBTestCase):
mock_maxver.return_value = api_version.APIVersionRequest("3.5") mock_maxver.return_value = api_version.APIVersionRequest("3.5")
request = wsgi.Request.blank('/') request = wsgi.Request.blank('/')
request.headers = {self.header_name: 'latest'} request.headers = self._make_microversion_header('latest')
request.set_api_version_request() request.set_api_version_request()
self.assertEqual(api_version.APIVersionRequest("3.5"), self.assertEqual(api_version.APIVersionRequest("3.5"),
request.api_version_request) request.api_version_request)
def test_api_version_request_header_invalid(self): def test_api_version_request_header_invalid(self):
request = wsgi.Request.blank('/') request = wsgi.Request.blank('/')
request.headers = {self.header_name: '2.1.3'} request.headers = self._make_microversion_header('2.1.3')
self.assertRaises(exception.InvalidAPIVersionString, self.assertRaises(exception.InvalidAPIVersionString,
request.set_api_version_request) request.set_api_version_request)
@@ -269,8 +287,7 @@ class JSONDeserializerTest(test.NoDBTestCase):
deserializer.deserialize, data) deserializer.deserialize, data)
class ResourceTest(test.NoDBTestCase): class ResourceTest(MicroversionedTest):
header_name = 'X-OpenStack-Nova-API-Version'
def get_req_id_header_name(self, request): def get_req_id_header_name(self, request):
header_name = 'x-openstack-request-id' header_name = 'x-openstack-request-id'
@@ -308,7 +325,7 @@ class ResourceTest(test.NoDBTestCase):
app = fakes.TestRouterV21(Controller()) app = fakes.TestRouterV21(Controller())
req = webob.Request.blank('/tests') req = webob.Request.blank('/tests')
req.headers = {self.header_name: version} req.headers = self._make_microversion_header(version)
response = req.get_response(app) response = req.get_response(app)
self.assertEqual(b'success', response.body) self.assertEqual(b'success', response.body)
self.assertEqual(response.status_int, 200) self.assertEqual(response.status_int, 200)
@@ -322,7 +339,7 @@ class ResourceTest(test.NoDBTestCase):
app = fakes.TestRouterV21(Controller()) app = fakes.TestRouterV21(Controller())
req = webob.Request.blank('/tests') req = webob.Request.blank('/tests')
req.headers = {self.header_name: invalid_version} req.headers = self._make_microversion_header(invalid_version)
response = req.get_response(app) response = req.get_response(app)
self.assertEqual(400, response.status_int) self.assertEqual(400, response.status_int)

View File

@@ -0,0 +1,7 @@
---
features:
- >
Microversions may now (with microversion 2.27) be requested with
the "OpenStack-API-Version: compute 2.27" header, in alignment with
OpenStack-wide standards. The original format,
"X-OpenStack-Nova-API-Version: 2.27", may still be used.