Skip to content

Commit

Permalink
Merge "Support for both microversion headers"
Browse files Browse the repository at this point in the history
  • Loading branch information
Jenkins authored and openstack-gerrit committed May 26, 2016
2 parents 432d4ec + bd199e3 commit dbf9bf0
Show file tree
Hide file tree
Showing 16 changed files with 190 additions and 68 deletions.
44 changes: 31 additions & 13 deletions api-guide/source/microversions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
3 changes: 2 additions & 1 deletion api-ref/source/versions.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion doc/api_samples/versions/v21-version-get-resp.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
}
],
"status": "CURRENT",
"version": "2.26",
"version": "2.27",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}
Expand Down
2 changes: 1 addition & 1 deletion doc/api_samples/versions/versions-get-resp.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
}
],
"status": "CURRENT",
"version": "2.26",
"version": "2.27",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}
Expand Down
34 changes: 25 additions & 9 deletions doc/source/api_microversion_dev.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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?
----------------------------------
Expand Down Expand Up @@ -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``.

Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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()
Expand Down
10 changes: 8 additions & 2 deletions nova/api/openstack/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,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):
Expand All @@ -127,13 +130,16 @@ def _filter_response_headers(self, response):

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:
Expand Down
4 changes: 3 additions & 1 deletion nova/api/openstack/api_version_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@
* 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
Expand All @@ -80,7 +82,7 @@
# 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


Expand Down
8 changes: 8 additions & 0 deletions nova/api/openstack/rest_api_version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
19 changes: 13 additions & 6 deletions nova/api/openstack/wsgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,10 @@
# 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'
Expand Down Expand Up @@ -230,7 +231,7 @@ def set_api_version_request(self):
"""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(
Expand Down Expand Up @@ -767,8 +768,11 @@ def _process_stack(self, request, action, action_args,

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

Expand Down Expand Up @@ -1121,9 +1125,12 @@ def __call__(self, req):

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'
Expand Down
6 changes: 4 additions & 2 deletions nova/tests/functional/api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,13 @@ def api_request(self, relative_uri, check_response_status=None,

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)

Expand Down
4 changes: 4 additions & 0 deletions nova/tests/functional/test_legacy_v2_compatible_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ def test_request_with_microversion_headers(self):
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"])

Expand All @@ -42,6 +44,8 @@ def test_request_without_addtional_properties_check(self):
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"])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def _make_request(self, url):
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')))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def _make_request(self, url, body=None):
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'
Expand Down

0 comments on commit dbf9bf0

Please sign in to comment.