Add support for next_min_version and not_before

The API-WG just approved the spec for version discovery documents to
optionally provide "next_min_version" and "not_before" information.

  http://specs.openstack.org/openstack/api-wg/guidelines/microversion_specification.html#version-discovery

The intended use of these is to communicate that at a point in the
future the service plans to raise the minimum microversion. It can't say
when that will happen, as a service does not know when deployers will
decide to upgrade their services. But it can communicate the earliest
date it's possible to happen, which would be the first date the service
itself would raise the minimum.

This can be used to emit warnings to users who are using a microversion
less than the next_min_version and to tell them how long they have to
think about it.

Currently keystoneauth will not consume these for that purpose. This
patch is merely about collecting the information from the discovery
document if it is there so a consumer can take action on it if they
wish.

Change-Id: Ibc404ef55eeae721a0d1d16e4e3e51ad77b5a75c
This commit is contained in:
Monty Taylor 2017-07-11 18:23:34 -05:00
parent de41fec992
commit 24b09f4088
3 changed files with 64 additions and 7 deletions

View File

@ -170,6 +170,9 @@ The overall transaction then has three parts:
Major API of a given service, as well as reporting the available microversion
ranges that endpoint supports, if any.
More information is available in the `API-WG Specs`_ on the topics of
`Microversions`_ and `Consuming the Catalog`_.
Authentication
--------------
@ -349,7 +352,11 @@ of ranges, lists of input values or ``latest`` version.
It can also be used to determine the `min_microversion` and `max_microversion`
supported by the API. If an API does not support microversions, the values for
both will be ``None``.
both will be ``None``. It will also contain values for `next_min_version` and
`not_before` if they exist for the endpoint, or ``None`` if they do not. The
:class:`keystoneauth1.discovery.EndpointData` object will always contain
microversion related attributes regardless of whether the REST document does
or not.
``get_endpoint_data`` makes use of the same cache as the rest of the discovery
process, so calling it should incur no undue expense. By default it will make
@ -357,3 +364,8 @@ at least one version discovery call so that it can fetch microversion metadata.
If the user knows a service does not support microversions and is merely
curious as to which major version was discovered, `discover_versions` can be
set to `False` to prevent fetching microversion metadata.
.. _API-WG Specs: http://specs.openstack.org/openstack/api-wg/
.. _Consuming the Catalog: http://specs.openstack.org/openstack/api-wg/guidelines/consuming-catalog.html
.. _Microversions: http://specs.openstack.org/openstack/api-wg/guidelines/microversion_specification.html#version-discovery

View File

@ -390,6 +390,10 @@ class Discover(object):
max_microversion = v.get('version') or None
if max_microversion:
max_microversion = normalize_version_number(max_microversion)
next_min_version = v.get('next_min_version') or None
if next_min_version:
next_min_version = normalize_version_number(next_min_version)
not_before = v.get('not_before') or None
self_url = None
collection_url = None
@ -416,6 +420,8 @@ class Discover(object):
'collection': collection_url,
'min_microversion': min_microversion,
'max_microversion': max_microversion,
'next_min_version': next_min_version,
'not_before': not_before,
'raw_status': v['status']})
versions.sort(key=lambda v: v['version'], reverse=reverse)
@ -581,7 +587,9 @@ class EndpointData(object):
api_version=None,
major_version=None,
min_microversion=None,
max_microversion=None):
max_microversion=None,
next_min_version=None,
not_before=None):
self.catalog_url = catalog_url
self.service_url = service_url
self.service_type = service_type
@ -595,6 +603,8 @@ class EndpointData(object):
self.major_version = major_version
self.min_microversion = min_microversion
self.max_microversion = max_microversion
self.next_min_version = next_min_version
self.not_before = not_before
self._saved_project_id = None
self._catalog_matches_version = False
self._catalog_matches_exactly = False
@ -615,7 +625,9 @@ class EndpointData(object):
api_version=self.api_version,
major_version=self.major_version,
min_microversion=self.min_microversion,
max_microversion=self.max_microversion)
max_microversion=self.max_microversion,
next_min_version=self.next_min_version,
not_before=self.not_before)
# Save cached discovery object - but we don't want to
# actually provide a constructor argument
new_data._disc = self._disc
@ -752,6 +764,8 @@ class EndpointData(object):
self.min_microversion = discovered_data['min_microversion']
self.max_microversion = discovered_data['max_microversion']
self.next_min_version = discovered_data['next_min_version']
self.not_before = discovered_data['not_before']
# TODO(mordred): these next two things should be done by Discover
# in versioned_data_for.

View File

