Update to microversion 2.27

This enables the use of the OpenStack-API-Version header, in
addition to the older X-OpenStack-Nova-API-Version header.

If the client knows that it is in a version less than 2.27, it
will not send the newer header.

If the client knows that is in a version >= 2.27 if it gets a
response that does not have the expected response header (the
newer one) it will warn. If it is in an older version, it will
warn about the other one missing.

A server that is 2.27 or beyond will accept both headers and
respond with both.

Change-Id: I1f7b1ca0fe795e4ecd333de761d96fff117969c0
This commit is contained in:
Chris Dent 2016-05-31 13:17:47 +01:00
parent 2253d02266
commit 082ed80369
4 changed files with 57 additions and 14 deletions

View File

@ -25,4 +25,4 @@ API_MIN_VERSION = api_versions.APIVersion("2.1")
# when client supported the max version, and bumped sequentially, otherwise
# the client may break due to server side new version may include some
# backward incompatible change.
API_MAX_VERSION = api_versions.APIVersion("2.26")
API_MAX_VERSION = api_versions.APIVersion("2.27")

View File

@ -29,7 +29,9 @@ if not LOG.handlers:
LOG.addHandler(logging.StreamHandler())
HEADER_NAME = "X-OpenStack-Nova-API-Version"
LEGACY_HEADER_NAME = "X-OpenStack-Nova-API-Version"
HEADER_NAME = "OpenStack-API-Version"
SERVICE_TYPE = "compute"
# key is a deprecated version and value is an alternative version.
DEPRECATED_VERSIONS = {"1.1": "2"}
@ -317,19 +319,25 @@ def discover_version(client, requested_version):
def update_headers(headers, api_version):
"""Set 'X-OpenStack-Nova-API-Version' header if api_version is not null"""
"""Set microversion headers if api_version is not null"""
if not api_version.is_null() and api_version.ver_minor != 0:
headers[HEADER_NAME] = api_version.get_string()
if not api_version.is_null():
version_string = api_version.get_string()
if api_version.ver_minor != 0:
headers[LEGACY_HEADER_NAME] = version_string
if api_version.ver_minor >= 27:
headers[HEADER_NAME] = '%s %s' % (SERVICE_TYPE, version_string)
def check_headers(response, api_version):
"""Checks that 'X-OpenStack-Nova-API-Version' header in response."""
if api_version.ver_minor > 0 and HEADER_NAME not in response.headers:
LOG.warning(_LW(
"Your request was processed by a Nova API which does not support "
"microversions (%s header is missing from response). "
"Warning: Response may be incorrect."), HEADER_NAME)
"""Checks that microversion header is in response."""
if api_version.ver_minor > 0:
if (api_version.ver_minor < 27
and LEGACY_HEADER_NAME not in response.headers):
_warn_missing_microversion_header(LEGACY_HEADER_NAME)
elif (api_version.ver_minor >= 27
and HEADER_NAME not in response.headers):
_warn_missing_microversion_header(HEADER_NAME)
def add_substitution(versioned_method):
@ -378,3 +386,11 @@ def wraps(start_version, end_version=None):
return substitution
return decor
def _warn_missing_microversion_header(header_name):
"""Log a warning about missing microversion response header."""
LOG.warning(_LW(
"Your request was processed by a Nova API which does not support "
"microversions (%s header is missing from response). "
"Warning: Response may be incorrect."), header_name)

View File

@ -144,6 +144,18 @@ class UpdateHeadersTestCase(utils.TestCase):
{"X-OpenStack-Nova-API-Version": api_version.get_string()},
headers)
def test_api_version_is_gte_27(self):
api_version = api_versions.APIVersion("2.27")
headers = {}
api_versions.update_headers(headers, api_version)
self.assertIn('X-OpenStack-Nova-API-Version', headers)
self.assertIn('OpenStack-API-Version', headers)
self.assertEqual(api_version.get_string(),
headers['X-OpenStack-Nova-API-Version'])
self.assertEqual('%s %s' % (api_versions.SERVICE_TYPE,
api_version.get_string()),
headers['OpenStack-API-Version'])
class CheckHeadersTestCase(utils.TestCase):
def setUp(self):
@ -152,8 +164,9 @@ class CheckHeadersTestCase(utils.TestCase):
self.mock_log = mock_log_patch.start()
self.addCleanup(mock_log_patch.stop)
def test_microversion_is_specified(self):
response = mock.MagicMock(headers={api_versions.HEADER_NAME: ""})
def test_legacy_microversion_is_specified(self):
response = mock.MagicMock(
headers={api_versions.LEGACY_HEADER_NAME: ""})
api_versions.check_headers(response, api_versions.APIVersion("2.2"))
self.assertFalse(self.mock_log.warning.called)
@ -161,8 +174,19 @@ class CheckHeadersTestCase(utils.TestCase):
api_versions.check_headers(response, api_versions.APIVersion("2.2"))
self.assertTrue(self.mock_log.warning.called)
def test_generic_microversion_is_specified(self):
response = mock.MagicMock(
headers={api_versions.HEADER_NAME: ""})
api_versions.check_headers(response, api_versions.APIVersion("2.27"))
self.assertFalse(self.mock_log.warning.called)
response = mock.MagicMock(headers={})
api_versions.check_headers(response, api_versions.APIVersion("2.27"))
self.assertTrue(self.mock_log.warning.called)
def test_microversion_is_not_specified(self):
response = mock.MagicMock(headers={api_versions.HEADER_NAME: ""})
response = mock.MagicMock(
headers={api_versions.LEGACY_HEADER_NAME: ""})
api_versions.check_headers(response, api_versions.APIVersion("2.2"))
self.assertFalse(self.mock_log.warning.called)

View File

@ -2776,6 +2776,9 @@ class ShellTest(utils.TestCase):
# new microversion, just an additional checks. See
# https://review.openstack.org/#/c/233076/ for more details)
20, # doesn't require any changes in novaclient
27, # NOTE(cdent): 27 adds support for updated microversion
# headers, and is tested in test_api_versions, but is
# not explicitly tested via wraps and _SUBSTITUTIONS.
])
versions_supported = set(range(0,
novaclient.API_MAX_VERSION.ver_minor + 1))