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
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:
* If `X-OpenStack-Nova-API-Version` is not provided, act as if the minimum
supported microversion was specified.
* If neither `X-OpenStack-Nova-API-Version` nor `OpenStack-API-Version`
(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
that microversion. If that's outside of the range of microversions supported,
return 406 Not Acceptable.
* If both headers are provided, `OpenStack-API-Version` will be preferred.
* If `X-OpenStack-Nova-API-Version` is ``latest`` (special keyword), act as
if maximum was specified.
* If `X-OpenStack-Nova-API-Version` or `OpenStack-API-Version` is provided,
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
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
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
* Vary: X-OpenStack-Nova-API-Version
X-OpenStack-Nova-API-Version: microversion_number
Vary: X-OpenStack-Nova-API-Version
The first header specifies the microversion number of the API which was
executed.
The second 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
the body and query parameters. See :rfc:`2616` section 14.44 for details.
The `Vary` header is used as a hint to caching proxies that the response
is also dependent on the microversion and not just the body and query
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
- ''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:
`Microversions

View File

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

View File

@ -22,7 +22,7 @@
}
],
"status": "CURRENT",
"version": "2.26",
"version": "2.27",
"min_version": "2.1",
"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
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
is done with an HTTP header ``X-OpenStack-Nova-API-Version`` which
is a monotonically increasing semantic version number starting from
``2.1``.
is done with an HTTP header ``OpenStack-API-Version`` which has as its
value a string containing the name of the service, ``compute``, and a
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
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
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
<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?
----------------------------------
@ -217,7 +233,7 @@ In the controller class::
....
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``)
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
``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``.
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
``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
``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
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
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::
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')
controller = controller.TestableController()

View File

@ -114,11 +114,14 @@ class LegacyV2CompatibleWrapper(base_wsgi.Middleware):
def _filter_request_headers(self, req):
"""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:
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
def _filter_response_headers(self, response):
@ -128,13 +131,16 @@ class LegacyV2CompatibleWrapper(base_wsgi.Middleware):
if wsgi.API_VERSION_REQUEST_HEADER in response.headers:
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:
vary_headers = response.headers['Vary'].split(',')
filtered_vary = []
for vary in vary_headers:
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
filtered_vary.append(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
disk_over_commit for os-migrateLive.
* 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
@ -80,7 +82,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.26"
_MAX_API_VERSION = "2.27"
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
for each filter. In this case string tags for each filter must be separated
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
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
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'
@ -230,7 +231,7 @@ class Request(wsgi.Request):
"""Set API version request based on the request header information."""
hdr_string = microversion_parse.get_version(
self.headers, service_type='compute',
legacy_headers=[API_VERSION_REQUEST_HEADER])
legacy_headers=[LEGACY_API_VERSION_REQUEST_HEADER])
if hdr_string is None:
self.api_version_request = api_version.APIVersionRequest(
@ -767,8 +768,11 @@ class Resource(wsgi.Application):
if not request.api_version_request.is_null():
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()
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
@ -1121,9 +1125,12 @@ class Fault(webob.exc.HTTPException):
if not req.api_version_request.is_null():
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()
self.wrapped_exc.headers['Vary'] = \
API_VERSION_REQUEST_HEADER
self.wrapped_exc.headers.add('Vary', 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.charset = 'UTF-8'

View File

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

View File

@ -34,6 +34,8 @@ class LegacyV2CompatibleTestBase(test_servers.ServersTestBase):
response = self.api.api_post('os-keypairs',
{"keypair": {"name": "test"}})
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('type', response.body["keypair"])
@ -42,6 +44,8 @@ class LegacyV2CompatibleTestBase(test_servers.ServersTestBase):
response = self.api.api_post('os-keypairs',
{"keypair": {"name": "test", "foooooo": "barrrrrr"}})
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('type', response.body["keypair"])

View File

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

View File

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

View File

@ -20,7 +20,7 @@ from nova import test
from nova.tests.unit.api.openstack import fakes
class MicroversionsTest(test.NoDBTestCase):
class LegacyMicroversionsTest(test.NoDBTestCase):
header_name = 'X-OpenStack-Nova-API-Version'
@ -30,10 +30,19 @@ class MicroversionsTest(test.NoDBTestCase):
res = req.get_response(app)
self.assertEqual(ret_code, res.status_int)
if ret_header:
if 'nova' not in self.header_name.lower():
ret_header = 'compute %s' % ret_header
self.assertEqual(ret_header,
res.headers[self.header_name])
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",
return_value='nova.api.v21.test_extensions')
def test_microversions_no_header(self, mock_namespace):
@ -53,8 +62,11 @@ class MicroversionsTest(test.NoDBTestCase):
self.assertEqual(200, res.status_int)
resp_json = jsonutils.loads(res.body)
self.assertEqual('val', resp_json['param'])
self.assertEqual("2.1", res.headers[self.header_name])
self.assertEqual(self.header_name, res.headers['Vary'])
if 'nova' in self.header_name.lower():
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.APIRouterV21.api_extension_namespace",
@ -65,13 +77,16 @@ class MicroversionsTest(test.NoDBTestCase):
app = fakes.wsgi_app_v21(init_only='test-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)
self.assertEqual(200, res.status_int)
resp_json = jsonutils.loads(res.body)
self.assertEqual('val2', resp_json['param'])
self.assertEqual("2.3", res.headers[self.header_name])
self.assertEqual(self.header_name, res.headers['Vary'])
if 'nova' in self.header_name.lower():
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.APIRouterV21.api_extension_namespace",
@ -82,11 +97,14 @@ class MicroversionsTest(test.NoDBTestCase):
app = fakes.wsgi_app_v21(init_only='test-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)
self.assertEqual(400, res.status_int)
self.assertEqual("3.0", res.headers[self.header_name])
self.assertEqual(self.header_name, res.headers['Vary'])
if 'nova' in self.header_name.lower():
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.APIRouterV21.api_extension_namespace",
@ -97,7 +115,7 @@ class MicroversionsTest(test.NoDBTestCase):
app = fakes.wsgi_app_v21(init_only='test-microversions')
req = fakes.HTTPRequest.blank(url)
req.headers = {self.header_name: req_version}
req.headers = self._make_header(req_version)
res = req.get_response(app)
self.assertEqual(200, res.status_int)
resp_json = jsonutils.loads(res.body)
@ -123,7 +141,7 @@ class MicroversionsTest(test.NoDBTestCase):
app = fakes.wsgi_app_v21(init_only='test-microversions')
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)
self.assertEqual(202, res.status_int)
resp_json = jsonutils.loads(res.body)
@ -160,7 +178,7 @@ class MicroversionsTest(test.NoDBTestCase):
app = fakes.wsgi_app_v21(init_only='test-microversions')
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)
self.assertEqual(406, res.status_int)
res_json = jsonutils.loads(res.body)
@ -177,7 +195,7 @@ class MicroversionsTest(test.NoDBTestCase):
app = fakes.wsgi_app_v21(init_only='test-microversions')
req = fakes.HTTPRequest.blank('/v2/fake/microversions3')
req.method = 'POST'
req.headers = {self.header_name: '2.2'}
req.headers = self._make_header('2.2')
req.environ['CONTENT_TYPE'] = "application/json"
req.body = jsonutils.dump_as_bytes({'dummy': {'val': 'foo'}})
@ -185,8 +203,11 @@ class MicroversionsTest(test.NoDBTestCase):
self.assertEqual(200, res.status_int)
resp_json = jsonutils.loads(res.body)
self.assertEqual('create_val1', resp_json['param'])
self.assertEqual("2.2", res.headers[self.header_name])
self.assertEqual(self.header_name, res.headers['Vary'])
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])
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.APIRouterV21.api_extension_namespace",
@ -217,7 +238,7 @@ class MicroversionsTest(test.NoDBTestCase):
app = fakes.wsgi_app_v21(init_only='test-microversions')
req = fakes.HTTPRequest.blank('/v2/fake/microversions3/1')
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.environ['CONTENT_TYPE'] = "application/json"
@ -225,7 +246,10 @@ class MicroversionsTest(test.NoDBTestCase):
self.assertEqual(200, res.status_int)
resp_json = jsonutils.loads(res.body)
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.APIRouterV21.api_extension_namespace",
@ -236,7 +260,7 @@ class MicroversionsTest(test.NoDBTestCase):
app = fakes.wsgi_app_v21(init_only='test-microversions')
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.method = 'PUT'
req.body = jsonutils.dump_as_bytes({'dummy': {'val2': 'foo'}})
@ -245,7 +269,10 @@ class MicroversionsTest(test.NoDBTestCase):
self.assertEqual(200, res.status_int)
resp_json = jsonutils.loads(res.body)
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.APIRouterV21.api_extension_namespace",
@ -256,7 +283,7 @@ class MicroversionsTest(test.NoDBTestCase):
mock_maxver.return_value = api_version.APIVersionRequest("2.2")
app = fakes.wsgi_app_v21(init_only='test-microversions')
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.method = 'POST'
@ -264,6 +291,8 @@ class MicroversionsTest(test.NoDBTestCase):
self.assertEqual(200, res.status_int)
resp_json = jsonutils.loads(res.body)
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])
def test_microversions_inner_function_v22(self):
@ -306,7 +335,7 @@ class MicroversionsTest(test.NoDBTestCase):
app = fakes.wsgi_app_v21(init_only='test-microversions')
req = fakes.HTTPRequest.blank('/v2/fake/microversions3/1/action')
if req_header:
req.headers = {self.header_name: req_header}
req.headers = self._make_header(req_header)
req.method = 'POST'
req.body = jsonutils.dump_as_bytes({'foo': None})
@ -324,3 +353,8 @@ class MicroversionsTest(test.NoDBTestCase):
def test_microversions_actions_no_header(self):
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 six
import testscenarios
import webob
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
class RequestTest(test.NoDBTestCase):
header_name = 'X-OpenStack-Nova-API-Version'
class MicroversionedTest(testscenarios.WithScenarios, test.NoDBTestCase):
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):
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")
request = wsgi.Request.blank('/')
request.headers = {self.header_name: '2.14'}
request.headers = self._make_microversion_header('2.14')
request.set_api_version_request()
self.assertEqual(api_version.APIVersionRequest("2.14"),
request.api_version_request)
@ -175,14 +193,14 @@ class RequestTest(test.NoDBTestCase):
mock_maxver.return_value = api_version.APIVersionRequest("3.5")
request = wsgi.Request.blank('/')
request.headers = {self.header_name: 'latest'}
request.headers = self._make_microversion_header('latest')
request.set_api_version_request()
self.assertEqual(api_version.APIVersionRequest("3.5"),
request.api_version_request)
def test_api_version_request_header_invalid(self):
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,
request.set_api_version_request)
@ -269,8 +287,7 @@ class JSONDeserializerTest(test.NoDBTestCase):
deserializer.deserialize, data)
class ResourceTest(test.NoDBTestCase):
header_name = 'X-OpenStack-Nova-API-Version'
class ResourceTest(MicroversionedTest):
def get_req_id_header_name(self, request):
header_name = 'x-openstack-request-id'
@ -308,7 +325,7 @@ class ResourceTest(test.NoDBTestCase):
app = fakes.TestRouterV21(Controller())
req = webob.Request.blank('/tests')
req.headers = {self.header_name: version}
req.headers = self._make_microversion_header(version)
response = req.get_response(app)
self.assertEqual(b'success', response.body)
self.assertEqual(response.status_int, 200)
@ -322,7 +339,7 @@ class ResourceTest(test.NoDBTestCase):
app = fakes.TestRouterV21(Controller())
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)
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.