@ -157,6 +157,8 @@ CINDER_EXAMPLES = {
"id": "v3.0",
"version": "3.27",
"min_version": "3.0",
"next_min_version": "3.4",
"not_before": "2019-12-31",
"links": [
{
"href": BASE_URL,
@ -415,21 +417,31 @@ class VersionDataTests(utils.TestCase):
# no version info in input
test_ok({},
{'min_microversion': None, 'max_microversion': None})
{'min_microversion': None, 'max_microversion': None,
'next_min_version': None, 'not_before': None})
# version => max_microversion
test_ok({'version': '2.2'},
{'min_microversion': None, 'max_microversion': (2, 2)})
{'min_microversion': None, 'max_microversion': (2, 2),
'next_min_version': None, 'not_before': None})
# max_version supersedes version (even if malformed). min_version &
# normalization.
test_ok({'min_version': '2', 'version': 'foo', 'max_version': '2.2'},
{'min_microversion': (2, 0), 'max_microversion': (2, 2)})
{'min_microversion': (2, 0), 'max_microversion': (2, 2),
'next_min_version': None, 'not_before': None})
# Edge case: min/max_version ignored if present but "empty"; version
# used for max_microversion.
test_ok({'min_version': '', 'version': '2.1', 'max_version': ''},
{'min_microversion': None, 'max_microversion': (2, 1)})
{'min_microversion': None, 'max_microversion': (2, 1),
'next_min_version': None, 'not_before': None})
# next_min_version set
test_ok({'min_version': '2', 'max_version': '2.2',
'next_min_version': '2.1', 'not_before': '2019-07-01'},
{'min_microversion': (2, 0), 'max_microversion': (2, 2),
'next_min_version': (2, 1), 'not_before': '2019-07-01'})
# Badly-formatted min_version
test_exc({'min_version': 'foo', 'max_version': '2.1'})
@ -440,6 +452,9 @@ class VersionDataTests(utils.TestCase):
# Badly-formatted version (when max_version omitted)
test_exc({'min_version': '2.1', 'version': 'foo'})
# Badly-formatted next_min_version
test_exc({'next_min_version': 'bogus', 'not_before': '2019-07-01'})
def test_data_for_url(self):
mock = self.requests_mock.get(V3_URL,
status_code=200,
@ -542,6 +557,8 @@ class VersionDataTests(utils.TestCase):
'collection': None,
'max_microversion': None,
'min_microversion': None,
'next_min_version': None,
'not_before': None,
'version': (1, 0),
'url': v1_url,
'raw_status': 'CURRENT',
@ -550,6 +567,8 @@ class VersionDataTests(utils.TestCase):
'collection': None,
'max_microversion': None,
'min_microversion': None,
'next_min_version': None,
'not_before': None,
'version': (2, 0),
'url': v2_url,
'raw_status': 'CURRENT',
@ -558,6 +577,8 @@ class VersionDataTests(utils.TestCase):
'collection': BASE_URL,
'max_microversion': (3, 27),
'min_microversion': (3, 0),
'next_min_version': (3, 4),
'not_before': u'2019-12-31',
'version': (3, 0),
'url': v3_url,
'raw_status': 'CURRENT',
@ -610,6 +631,8 @@ class VersionDataTests(utils.TestCase):
'collection': None,
'max_microversion': None,
'min_microversion': None,
'next_min_version': None,
'not_before': None,
'version': (1, 0),
'url': v1_url,
'raw_status': 'SUPPORTED',
@ -618,6 +641,8 @@ class VersionDataTests(utils.TestCase):
'collection': None,
'max_microversion': None,
'min_microversion': None,
'next_min_version': None,
'not_before': None,
'version': (1, 1),
'url': v1_url,
'raw_status': 'CURRENT',
@ -626,6 +651,8 @@ class VersionDataTests(utils.TestCase):
'collection': None,
'max_microversion': None,
'min_microversion': None,
'next_min_version': None,
'not_before': None,
'version': (2, 0),
'url': v2_url,
'raw_status': 'SUPPORTED',
@ -634,6 +661,8 @@ class VersionDataTests(utils.TestCase):
'collection': None,
'max_microversion': None,
'min_microversion': None,
'next_min_version': None,
'not_before': None,
'version': (2, 1),
'url': v2_url,
'raw_status': 'SUPPORTED',
@ -642,6 +671,8 @@ class VersionDataTests(utils.TestCase):
'collection': None,
'max_microversion': None,
'min_microversion': None,
'next_min_version': None,
'not_before': None,
'version': (2, 2),
'url': v2_url,
'raw_status': 'CURRENT',