Merge "Discourage 'version' and accept 'M.latest'"
This commit is contained in:
commit
029b2a37e9
|
@ -34,6 +34,28 @@ from keystoneauth1 import exceptions
|
|||
|
||||
_LOGGER = utils.get_logger(__name__)
|
||||
|
||||
LATEST = float('inf')
|
||||
|
||||
|
||||
def _str_or_latest(val):
|
||||
"""Convert val to a string, handling LATEST => 'latest'.
|
||||
|
||||
:param val: An int or the special value LATEST.
|
||||
:return: A string representation of val. If val was LATEST, the return is
|
||||
'latest'.
|
||||
"""
|
||||
return 'latest' if val == LATEST else str(val)
|
||||
|
||||
|
||||
def _int_or_latest(val):
|
||||
"""Convert val to an int or the special value LATEST.
|
||||
|
||||
:param val: An int()-able, or the string 'latest', or the special value
|
||||
LATEST.
|
||||
:return: An int, or the special value LATEST
|
||||
"""
|
||||
return LATEST if val == 'latest' or val == LATEST else int(val)
|
||||
|
||||
|
||||
@positional()
|
||||
def get_version_data(session, url, authenticated=None):
|
||||
|
@ -122,17 +144,26 @@ def normalize_version_number(version):
|
|||
|
||||
'v1.20.3', '1.20.3', (1, 20, 3), ['1', '20', '3']
|
||||
|
||||
The following all produce a return value of (LATEST, LATEST)::
|
||||
|
||||
'latest', 'vlatest', ('latest', 'latest'), (LATEST, LATEST)
|
||||
|
||||
The following all produce a return value of (2, LATEST)::
|
||||
|
||||
'2.latest', 'v2.latest', (2, LATEST), ('2', 'latest')
|
||||
|
||||
:param version: A version specifier in any of the following forms:
|
||||
String, possibly prefixed with 'v', containing one or more numbers
|
||||
separated by periods. Examples: 'v1', 'v1.2', '1.2.3', '123'
|
||||
*or* the string 'latest', separated by periods. Examples: 'v1',
|
||||
'v1.2', '1.2.3', '123', 'latest', '1.latest', 'v1.latest'.
|
||||
Integer. This will be assumed to be the major version, with a minor
|
||||
version of 0.
|
||||
Float. The integer part is assumed to be the major version; the
|
||||
decimal part the minor version.
|
||||
Non-string iterable comprising integers or integer strings.
|
||||
Examples: (1,), [1, 2], ('12', '34', '56')
|
||||
:return: A tuple of integers of len >= 2.
|
||||
:rtype: tuple(int)
|
||||
Non-string iterable comprising integers, integer strings, the string
|
||||
'latest', or the special value LATEST.
|
||||
Examples: (1,), [1, 2], ('12', '34', '56'), (LATEST,), (2, 'latest')
|
||||
:return: A tuple of len >= 2 comprising integers and/or LATEST.
|
||||
:raises TypeError: If the input version cannot be interpreted.
|
||||
"""
|
||||
# Copy the input var so the error presents the original value
|
||||
|
@ -142,7 +173,7 @@ def normalize_version_number(version):
|
|||
# processing. This ensures at least 1 decimal point if e.g. [1] is given.
|
||||
if not isinstance(ver, six.string_types):
|
||||
try:
|
||||
ver = '.'.join(map(str, ver))
|
||||
ver = '.'.join(map(_str_or_latest, ver))
|
||||
except TypeError:
|
||||
# Not an iterable
|
||||
pass
|
||||
|
@ -164,7 +195,7 @@ def normalize_version_number(version):
|
|||
|
||||
# If it's an int or float, turn it into a float string
|
||||
elif isinstance(ver, (int, float)):
|
||||
ver = str(float(ver))
|
||||
ver = _str_or_latest(float(ver))
|
||||
|
||||
# At this point, we should either have a string that contains numbers with
|
||||
# at least one decimal point, or something decidedly else.
|
||||
|
@ -176,9 +207,13 @@ def normalize_version_number(version):
|
|||
# Not a string
|
||||
pass
|
||||
|
||||
# Handle special case variants of just 'latest'
|
||||
if ver == 'latest' or tuple(ver) == ('latest',):
|
||||
return LATEST, LATEST
|
||||
|
||||
# It's either an interable, or something else that makes us sad.
|
||||
try:
|
||||
return tuple(map(int, ver))
|
||||
return tuple(map(_int_or_latest, ver))
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
|
@ -190,58 +225,97 @@ def _normalize_version_args(version, min_version, max_version):
|
|||
raise ValueError(
|
||||
"version is mutually exclusive with min_version and max_version")
|
||||
|
||||
if min_version == 'latest' and max_version not in (None, 'latest'):
|
||||
raise ValueError(
|
||||
"min_version is 'latest' and max_version is {max_version}"
|
||||
" but is only allowed to be 'latest' or None".format(
|
||||
max_version=max_version))
|
||||
if version:
|
||||
# Explode this into min_version and max_version
|
||||
min_version = normalize_version_number(version)
|
||||
max_version = (min_version[0], LATEST)
|
||||
return min_version, max_version
|
||||
|
||||
if version and version != 'latest':
|
||||
version = normalize_version_number(version)
|
||||
if min_version == 'latest':
|
||||
if max_version not in (None, 'latest'):
|
||||
raise ValueError(
|
||||
"min_version is 'latest' and max_version is {max_version}"
|
||||
" but is only allowed to be 'latest' or None".format(
|
||||
max_version=max_version))
|
||||
max_version = 'latest'
|
||||
|
||||
# Normalize e.g. empty string to None
|
||||
min_version = min_version or None
|
||||
max_version = max_version or None
|
||||
|
||||
if min_version:
|
||||
if min_version == 'latest':
|
||||
min_version = None
|
||||
max_version = 'latest'
|
||||
else:
|
||||
min_version = normalize_version_number(min_version)
|
||||
min_version = normalize_version_number(min_version)
|
||||
# If min_version was specified but max_version was not, max is latest.
|
||||
max_version = normalize_version_number(max_version or 'latest')
|
||||
|
||||
if max_version and max_version != 'latest':
|
||||
# NOTE(efried): We should be doing this instead:
|
||||
# max_version = normalize_version_number(max_version or 'latest')
|
||||
# However, see first NOTE(jamielennox) in EndpointData._set_version_info.
|
||||
if max_version:
|
||||
max_version = normalize_version_number(max_version)
|
||||
|
||||
return version, min_version, max_version
|
||||
if None not in (min_version, max_version) and max_version < min_version:
|
||||
raise ValueError("min_version cannot be greater than max_version")
|
||||
|
||||
return min_version, max_version
|
||||
|
||||
|
||||
def version_to_string(version):
|
||||
"""Turn a version tuple into a string.
|
||||
|
||||
:param tuple(int) version: A version represented as a tuple of ints.
|
||||
:param tuple version: A version represented as a tuple of ints. As a
|
||||
special case, a tuple member may be LATEST, which
|
||||
translates to 'latest'.
|
||||
:return: A version represented as a period-delimited string.
|
||||
"""
|
||||
return ".".join(map(str, version))
|
||||
# Special case
|
||||
if all(ver == LATEST for ver in version):
|
||||
return 'latest'
|
||||
|
||||
return ".".join(map(_str_or_latest, version))
|
||||
|
||||
|
||||
def version_between(min_version, max_version, candidate):
|
||||
"""Determine whether a candidate version is within a specified range.
|
||||
|
||||
:param min_version: Normalized lower bound. May be None. May be
|
||||
(LATEST, LATEST).
|
||||
:param max_version: Normalized upper bound. May be None. May be
|
||||
(LATEST, LATEST).
|
||||
:param candidate: Normalized candidate version to test. May not be None.
|
||||
:return: True if candidate is between min_version and max_version; False
|
||||
otherwise.
|
||||
:raises ValueError: If candidate is None or the input is not properly
|
||||
normalized.
|
||||
"""
|
||||
def is_normalized(ver):
|
||||
return normalize_version_number(ver) == ver
|
||||
|
||||
# A version can't be between a range that doesn't exist
|
||||
if not min_version and not max_version:
|
||||
return False
|
||||
|
||||
if candidate is None:
|
||||
raise ValueError("candidate cannot be None.")
|
||||
|
||||
if min_version is not None and not is_normalized(min_version):
|
||||
raise ValueError("min_version is not normalized.")
|
||||
if max_version is not None and not is_normalized(max_version):
|
||||
raise ValueError("max_version is not normalized.")
|
||||
if not is_normalized(candidate):
|
||||
raise ValueError("candidate is not normalized.")
|
||||
# This is only possible if args weren't run through _normalize_version_args
|
||||
if max_version is None and min_version is not None:
|
||||
raise ValueError("Can't use None as an upper bound.")
|
||||
|
||||
# If the candidate is less than the min_version, it's
|
||||
# not a match.
|
||||
if min_version:
|
||||
min_version = normalize_version_number(min_version)
|
||||
if candidate < min_version:
|
||||
return False
|
||||
|
||||
# Lack of max_version implies latest.
|
||||
if max_version == 'latest' or not max_version:
|
||||
return True
|
||||
|
||||
max_version = normalize_version_number(max_version)
|
||||
if version_match(max_version, candidate):
|
||||
return True
|
||||
if max_version < candidate:
|
||||
# not a match. None works here.
|
||||
if min_version is not None and candidate < min_version:
|
||||
return False
|
||||
|
||||
if max_version is not None and candidate > max_version:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
@ -271,6 +345,24 @@ def version_match(required, candidate):
|
|||
return True
|
||||
|
||||
|
||||
def _latest_soft_match(required, candidate):
|
||||
if not required:
|
||||
return False
|
||||
|
||||
if LATEST not in required:
|
||||
return False
|
||||
|
||||
if all(part == LATEST for part in required):
|
||||
return True
|
||||
|
||||
if required[0] == candidate[0] and required[1] == LATEST:
|
||||
return True
|
||||
|
||||
# TODO(efried): Do we need to handle >2-part version numbers here?
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _combine_relative_url(discovery_url, version_url):
|
||||
# NOTE(jamielennox): urllib.parse.urljoin allows the url to be relative
|
||||
# or even protocol-less. The additional trailing '/' makes urljoin respect
|
||||
|
@ -446,6 +538,10 @@ class Discover(object):
|
|||
version = normalize_version_number(version)
|
||||
|
||||
for data in self.version_data(reverse=True, **kwargs):
|
||||
# Since the data is reversed, the latest version is first. If
|
||||
# latest was requested, return it.
|
||||
if _latest_soft_match(version, data['version']):
|
||||
return data
|
||||
if version_match(version, data['version']):
|
||||
return data
|
||||
|
||||
|
@ -468,43 +564,39 @@ class Discover(object):
|
|||
data = self.data_for(version, **kwargs)
|
||||
return data['url'] if data else None
|
||||
|
||||
def versioned_data_for(self, version=None, url=None,
|
||||
def versioned_data_for(self, url=None,
|
||||
min_version=None, max_version=None,
|
||||
**kwargs):
|
||||
"""Return endpoint data for the service at a url.
|
||||
|
||||
version, min_version and max_version can all be given either as a
|
||||
string or a tuple.
|
||||
min_version and max_version can be given either as strings or tuples.
|
||||
|
||||
:param version: The version is the minimum version in the
|
||||
same major release as there should be no compatibility issues with
|
||||
using a version newer than the one asked for. If version is not
|
||||
given, the highest available version will be matched.
|
||||
:param string url: If url is given, the data will be returned for the
|
||||
endpoint data that has a self link matching the url.
|
||||
:param min_version: The minimum version that is acceptable. Mutually
|
||||
exclusive with version. If min_version is given with no max_version
|
||||
it is as if max version is 'latest'. If min_version is 'latest',
|
||||
max_version may only be 'latest' or None.
|
||||
:param max_version: The maximum version that is acceptable. Mutually
|
||||
exclusive with version. If min_version is given with no max_version
|
||||
it is as if max version is 'latest'. If min_version is 'latest',
|
||||
max_version may only be 'latest' or None.
|
||||
:param min_version: The minimum endpoint version that is acceptable. If
|
||||
min_version is given with no max_version it is as if max version is
|
||||
'latest'. If min_version is 'latest', max_version may only be
|
||||
'latest' or None.
|
||||
:param max_version: The maximum endpoint version that is acceptable. If
|
||||
min_version is given with no max_version it is as if max version is
|
||||
'latest'. If min_version is 'latest', max_version may only be
|
||||
'latest' or None.
|
||||
|
||||
:returns: the endpoint data for a URL that matches the required version
|
||||
(the format is described in version_data) or None if no
|
||||
match.
|
||||
:rtype: dict
|
||||
"""
|
||||
version, min_version, max_version = _normalize_version_args(
|
||||
version, min_version, max_version)
|
||||
no_version = not version and not max_version and not min_version
|
||||
min_version, max_version = _normalize_version_args(
|
||||
None, min_version, max_version)
|
||||
no_version = not max_version and not min_version
|
||||
|
||||
version_data = self.version_data(reverse=True, **kwargs)
|
||||
|
||||
# If we don't have to check a min_version, we can short
|
||||
# circuit anything else
|
||||
if 'latest' in (version, max_version) and not min_version:
|
||||
if (max_version == (LATEST, LATEST) and
|
||||
(not min_version or min_version == (LATEST, LATEST))):
|
||||
# because we reverse we can just take the first entry
|
||||
return version_data[0]
|
||||
|
||||
|
@ -520,7 +612,7 @@ class Discover(object):
|
|||
for data in version_data:
|
||||
if url and data['url'] and data['url'].rstrip('/') + '/' == url:
|
||||
return data
|
||||
if version and version_match(version, data['version']):
|
||||
if _latest_soft_match(min_version, data['version']):
|
||||
return data
|
||||
if version_between(min_version, max_version, data['version']):
|
||||
return data
|
||||
|
@ -537,27 +629,22 @@ class Discover(object):
|
|||
# We couldn't find a match.
|
||||
return None
|
||||
|
||||
def versioned_url_for(self, version=None,
|
||||
min_version=None, max_version=None, **kwargs):
|
||||
def versioned_url_for(self, min_version=None, max_version=None, **kwargs):
|
||||
"""Get the endpoint url for a version.
|
||||
|
||||
version, min_version and max_version can all be given either as a
|
||||
string or a tuple.
|
||||
min_version and max_version can be given either as strings or tuples.
|
||||
|
||||
:param version: The version is always a minimum version in the
|
||||
same major release as there should be no compatibility issues with
|
||||
using a version newer than the one asked for.
|
||||
:param min_version: The minimum version that is acceptable. Mutually
|
||||
exclusive with version. If min_version is given with no max_version
|
||||
it is as if max version is 'latest'.
|
||||
:param max_version: The maximum version that is acceptable. Mutually
|
||||
exclusive with version. If min_version is given with no max_version
|
||||
it is as if max version is 'latest'.
|
||||
:param min_version: The minimum version that is acceptable. If
|
||||
min_version is given with no max_version it is as if max version
|
||||
is 'latest'.
|
||||
:param max_version: The maximum version that is acceptable. If
|
||||
min_version is given with no max_version it is as if max version is
|
||||
'latest'.
|
||||
|
||||
:returns: The url for the specified version or None if no match.
|
||||
:rtype: str
|
||||
"""
|
||||
data = self.versioned_data_for(version, min_version=min_version,
|
||||
data = self.versioned_data_for(min_version=min_version,
|
||||
max_version=max_version, **kwargs)
|
||||
return data['url'] if data else None
|
||||
|
||||
|
@ -639,22 +726,19 @@ class EndpointData(object):
|
|||
return self.service_url or self.catalog_url
|
||||
|
||||
@positional(3)
|
||||
def get_versioned_data(self, session, version=None,
|
||||
allow=None, cache=None, allow_version_hack=True,
|
||||
project_id=None, discover_versions=True,
|
||||
def get_versioned_data(self, session, allow=None, cache=None,
|
||||
allow_version_hack=True, project_id=None,
|
||||
discover_versions=True,
|
||||
min_version=None, max_version=None):
|
||||
"""Run version discovery for the service described.
|
||||
|
||||
Performs Version Discovery and returns a new EndpointData object with
|
||||
information found.
|
||||
|
||||
version, min_version and max_version can all be given either as a
|
||||
string or a tuple.
|
||||
min_version and max_version can be given either as strings or tuples.
|
||||
|
||||
:param session: A session object that can be used for communication.
|
||||
:type session: keystoneauth1.session.Session
|
||||
:param version: The minimum major version required for this endpoint.
|
||||
Mutually exclusive with min_version and max_version.
|
||||
:param dict allow: Extra filters to pass when discovering API
|
||||
versions. (optional)
|
||||
:param dict cache: A dict to be used for caching results in
|
||||
|
@ -671,14 +755,12 @@ class EndpointData(object):
|
|||
if it's not neccessary to fulfill the
|
||||
major version request. (optional,
|
||||
defaults to True)
|
||||
:param min_version: The minimum version that is acceptable. Mutually
|
||||
exclusive with version. If min_version is given
|
||||
with no max_version it is as if max version is
|
||||
'latest'.
|
||||
:param max_version: The maximum version that is acceptable. Mutually
|
||||
exclusive with version. If min_version is given
|
||||
with no max_version it is as if max version is
|
||||
'latest'.
|
||||
:param min_version: The minimum version that is acceptable. If
|
||||
min_version is given with no max_version it is as
|
||||
if max version is 'latest'.
|
||||
:param max_version: The maximum version that is acceptable. If
|
||||
min_version is given with no max_version it is as
|
||||
if max version is 'latest'.
|
||||
|
||||
:returns: A new EndpointData with the requested versioned data.
|
||||
:rtype: :py:class:`keystoneauth1.discover.EndpointData`
|
||||
|
@ -686,8 +768,8 @@ class EndpointData(object):
|
|||
appropriate versioned data
|
||||
could not be discovered.
|
||||
"""
|
||||
version, min_version, max_version = _normalize_version_args(
|
||||
version, min_version, max_version)
|
||||
min_version, max_version = _normalize_version_args(
|
||||
None, min_version, max_version)
|
||||
|
||||
if not allow:
|
||||
allow = {}
|
||||
|
@ -696,19 +778,19 @@ class EndpointData(object):
|
|||
new_data = copy.copy(self)
|
||||
|
||||
new_data._set_version_info(
|
||||
session=session, version=version, allow=allow, cache=cache,
|
||||
session=session, allow=allow, cache=cache,
|
||||
allow_version_hack=allow_version_hack, project_id=project_id,
|
||||
discover_versions=discover_versions, min_version=min_version,
|
||||
max_version=max_version)
|
||||
return new_data
|
||||
|
||||
def _set_version_info(self, session, version, allow=None, cache=None,
|
||||
def _set_version_info(self, session, allow=None, cache=None,
|
||||
allow_version_hack=True, project_id=None,
|
||||
discover_versions=False,
|
||||
min_version=None, max_version=None):
|
||||
match_url = None
|
||||
|
||||
no_version = not version and not max_version and not min_version
|
||||
no_version = not max_version and not min_version
|
||||
if no_version and not discover_versions:
|
||||
# NOTE(jamielennox): This may not be the best thing to default to
|
||||
# but is here for backwards compatibility. It may be worth
|
||||
|
@ -727,27 +809,22 @@ class EndpointData(object):
|
|||
# satisfy the request without further work
|
||||
if self._disc:
|
||||
discovered_data = self._disc.versioned_data_for(
|
||||
version, min_version=min_version, max_version=max_version,
|
||||
min_version=min_version, max_version=max_version,
|
||||
url=match_url, **allow)
|
||||
if not discovered_data:
|
||||
self._run_discovery(
|
||||
session=session, cache=cache,
|
||||
version=version, min_version=min_version,
|
||||
max_version=max_version, project_id=project_id,
|
||||
allow_version_hack=allow_version_hack,
|
||||
min_version=min_version, max_version=max_version,
|
||||
project_id=project_id, allow_version_hack=allow_version_hack,
|
||||
discover_versions=discover_versions)
|
||||
if not self._disc:
|
||||
return
|
||||
discovered_data = self._disc.versioned_data_for(
|
||||
version, min_version=min_version, max_version=max_version,
|
||||
min_version=min_version, max_version=max_version,
|
||||
url=match_url, **allow)
|
||||
|
||||
if not discovered_data:
|
||||
if version:
|
||||
raise exceptions.DiscoveryFailure(
|
||||
"Version {version} requested, but was not found".format(
|
||||
version=version_to_string(version)))
|
||||
elif min_version and not max_version:
|
||||
if min_version and not max_version:
|
||||
raise exceptions.DiscoveryFailure(
|
||||
"Minimum version {min_version} was not found".format(
|
||||
min_version=version_to_string(min_version)))
|
||||
|
@ -787,13 +864,12 @@ class EndpointData(object):
|
|||
self.service_url = url
|
||||
|
||||
@positional(1)
|
||||
def _run_discovery(self, session, cache, version, min_version,
|
||||
max_version, project_id,
|
||||
allow_version_hack, discover_versions):
|
||||
def _run_discovery(self, session, cache, min_version, max_version,
|
||||
project_id, allow_version_hack, discover_versions):
|
||||
tried = set()
|
||||
|
||||
for vers_url in self._get_discovery_url_choices(
|
||||
version=version, project_id=project_id,
|
||||
project_id=project_id,
|
||||
allow_version_hack=allow_version_hack,
|
||||
min_version=min_version,
|
||||
max_version=max_version):
|
||||
|
@ -850,12 +926,12 @@ class EndpointData(object):
|
|||
" found and allow_version_hack was False")
|
||||
|
||||
def _get_discovery_url_choices(
|
||||
self, version=None, project_id=None, allow_version_hack=True,
|
||||
self, project_id=None, allow_version_hack=True,
|
||||
min_version=None, max_version=None):
|
||||
"""Find potential locations for version discovery URLs.
|
||||
|
||||
version, min_version and max_version are already normalized, so will
|
||||
either be None, 'latest' or a tuple.
|
||||
min_version and max_version are already normalized, so will either be
|
||||
None or a tuple.
|
||||
"""
|
||||
url = urllib.parse.urlparse(self.url)
|
||||
url_parts = url.path.split('/')
|
||||
|
@ -891,14 +967,12 @@ class EndpointData(object):
|
|||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
is_between = version_between(
|
||||
min_version, max_version, url_version)
|
||||
exact_match = (version and version != 'latest' and
|
||||
version_match(version, url_version))
|
||||
is_between = version_between(min_version, max_version, url_version)
|
||||
exact_match = (is_between and max_version and
|
||||
max_version[0] == url_version[0])
|
||||
high_match = (is_between and max_version and
|
||||
max_version != 'latest' and
|
||||
max_version[1] != LATEST and
|
||||
version_match(max_version, url_version))
|
||||
|
||||
if exact_match or is_between:
|
||||
self._catalog_matches_version = True
|
||||
self._catalog_matches_exactly = exact_match
|
||||
|
|
|
@ -157,12 +157,10 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
|
|||
return False
|
||||
|
||||
def get_endpoint_data(self, session, service_type=None, interface=None,
|
||||
region_name=None, service_name=None, version=None,
|
||||
allow={}, allow_version_hack=True,
|
||||
discover_versions=True, skip_discovery=False,
|
||||
min_version=None, max_version=None,
|
||||
endpoint_override=None,
|
||||
**kwargs):
|
||||
region_name=None, service_name=None, allow={},
|
||||
allow_version_hack=True, discover_versions=True,
|
||||
skip_discovery=False, min_version=None,
|
||||
max_version=None, endpoint_override=None, **kwargs):
|
||||
"""Return a valid endpoint data for a service.
|
||||
|
||||
If a valid token is not present then a new one will be fetched using
|
||||
|
@ -190,8 +188,6 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
|
|||
(optional)
|
||||
:param string service_name: The name of the service in the catalog.
|
||||
(optional)
|
||||
:param version: The minimum version number required for this
|
||||
endpoint. (optional)
|
||||
:param dict allow: Extra filters to pass when discovering API
|
||||
versions. (optional)
|
||||
:param bool allow_version_hack: Allow keystoneauth to hack up catalog
|
||||
|
@ -220,6 +216,7 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
|
|||
but version discovery will be run.
|
||||
Sets allow_version_hack to False
|
||||
(optional)
|
||||
:param kwargs: Ignored.
|
||||
|
||||
:raises keystoneauth1.exceptions.http.HttpError: An error from an
|
||||
invalid HTTP response.
|
||||
|
@ -227,6 +224,9 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
|
|||
:return: Valid EndpointData or None if not available.
|
||||
:rtype: `keystoneauth1.discover.EndpointData` or None
|
||||
"""
|
||||
min_version, max_version = discover._normalize_version_args(
|
||||
None, min_version, max_version)
|
||||
|
||||
# NOTE(jamielennox): if you specifically ask for requests to be sent to
|
||||
# the auth url then we can ignore many of the checks. Typically if you
|
||||
# are asking for the auth endpoint it means that there is no catalog to
|
||||
|
@ -283,7 +283,7 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
|
|||
|
||||
try:
|
||||
return endpoint_data.get_versioned_data(
|
||||
session, version,
|
||||
session,
|
||||
project_id=project_id,
|
||||
min_version=min_version,
|
||||
max_version=max_version,
|
||||
|
@ -295,7 +295,7 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
|
|||
exceptions.ConnectionError):
|
||||
# If a version was requested, we didn't find it, return
|
||||
# None.
|
||||
if version or max_version or min_version:
|
||||
if max_version or min_version:
|
||||
return None
|
||||
# If one wasn't, then the endpoint_data we already have
|
||||
# should be fine
|
||||
|
@ -361,6 +361,10 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
|
|||
:return: A valid endpoint URL or None if not available.
|
||||
:rtype: string or None
|
||||
"""
|
||||
# Explode `version` into min_version and max_version - everything below
|
||||
# here uses the latter rather than the former.
|
||||
min_version, max_version = discover._normalize_version_args(
|
||||
version, min_version, max_version)
|
||||
# Set discover_versions to False since we're only going to return
|
||||
# a URL. Fetching the microversion data would be needlessly
|
||||
# expensive in the common case. However, discover_versions=False
|
||||
|
@ -369,11 +373,8 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
|
|||
endpoint_data = self.get_endpoint_data(
|
||||
session, service_type=service_type, interface=interface,
|
||||
region_name=region_name, service_name=service_name,
|
||||
version=version, allow=allow,
|
||||
min_version=min_version,
|
||||
max_version=max_version,
|
||||
discover_versions=False,
|
||||
skip_discovery=skip_discovery,
|
||||
allow=allow, min_version=min_version, max_version=max_version,
|
||||
discover_versions=False, skip_discovery=skip_discovery,
|
||||
allow_version_hack=allow_version_hack, **kwargs)
|
||||
return endpoint_data.url if endpoint_data else None
|
||||
|
||||
|
|
|
@ -434,6 +434,11 @@ class Session(object):
|
|||
# TODO(mordred) Validate that the requested microversion works
|
||||
# with the microversion range we found in discovery.
|
||||
microversion = discover.normalize_version_number(microversion)
|
||||
# Can't specify a M.latest microversion
|
||||
if (microversion[0] != discover.LATEST and
|
||||
discover.LATEST in microversion[1:]):
|
||||
raise TypeError(
|
||||
"Specifying a '{major}.latest' microversion is not allowed.")
|
||||
microversion = discover.version_to_string(microversion)
|
||||
if not service_type:
|
||||
if endpoint_filter and 'service_type' in endpoint_filter:
|
||||
|
@ -942,6 +947,7 @@ class Session(object):
|
|||
return kwargs['endpoint_override']
|
||||
|
||||
auth = self._auth_required(auth, 'determine endpoint URL')
|
||||
|
||||
return auth.get_endpoint(self, **kwargs)
|
||||
|
||||
def get_endpoint_data(self, auth=None, **kwargs):
|
||||
|
|
|
@ -535,11 +535,13 @@ class CommonIdentityTests(object):
|
|||
data_v2 = a.get_endpoint_data(session=s,
|
||||
service_type='compute',
|
||||
interface='admin',
|
||||
version=(2, 0))
|
||||
min_version=(2, 0),
|
||||
max_version=(2, discover.LATEST))
|
||||
data_v3 = a.get_endpoint_data(session=s,
|
||||
service_type='compute',
|
||||
interface='admin',
|
||||
version=(3, 0))
|
||||
min_version=(3, 0),
|
||||
max_version=(3, discover.LATEST))
|
||||
|
||||
self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v2.0', data_v2.url)
|
||||
self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v3', data_v3.url)
|
||||
|
@ -566,15 +568,26 @@ class CommonIdentityTests(object):
|
|||
interface='admin')
|
||||
self.assertEqual(v3_compute, data.url)
|
||||
|
||||
v2_data = data.get_versioned_data(s, version='2.0')
|
||||
v2_data = data.get_versioned_data(s, min_version='2.0',
|
||||
max_version='2.latest')
|
||||
self.assertEqual(v2_compute, v2_data.url)
|
||||
self.assertEqual(v2_compute, v2_data.service_url)
|
||||
self.assertEqual(self.TEST_COMPUTE_ADMIN, v2_data.catalog_url)
|
||||
|
||||
v3_data = data.get_versioned_data(s, version='3.0')
|
||||
self.assertEqual(v3_compute, v3_data.url)
|
||||
self.assertEqual(v3_compute, v3_data.service_url)
|
||||
self.assertEqual(self.TEST_COMPUTE_ADMIN, v3_data.catalog_url)
|
||||
# Variants that all return v3 data
|
||||
for vkwargs in (dict(min_version='3.0', max_version='3.latest'),
|
||||
# min/max spans major versions
|
||||
dict(min_version='2.0', max_version='3.latest'),
|
||||
# latest major max
|
||||
dict(min_version='2.0', max_version='latest'),
|
||||
# implicit max
|
||||
dict(min_version='2.0'),
|
||||
# implicit min/max
|
||||
dict()):
|
||||
v3_data = data.get_versioned_data(s, **vkwargs)
|
||||
self.assertEqual(v3_compute, v3_data.url)
|
||||
self.assertEqual(v3_compute, v3_data.service_url)
|
||||
self.assertEqual(self.TEST_COMPUTE_ADMIN, v3_data.catalog_url)
|
||||
|
||||
def test_interface_list(self):
|
||||
|
||||
|
@ -637,7 +650,8 @@ class CommonIdentityTests(object):
|
|||
data.url)
|
||||
|
||||
v3_data = data.get_versioned_data(
|
||||
s, version='3.0', project_id=self.project_id)
|
||||
s, min_version='3.0', max_version='3.latest',
|
||||
project_id=self.project_id)
|
||||
|
||||
self.assertEqual(self.TEST_VOLUME.versions['v3'].service.public,
|
||||
v3_data.url)
|
||||
|
@ -651,7 +665,8 @@ class CommonIdentityTests(object):
|
|||
# find the unversioned endpoint
|
||||
self.requests_mock.get(self.TEST_VOLUME.unversioned.public, resps)
|
||||
v2_data = data.get_versioned_data(
|
||||
s, version='2.0', project_id=self.project_id)
|
||||
s, min_version='2.0', max_version='2.latest',
|
||||
project_id=self.project_id)
|
||||
|
||||
# Even though we never requested volumev2 from the catalog, we should
|
||||
# wind up re-constructing it via version discovery and re-appending
|
||||
|
@ -700,7 +715,8 @@ class CommonIdentityTests(object):
|
|||
# it should fetch the unversioned endpoint
|
||||
v2_data = s.get_endpoint_data(service_type='volumev3',
|
||||
interface='public',
|
||||
version='2.0',
|
||||
min_version='2.0',
|
||||
max_version='2.latest',
|
||||
project_id=self.project_id)
|
||||
|
||||
# Even though we never requested volumev2 from the catalog, we should
|
||||
|
@ -718,7 +734,8 @@ class CommonIdentityTests(object):
|
|||
# request for v2, we should have all the relevant data cached in the
|
||||
# discovery object - and should not fetch anything new.
|
||||
v3_data = v2_data.get_versioned_data(
|
||||
s, version='3.0', project_id=self.project_id)
|
||||
s, min_version='3.0', max_version='3.latest',
|
||||
project_id=self.project_id)
|
||||
|
||||
self.assertEqual(self.TEST_VOLUME.versions['v3'].service.public,
|
||||
v3_data.url)
|
||||
|
@ -1104,7 +1121,8 @@ class CatalogHackTests(utils.TestCase):
|
|||
data = sess.get_endpoint_data(endpoint_override=self.OTHER_URL,
|
||||
service_type=self.IDENTITY,
|
||||
interface='public',
|
||||
version=(2, 0))
|
||||
min_version=(2, 0),
|
||||
max_version=(2, discover.LATEST))
|
||||
|
||||
self.assertTrue(common_m.called)
|
||||
self.assertEqual(self.OTHER_URL, data.url)
|
||||
|
@ -1308,9 +1326,8 @@ class CatalogHackTests(utils.TestCase):
|
|||
endpoint = sess.get_endpoint(service_type=self.IDENTITY,
|
||||
min_version='1', max_version='2')
|
||||
|
||||
# We should make one more calls
|
||||
# TODO(mordred) optimize this - we can peek in the cache
|
||||
self.assertTrue(v2_m.called)
|
||||
# We should make no calls - we peek in the cache
|
||||
self.assertFalse(v2_m.called)
|
||||
self.assertFalse(common_m.called)
|
||||
|
||||
# And get the v2 url
|
||||
|
|
|
@ -303,13 +303,114 @@ class DiscoverUtils(utils.TestCase):
|
|||
assertVersion([1, 40], (1, 40))
|
||||
assertVersion((1,), (1, 0))
|
||||
assertVersion(['1'], (1, 0))
|
||||
assertVersion('latest', (discover.LATEST, discover.LATEST))
|
||||
assertVersion(['latest'], (discover.LATEST, discover.LATEST))
|
||||
assertVersion(discover.LATEST, (discover.LATEST, discover.LATEST))
|
||||
assertVersion((discover.LATEST,), (discover.LATEST, discover.LATEST))
|
||||
assertVersion('10.latest', (10, discover.LATEST))
|
||||
assertVersion((10, 'latest'), (10, discover.LATEST))
|
||||
assertVersion((10, discover.LATEST), (10, discover.LATEST))
|
||||
|
||||
versionRaises(None)
|
||||
versionRaises('hello')
|
||||
versionRaises('1.a')
|
||||
versionRaises('vacuum')
|
||||
versionRaises('')
|
||||
versionRaises(('1', 'a'))
|
||||
|
||||
def test_version_args(self):
|
||||
"""Validate _normalize_version_args."""
|
||||
def assert_min_max(in_ver, in_min, in_max, out_min, out_max):
|
||||
self.assertEqual(
|
||||
(out_min, out_max),
|
||||
discover._normalize_version_args(in_ver, in_min, in_max))
|
||||
|
||||
def normalize_raises(ver, min, max):
|
||||
self.assertRaises(ValueError,
|
||||
discover._normalize_version_args, ver, min, max)
|
||||
|
||||
assert_min_max(None, None, None,
|
||||
None, None)
|
||||
assert_min_max(None, None, 'v1.2',
|
||||
None, (1, 2))
|
||||
assert_min_max(None, 'v1.2', 'latest',
|
||||
(1, 2), (discover.LATEST, discover.LATEST))
|
||||
assert_min_max(None, 'v1.2', '1.6',
|
||||
(1, 2), (1, 6))
|
||||
assert_min_max(None, 'v1.2', '1.latest',
|
||||
(1, 2), (1, discover.LATEST))
|
||||
assert_min_max(None, 'latest', 'latest',
|
||||
(discover.LATEST, discover.LATEST),
|
||||
(discover.LATEST, discover.LATEST))
|
||||
assert_min_max(None, 'latest', None,
|
||||
(discover.LATEST, discover.LATEST),
|
||||
(discover.LATEST, discover.LATEST))
|
||||
assert_min_max(None, (1, 2), None,
|
||||
(1, 2), (discover.LATEST, discover.LATEST))
|
||||
assert_min_max('', ('1', '2'), (1, 6),
|
||||
(1, 2), (1, 6))
|
||||
assert_min_max(None, ('1', '2'), (1, discover.LATEST),
|
||||
(1, 2), (1, discover.LATEST))
|
||||
assert_min_max('v1.2', '', None,
|
||||
(1, 2), (1, discover.LATEST))
|
||||
assert_min_max('1.latest', None, '',
|
||||
(1, discover.LATEST), (1, discover.LATEST))
|
||||
assert_min_max('v1', None, None,
|
||||
(1, 0), (1, discover.LATEST))
|
||||
assert_min_max('latest', None, None,
|
||||
(discover.LATEST, discover.LATEST),
|
||||
(discover.LATEST, discover.LATEST))
|
||||
|
||||
normalize_raises('v1', 'v2', None)
|
||||
normalize_raises('v1', None, 'v2')
|
||||
normalize_raises(None, 'latest', 'v1')
|
||||
normalize_raises(None, 'v1.2', 'v1.1')
|
||||
normalize_raises(None, 'v1.2', 1)
|
||||
|
||||
def test_version_to_string(self):
|
||||
def assert_string(inp, out):
|
||||
self.assertEqual(out, discover.version_to_string(inp))
|
||||
|
||||
assert_string((discover.LATEST,), 'latest')
|
||||
assert_string((discover.LATEST, discover.LATEST), 'latest')
|
||||
assert_string((discover.LATEST, discover.LATEST, discover.LATEST),
|
||||
'latest')
|
||||
assert_string((1,), '1')
|
||||
assert_string((1, 2), '1.2')
|
||||
assert_string((1, discover.LATEST), '1.latest')
|
||||
|
||||
def test_version_between(self):
|
||||
def good(minver, maxver, cand):
|
||||
self.assertTrue(discover.version_between(minver, maxver, cand))
|
||||
|
||||
def bad(minver, maxver, cand):
|
||||
self.assertFalse(discover.version_between(minver, maxver, cand))
|
||||
|
||||
def exc(minver, maxver, cand):
|
||||
self.assertRaises(ValueError,
|
||||
discover.version_between, minver, maxver, cand)
|
||||
|
||||
good((1, 0), (1, 0), (1, 0))
|
||||
good((1, 0), (1, 10), (1, 2))
|
||||
good(None, (1, 10), (1, 2))
|
||||
good((1, 20), (2, 0), (1, 21))
|
||||
good((1, 0), (2, discover.LATEST), (1, 21))
|
||||
good((1, 0), (2, discover.LATEST), (1, discover.LATEST))
|
||||
good((1, 50), (2, discover.LATEST), (2, discover.LATEST))
|
||||
|
||||
bad((discover.LATEST, discover.LATEST),
|
||||
(discover.LATEST, discover.LATEST), (1, 0))
|
||||
bad(None, None, (1, 0))
|
||||
bad((1, 50), (2, discover.LATEST), (3, 0))
|
||||
bad((1, 50), (2, discover.LATEST), (3, discover.LATEST))
|
||||
bad((1, 50), (2, 5), (2, discover.LATEST))
|
||||
|
||||
exc((1, 0), (1, 0), None)
|
||||
exc('v1.0', (1, 0), (1, 0))
|
||||
exc((1, 0), 'v1.0', (1, 0))
|
||||
exc((1, 0), (1, 0), 'v1.0')
|
||||
exc((1, 0), None, (1, 0))
|
||||
|
||||
|
||||
class VersionDataTests(utils.TestCase):
|
||||
|
||||
|
@ -476,7 +577,7 @@ class VersionDataTests(utils.TestCase):
|
|||
|
||||
disc = discover.Discover(self.session, V3_URL)
|
||||
|
||||
data = disc.versioned_data_for(version=None)
|
||||
data = disc.versioned_data_for()
|
||||
self.assertEqual(data['version'], (3, 0))
|
||||
self.assertEqual(data['raw_status'], 'stable')
|
||||
self.assertEqual(data['url'], V3_URL)
|
||||
|
@ -508,21 +609,43 @@ class VersionDataTests(utils.TestCase):
|
|||
self.assertIn(v['version'], ((2, 0), (3, 0)))
|
||||
self.assertEqual(v['raw_status'], 'stable')
|
||||
|
||||
for meth in (disc.data_for, disc.versioned_data_for):
|
||||
version = meth('v3.0')
|
||||
for version in (disc.data_for('v3.0'),
|
||||
disc.data_for('3.latest'),
|
||||
disc.data_for('latest'),
|
||||
disc.versioned_data_for(
|
||||
min_version='v3.0', max_version='v3.latest'),
|
||||
disc.versioned_data_for(min_version='3'),
|
||||
disc.versioned_data_for(min_version='3.latest'),
|
||||
disc.versioned_data_for(min_version='latest'),
|
||||
disc.versioned_data_for(min_version='3.latest',
|
||||
max_version='latest'),
|
||||
disc.versioned_data_for(min_version='latest',
|
||||
max_version='latest'),
|
||||
disc.versioned_data_for(min_version=2),
|
||||
disc.versioned_data_for(min_version='2.latest')):
|
||||
self.assertEqual((3, 0), version['version'])
|
||||
self.assertEqual('stable', version['raw_status'])
|
||||
self.assertEqual(V3_URL, version['url'])
|
||||
|
||||
version = meth(2)
|
||||
for version in (disc.data_for(2),
|
||||
disc.data_for('2.latest'),
|
||||
disc.versioned_data_for(
|
||||
min_version=2, max_version=(2, discover.LATEST)),
|
||||
disc.versioned_data_for(
|
||||
min_version='2.latest', max_version='2.latest')):
|
||||
self.assertEqual((2, 0), version['version'])
|
||||
self.assertEqual('stable', version['raw_status'])
|
||||
self.assertEqual(V2_URL, version['url'])
|
||||
|
||||
for meth in (disc.url_for, disc.versioned_url_for):
|
||||
self.assertIsNone(meth('v4'))
|
||||
self.assertEqual(V3_URL, meth('v3'))
|
||||
self.assertEqual(V2_URL, meth('v2'))
|
||||
self.assertIsNone(disc.url_for('v4'))
|
||||
self.assertIsNone(disc.versioned_url_for(
|
||||
min_version='v4', max_version='v4.latest'))
|
||||
self.assertEqual(V3_URL, disc.url_for('v3'))
|
||||
self.assertEqual(V3_URL, disc.versioned_url_for(
|
||||
min_version='v3', max_version='v3.latest'))
|
||||
self.assertEqual(V2_URL, disc.url_for('v2'))
|
||||
self.assertEqual(V2_URL, disc.versioned_url_for(
|
||||
min_version='v2', max_version='v2.latest'))
|
||||
|
||||
self.assertTrue(mock.called_once)
|
||||
|
||||
|
@ -585,22 +708,33 @@ class VersionDataTests(utils.TestCase):
|
|||
},
|
||||
])
|
||||
|
||||
for meth in (disc.data_for, disc.versioned_data_for):
|
||||
version = meth('v2.0')
|
||||
for version in (disc.data_for('v2.0'),
|
||||
disc.versioned_data_for(min_version='v2.0',
|
||||
max_version='v2.latest')):
|
||||
self.assertEqual((2, 0), version['version'])
|
||||
self.assertEqual('CURRENT', version['raw_status'])
|
||||
self.assertEqual(v2_url, version['url'])
|
||||
|
||||
version = meth(1)
|
||||
for version in (disc.data_for(1),
|
||||
disc.versioned_data_for(
|
||||
min_version=(1,),
|
||||
max_version=(1, discover.LATEST))):
|
||||
self.assertEqual((1, 0), version['version'])
|
||||
self.assertEqual('CURRENT', version['raw_status'])
|
||||
self.assertEqual(v1_url, version['url'])
|
||||
|
||||
for meth in (disc.url_for, disc.versioned_url_for):
|
||||
self.assertIsNone(meth('v4'))
|
||||
self.assertEqual(v3_url, meth('v3'))
|
||||
self.assertEqual(v2_url, meth('v2'))
|
||||
self.assertEqual(v1_url, meth('v1'))
|
||||
self.assertIsNone(disc.url_for('v4'))
|
||||
self.assertIsNone(disc.versioned_url_for(min_version='v4',
|
||||
max_version='v4.latest'))
|
||||
self.assertEqual(v3_url, disc.url_for('v3'))
|
||||
self.assertEqual(v3_url, disc.versioned_url_for(
|
||||
min_version='v3', max_version='v3.latest'))
|
||||
self.assertEqual(v2_url, disc.url_for('v2'))
|
||||
self.assertEqual(v2_url, disc.versioned_url_for(
|
||||
min_version='v2', max_version='v2.latest'))
|
||||
self.assertEqual(v1_url, disc.url_for('v1'))
|
||||
self.assertEqual(v1_url, disc.versioned_url_for(
|
||||
min_version='v1', max_version='v1.latest'))
|
||||
|
||||
self.assertTrue(mock.called_once)
|
||||
|
||||
|
@ -679,24 +813,32 @@ class VersionDataTests(utils.TestCase):
|
|||
},
|
||||
])
|
||||
|
||||
for meth in (disc.data_for, disc.versioned_data_for):
|
||||
for ver in (2, 2.1, 2.2):
|
||||
version = meth(ver)
|
||||
for ver in (2, 2.1, 2.2):
|
||||
for version in (disc.data_for(ver),
|
||||
disc.versioned_data_for(
|
||||
min_version=ver,
|
||||
max_version=(2, discover.LATEST))):
|
||||
self.assertEqual((2, 2), version['version'])
|
||||
self.assertEqual('CURRENT', version['raw_status'])
|
||||
self.assertEqual(v2_url, version['url'])
|
||||
self.assertEqual(v2_url, disc.url_for(ver))
|
||||
|
||||
for ver in (1, 1.1):
|
||||
version = meth(ver)
|
||||
for ver in (1, 1.1):
|
||||
for version in (disc.data_for(ver),
|
||||
disc.versioned_data_for(
|
||||
min_version=ver,
|
||||
max_version=(1, discover.LATEST))):
|
||||
self.assertEqual((1, 1), version['version'])
|
||||
self.assertEqual('CURRENT', version['raw_status'])
|
||||
self.assertEqual(v1_url, version['url'])
|
||||
self.assertEqual(v1_url, disc.url_for(ver))
|
||||
|
||||
for meth in (disc.url_for, disc.versioned_url_for):
|
||||
self.assertIsNone(meth('v3'))
|
||||
self.assertIsNone(meth('v2.3'))
|
||||
self.assertIsNone(disc.url_for('v3'))
|
||||
self.assertIsNone(disc.versioned_url_for(min_version='v3',
|
||||
max_version='v3.latest'))
|
||||
self.assertIsNone(disc.url_for('v2.3'))
|
||||
self.assertIsNone(disc.versioned_url_for(min_version='v2.3',
|
||||
max_version='v2.latest'))
|
||||
|
||||
self.assertTrue(mock.called_once)
|
||||
|
||||
|
@ -799,7 +941,7 @@ class EndpointDataTests(utils.TestCase):
|
|||
mock_url_choices.return_value = ('url1', 'url2', 'url1', 'url3')
|
||||
epd = discover.EndpointData()
|
||||
epd._run_discovery(
|
||||
session='sess', cache='cache', version='vers', min_version='min',
|
||||
session='sess', cache='cache', min_version='min',
|
||||
max_version='max', project_id='projid',
|
||||
allow_version_hack='allow_hack', discover_versions='disc_vers')
|
||||
# Only one call with 'url1'
|
||||
|
|
|
@ -23,6 +23,7 @@ import six
|
|||
from testtools import matchers
|
||||
|
||||
from keystoneauth1 import adapter
|
||||
from keystoneauth1 import discover
|
||||
from keystoneauth1 import exceptions
|
||||
from keystoneauth1 import plugin
|
||||
from keystoneauth1 import session as client_session
|
||||
|
@ -123,6 +124,22 @@ class SessionTests(utils.TestCase):
|
|||
self.assertEqual(headers['X-OpenStack-Nova-API-Version'], '2.30')
|
||||
self.assertEqual(len(headers.keys()), 2)
|
||||
|
||||
# 'latest' (string) microversion
|
||||
headers = {}
|
||||
client_session.Session._set_microversion_headers(
|
||||
headers, 'latest', 'compute', None)
|
||||
self.assertEqual(headers['OpenStack-API-Version'], 'compute latest')
|
||||
self.assertEqual(headers['X-OpenStack-Nova-API-Version'], 'latest')
|
||||
self.assertEqual(len(headers.keys()), 2)
|
||||
|
||||
# LATEST (tuple) microversion
|
||||
headers = {}
|
||||
client_session.Session._set_microversion_headers(
|
||||
headers, (discover.LATEST, discover.LATEST), 'compute', None)
|
||||
self.assertEqual(headers['OpenStack-API-Version'], 'compute latest')
|
||||
self.assertEqual(headers['X-OpenStack-Nova-API-Version'], 'latest')
|
||||
self.assertEqual(len(headers.keys()), 2)
|
||||
|
||||
# ironic microversion, specified service type
|
||||
headers = {}
|
||||
client_session.Session._set_microversion_headers(
|
||||
|
@ -154,10 +171,19 @@ class SessionTests(utils.TestCase):
|
|||
self.assertEqual(headers['OpenStack-API-Version'], 'compute 2.30')
|
||||
self.assertEqual(headers['X-OpenStack-Nova-API-Version'], '2.30')
|
||||
|
||||
# Can't specify a 'M.latest' microversion
|
||||
self.assertRaises(TypeError,
|
||||
client_session.Session._set_microversion_headers,
|
||||
{}, '2.latest', 'service_type', None)
|
||||
self.assertRaises(TypeError,
|
||||
client_session.Session._set_microversion_headers,
|
||||
{}, (2, discover.LATEST), 'service_type', None)
|
||||
|
||||
# Normalization error
|
||||
self.assertRaises(TypeError,
|
||||
client_session.Session._set_microversion_headers,
|
||||
{}, 'bogus', 'service_type', None)
|
||||
|
||||
# No service type in param or endpoint filter
|
||||
self.assertRaises(TypeError,
|
||||
client_session.Session._set_microversion_headers,
|
||||
|
|
Loading…
Reference in New Issue