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:
parent
14d6a424ff
commit
bd199e3f9b
@ -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
|
||||
|
@ -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
|
||||
|
@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.26",
|
||||
"version": "2.27",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.26",
|
||||
"version": "2.27",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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'
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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"])
|
||||
|
||||
|
@ -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')))
|
||||
|
@ -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'
|
||||
|
@ -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'
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user