Add url manipulation and microversion collection
From the API-WG spec, there are two common patterns for service URLs that can be interpreted. Trailing project_id and a string that starts with v. If the project_id is in the URL, it needs to be removed before discovery can happen, but it needs to be put back on to the url found via discovery. If the endpointin the catalog has a version, and it matches the version we're asking for, then we don't need to go hunting for the unversioned doc. Also, in the EndpointData we're collecting, we want to grab microversion info, since we're already there in the discovery doc. There is one behavior change that can be seen in the tests. If the attempt at an unversioned discovery endpoint fails, we fall back to the url from the catalog ... but we attempt to get a discovery document from it because we need the metadata for microversions. The catalog URL should be returned as the endpoint even if the second discovery call attempt succeeds, so the user-facing interface is the same - there will just be, in some cases, an additional URL fetch behind the scenes. Change-Id: I2a036d65e4f7dba6f50daf6a0ce4589ee59ae95f
This commit is contained in:
parent
e89e354335
commit
a4066a86b5
|
@ -25,6 +25,7 @@ import copy
|
|||
import re
|
||||
|
||||
from positional import positional
|
||||
import six
|
||||
from six.moves import urllib
|
||||
|
||||
from keystoneauth1 import _utils as utils
|
||||
|
@ -74,31 +75,39 @@ def get_version_data(session, url, authenticated=None):
|
|||
|
||||
def normalize_version_number(version):
|
||||
"""Turn a version representation into a tuple."""
|
||||
# trim the v from a 'v2.0' or similar
|
||||
try:
|
||||
version = version.lstrip('v')
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# if it's an integer or a numeric as a string then normalize it
|
||||
# to a string, this ensures 1 decimal point
|
||||
try:
|
||||
num = float(version)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
version = str(num)
|
||||
# If it's a float as a string, don't do that, the split/map below
|
||||
# will do what we want. (Otherwise, we wind up with 3.20 -> (3, 2)
|
||||
if isinstance(version, six.string_types):
|
||||
# trim the v from a 'v2.0' or similar
|
||||
version = version.lstrip('v')
|
||||
try:
|
||||
# If version is a pure int, like '1' or '200' this will produce
|
||||
# a stringified version with a .0 added. If it's any other number,
|
||||
# such as '1.1' - int(version) raises an Exception
|
||||
version = str(float(int(version)))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# if it's a string (or an integer) from above break it on .
|
||||
try:
|
||||
return tuple(map(int, version.split('.')))
|
||||
except Exception:
|
||||
pass
|
||||
# If it's an int, turn it into a float
|
||||
elif isinstance(version, int):
|
||||
version = str(float(version))
|
||||
|
||||
# last attempt, maybe it's a list or iterable.
|
||||
elif isinstance(version, float):
|
||||
version = str(version)
|
||||
|
||||
# At this point, we should either have a string that contains a number
|
||||
# or something decidedly else.
|
||||
|
||||
# if it's a string from above break it on .
|
||||
if hasattr(version, 'split'):
|
||||
version = version.split('.')
|
||||
|
||||
# It's either an interable, or something else that makes us sad.
|
||||
try:
|
||||
return tuple(map(int, version))
|
||||
except Exception:
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
raise TypeError('Invalid version specified: %s' % version)
|
||||
|
@ -236,6 +245,16 @@ class Discover(object):
|
|||
|
||||
version_number = normalize_version_number(version_str)
|
||||
|
||||
# collect microversion information
|
||||
min_microversion = v.get('min_version') or None
|
||||
if min_microversion:
|
||||
min_microversion = normalize_version_number(min_microversion)
|
||||
max_microversion = v.get('max_version', v.get('version')) or None
|
||||
if max_microversion:
|
||||
max_microversion = normalize_version_number(max_microversion)
|
||||
|
||||
self_url = None
|
||||
collection_url = None
|
||||
for link in links:
|
||||
try:
|
||||
rel = link['rel']
|
||||
|
@ -246,14 +265,19 @@ class Discover(object):
|
|||
continue
|
||||
|
||||
if rel.lower() == 'self':
|
||||
break
|
||||
else:
|
||||
self_url = url
|
||||
elif rel.lower() == 'collection':
|
||||
collection_url = url
|
||||
if not self_url:
|
||||
_LOGGER.info('Skipping invalid version data. '
|
||||
'Missing link to endpoint.')
|
||||
continue
|
||||
|
||||
versions.append({'version': version_number,
|
||||
'url': url,
|
||||
'url': self_url,
|
||||
'collection': collection_url,
|
||||
'min_microversion': min_microversion,
|
||||
'max_microversion': max_microversion,
|
||||
'raw_status': v['status']})
|
||||
|
||||
versions.sort(key=lambda v: v['version'], reverse=reverse)
|
||||
|
@ -332,6 +356,8 @@ class EndpointData(object):
|
|||
self.major_version = major_version
|
||||
self.min_microversion = min_microversion
|
||||
self.max_microversion = max_microversion
|
||||
self._saved_project_id = None
|
||||
self._catalog_matches_version = False
|
||||
|
||||
def __copy__(self):
|
||||
"""Return a new EndpointData based on this one."""
|
||||
|
@ -357,7 +383,7 @@ class EndpointData(object):
|
|||
@positional(3)
|
||||
def get_versioned_data(self, session, version,
|
||||
authenticated=False, allow=None, cache=None,
|
||||
allow_version_hack=True):
|
||||
allow_version_hack=True, project_id=None):
|
||||
"""Run version discovery for the service described.
|
||||
|
||||
Performs Version Discovery and returns a new EndpointData object with
|
||||
|
@ -367,6 +393,9 @@ class EndpointData(object):
|
|||
:type session: keystoneauth1.session.Session
|
||||
:param tuple version: The minimum major version required for this
|
||||
endpoint.
|
||||
:param string project_id: ID of the currently scoped project. Used for
|
||||
removing project_id components of URLs from
|
||||
the catalog. (optional)
|
||||
:param dict allow: Extra filters to pass when discovering API
|
||||
versions. (optional)
|
||||
:param bool allow_version_hack: Allow keystoneauth to hack up catalog
|
||||
|
@ -393,25 +422,47 @@ class EndpointData(object):
|
|||
# defaulting to the most recent version.
|
||||
return new_data
|
||||
|
||||
# NOTE(jamielennox): For backwards compatibility people might have a
|
||||
# versioned endpoint in their catalog even though they want to use
|
||||
# other endpoint versions. So we support a list of client defined
|
||||
# situations where we can strip the version component from a URL before
|
||||
# doing discovery.
|
||||
vers_url = new_data._get_catalog_discover_hack(allow_version_hack)
|
||||
new_data._set_version_info(
|
||||
session=session, version=version, authenticated=authenticated,
|
||||
allow=allow, cache=cache, allow_version_hack=allow_version_hack,
|
||||
project_id=project_id)
|
||||
return new_data
|
||||
|
||||
def _set_version_info(self, session, version,
|
||||
authenticated=False, allow=None, cache=None,
|
||||
allow_version_hack=True, project_id=None):
|
||||
if project_id:
|
||||
self.project_id = project_id
|
||||
|
||||
disc = None
|
||||
vers_url = None
|
||||
tried = set()
|
||||
for vers_url in self._get_url_choices(version, project_id,
|
||||
allow_version_hack):
|
||||
|
||||
if vers_url in tried:
|
||||
continue
|
||||
tried.update(vers_url)
|
||||
try:
|
||||
disc = get_discovery(session, vers_url,
|
||||
cache=cache,
|
||||
authenticated=False)
|
||||
break
|
||||
except (exceptions.DiscoveryFailure,
|
||||
exceptions.HttpError,
|
||||
exceptions.ConnectionError):
|
||||
continue
|
||||
if not disc:
|
||||
# We couldn't find a version discovery document anywhere.
|
||||
if self._catalog_matches_version:
|
||||
# But - the version in the catalog is fine.
|
||||
self.service_url = self.catalog_url
|
||||
return
|
||||
|
||||
try:
|
||||
disc = get_discovery(session, vers_url,
|
||||
cache=cache,
|
||||
authenticated=False)
|
||||
except (exceptions.DiscoveryFailure,
|
||||
exceptions.HttpError,
|
||||
exceptions.ConnectionError):
|
||||
# NOTE(jamielennox): The logic here is required for backwards
|
||||
# compatibility. By itself it is not ideal.
|
||||
|
||||
if allow_version_hack:
|
||||
# NOTE(jamielennox): Again if we can't contact the server we
|
||||
# NOTE(jamielennox): If we can't contact the server we
|
||||
# fall back to just returning the URL from the catalog. This
|
||||
# is backwards compatible behaviour and used when there is no
|
||||
# other choice. Realistically if you have provided a version
|
||||
|
@ -421,25 +472,96 @@ class EndpointData(object):
|
|||
'Failed to contact the endpoint at %s for '
|
||||
'discovery. Fallback to using that endpoint as '
|
||||
'the base url.', self.url)
|
||||
return
|
||||
|
||||
else:
|
||||
# NOTE(jamielennox): If you've said no to allow_version_hack
|
||||
# and you can't determine the actual URL this is a failure
|
||||
# and we can't determine the actual URL this is a failure
|
||||
# because we are specifying that the deployment must be up to
|
||||
# date enough to properly specify a version and keystoneauth
|
||||
# can't deliver.
|
||||
raise
|
||||
|
||||
else:
|
||||
url = disc.url_for(version, **allow)
|
||||
if not url:
|
||||
raise exceptions.DiscoveryFailure(
|
||||
"Version {version} requested, but was not found".format(
|
||||
version=version_to_string(version)))
|
||||
new_data.service_url = url
|
||||
return new_data
|
||||
"Version requested but version discovery document was not"
|
||||
" found and allow_version_hack was False")
|
||||
|
||||
def _get_catalog_discover_hack(self, allow_version_hack=True):
|
||||
# NOTE(jamielennox): urljoin allows the url to be relative or even
|
||||
# protocol-less. The additional trailing '/' make urljoin respect
|
||||
# the current path as canonical even if the url doesn't include it.
|
||||
# for example a "v2" path from http://host/admin should resolve as
|
||||
# http://host/admin/v2 where it would otherwise be host/v2.
|
||||
# This has no effect on absolute urls returned from url_for.
|
||||
discovered_data = disc.data_for(version, **allow)
|
||||
if not discovered_data:
|
||||
raise exceptions.DiscoveryFailure(
|
||||
"Version {version} requested, but was not found".format(
|
||||
version=version_to_string(version)))
|
||||
|
||||
self.min_microversion = discovered_data['min_microversion']
|
||||
self.max_microversion = discovered_data['max_microversion']
|
||||
|
||||
discovered_url = discovered_data['url']
|
||||
|
||||
url = urllib.parse.urljoin(vers_url.rstrip('/') + '/', discovered_url)
|
||||
|
||||
# If we had to pop a project_id from the catalog_url, put it back on
|
||||
if self._saved_project_id:
|
||||
url = urllib.parse.urljoin(url.rstrip('/') + '/',
|
||||
self._saved_project_id)
|
||||
self.service_url = url
|
||||
|
||||
def _get_url_choices(self, version, project_id, allow_version_hack=True):
|
||||
if allow_version_hack:
|
||||
url = urllib.parse.urlparse(self.url)
|
||||
url_parts = url.path.split('/')
|
||||
|
||||
# First, check to see if the catalog url ends with a project id
|
||||
# We need to remove it and save it for later if it does
|
||||
if project_id and url_parts[-1].endswith(project_id):
|
||||
self._saved_project_id = url_parts.pop()
|
||||
|
||||
# Next, check to see if the url indicates a version and if that
|
||||
# version matches our request. If so, we can start by trying
|
||||
# the given url as it has a high potential for success
|
||||
url_version = None
|
||||
if url_parts[-1].startswith('v'):
|
||||
try:
|
||||
url_version = normalize_version_number(url_parts[-1])
|
||||
except TypeError:
|
||||
pass
|
||||
if url_version:
|
||||
if version_match(version, url_version):
|
||||
self._catalog_matches_version = True
|
||||
# This endpoint matches the version request, try it first
|
||||
yield urllib.parse.ParseResult(
|
||||
url.scheme,
|
||||
url.netloc,
|
||||
'/'.join(url_parts),
|
||||
url.params,
|
||||
url.query,
|
||||
url.fragment).geturl()
|
||||
url_parts.pop()
|
||||
|
||||
# If there were projects or versions in the url they are now gone.
|
||||
# That means we're left with the unversioned url
|
||||
yield urllib.parse.ParseResult(
|
||||
url.scheme,
|
||||
url.netloc,
|
||||
'/'.join(url_parts),
|
||||
url.params,
|
||||
url.query,
|
||||
url.fragment).geturl()
|
||||
|
||||
# NOTE(mordred): For backwards compatibility people might have
|
||||
# added version hacks using the version hack system. The logic
|
||||
# above should handle most cases, so by the time we get here it's
|
||||
# most likely to be a no-op
|
||||
yield self._get_catalog_discover_hack()
|
||||
|
||||
# As a final fallthrough case, add the url from the catalog. If hacks
|
||||
# are turned off, this will be the only choice.
|
||||
yield self.catalog_url
|
||||
|
||||
def _get_catalog_discover_hack(self):
|
||||
"""Apply the catalog hacks and figure out an unversioned endpoint.
|
||||
|
||||
This function is internal to keystoneauth1.
|
||||
|
@ -447,12 +569,9 @@ class EndpointData(object):
|
|||
:param bool allow_version_hack: Whether or not to allow version hacks
|
||||
to be applied. (defaults to True)
|
||||
|
||||
:returns: Either the unversioned url or the one from the catalog to try
|
||||
:returns: A potential unversioned url
|
||||
"""
|
||||
if allow_version_hack:
|
||||
return _VERSION_HACKS.get_discover_hack(self.service_type,
|
||||
self.url)
|
||||
return self.url
|
||||
return _VERSION_HACKS.get_discover_hack(self.service_type, self.url)
|
||||
|
||||
|
||||
@positional()
|
||||
|
|
|
@ -37,4 +37,5 @@ __all__ = ('DiscoveryList',
|
|||
'V2Token',
|
||||
'V3Token',
|
||||
'V3FederationToken',
|
||||
'VersionDiscovery',
|
||||
)
|
||||
|
|
|
@ -17,6 +17,7 @@ from keystoneauth1 import _utils as utils
|
|||
__all__ = ('DiscoveryList',
|
||||
'V2Discovery',
|
||||
'V3Discovery',
|
||||
'VersionDiscovery',
|
||||
)
|
||||
|
||||
_DEFAULT_DAYS_AGO = 30
|
||||
|
@ -95,6 +96,103 @@ class DiscoveryBase(dict):
|
|||
return mt
|
||||
|
||||
|
||||
class VersionDiscovery(DiscoveryBase):
|
||||
"""A Version element for non-keystone services without microversions.
|
||||
|
||||
Provides some default values and helper methods for creating a microversion
|
||||
endpoint version structure. Clients should use this instead of creating
|
||||
their own structures.
|
||||
|
||||
:param string href: The url that this entry should point to.
|
||||
:param string id: The version id that should be reported.
|
||||
"""
|
||||
|
||||
def __init__(self, href, id, **kwargs):
|
||||
super(VersionDiscovery, self).__init__(id, **kwargs)
|
||||
|
||||
self.add_link(href)
|
||||
|
||||
|
||||
class MicroversionDiscovery(DiscoveryBase):
|
||||
"""A Version element for that has microversions.
|
||||
|
||||
Provides some default values and helper methods for creating a microversion
|
||||
endpoint version structure. Clients should use this instead of creating
|
||||
their own structures.
|
||||
|
||||
:param string href: The url that this entry should point to.
|
||||
:param string id: The version id that should be reported.
|
||||
:param string min_version: The minimum supported microversion. (optional)
|
||||
:param string max_version: The maximum supported microversion. (optional)
|
||||
"""
|
||||
|
||||
@positional()
|
||||
def __init__(self, href, id, min_version='', max_version='', **kwargs):
|
||||
super(MicroversionDiscovery, self).__init__(id, **kwargs)
|
||||
|
||||
self.add_link(href)
|
||||
|
||||
self.min_version = min_version
|
||||
self.max_version = max_version
|
||||
|
||||
@property
|
||||
def min_version(self):
|
||||
return self.get('min_version')
|
||||
|
||||
@min_version.setter
|
||||
def min_version(self, value):
|
||||
self['min_version'] = value
|
||||
|
||||
@property
|
||||
def max_version(self):
|
||||
return self.get('max_version')
|
||||
|
||||
@max_version.setter
|
||||
def max_version(self, value):
|
||||
self['max_version'] = value
|
||||
|
||||
|
||||
class NovaMicroversionDiscovery(DiscoveryBase):
|
||||
"""A Version element with nova-style microversions.
|
||||
|
||||
Provides some default values and helper methods for creating a microversion
|
||||
endpoint version structure. Clients should use this instead of creating
|
||||
their own structures.
|
||||
|
||||
:param href: The url that this entry should point to.
|
||||
:param string id: The version id that should be reported.
|
||||
:param string min_version: The minimum microversion supported. (optional)
|
||||
:param string version: The maximum microversion supported. (optional)
|
||||
"""
|
||||
|
||||
@positional()
|
||||
def __init__(self, href, id, min_version=None, version=None, **kwargs):
|
||||
super(NovaMicroversionDiscovery, self).__init__(id, **kwargs)
|
||||
|
||||
self.add_link(href)
|
||||
|
||||
self.min_version = min_version
|
||||
self.version = version
|
||||
|
||||
@property
|
||||
def min_version(self):
|
||||
return self.get('min_version')
|
||||
|
||||
@min_version.setter
|
||||
def min_version(self, value):
|
||||
if value:
|
||||
self['min_version'] = value
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
return self.get('version')
|
||||
|
||||
@version.setter
|
||||
def version(self, value):
|
||||
if value:
|
||||
self['version'] = value
|
||||
|
||||
|
||||
class V2Discovery(DiscoveryBase):
|
||||
"""A Version element for a V2 identity service endpoint.
|
||||
|
||||
|
@ -255,3 +353,21 @@ class DiscoveryList(dict):
|
|||
obj = V3Discovery(href, **kwargs)
|
||||
self.add_version(obj)
|
||||
return obj
|
||||
|
||||
def add_microversion(self, href, id, **kwargs):
|
||||
"""Add a microversion version to the list.
|
||||
|
||||
The parameters are the same as MicroversionDiscovery.
|
||||
"""
|
||||
obj = MicroversionDiscovery(href=href, id=id, **kwargs)
|
||||
self.add_version(obj)
|
||||
return obj
|
||||
|
||||
def add_nova_microversion(self, href, id, **kwargs):
|
||||
"""Add a nova microversion version to the list.
|
||||
|
||||
The parameters are the same as NovaMicroversionDiscovery.
|
||||
"""
|
||||
obj = NovaMicroversionDiscovery(href=href, id=id, **kwargs)
|
||||
self.add_version(obj)
|
||||
return obj
|
||||
|
|
|
@ -201,6 +201,7 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
|
|||
endpoint_data = discover.EndpointData(
|
||||
service_url=self.auth_url,
|
||||
service_type=service_type or 'identity')
|
||||
project_id = None
|
||||
else:
|
||||
if not service_type:
|
||||
LOG.warning('Plugin cannot return an endpoint without '
|
||||
|
@ -214,6 +215,9 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
|
|||
interface = 'public'
|
||||
|
||||
service_catalog = self.get_access(session).service_catalog
|
||||
project_id = self.get_project_id(session)
|
||||
# NOTE(mordred): service_catalog.url_data_for raises if it can't
|
||||
# find a match, so this will always be a valid object.
|
||||
endpoint_data = service_catalog.endpoint_data_for(
|
||||
service_type=service_type,
|
||||
interface=interface,
|
||||
|
@ -224,7 +228,9 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
|
|||
|
||||
try:
|
||||
return endpoint_data.get_versioned_data(
|
||||
session, version, authenticated=False,
|
||||
session, version,
|
||||
project_id=project_id,
|
||||
authenticated=False,
|
||||
cache=self._discovery_cache,
|
||||
allow_version_hack=allow_version_hack, allow=allow)
|
||||
except (exceptions.DiscoveryFailure,
|
||||
|
|
|
@ -38,6 +38,30 @@ class CommonIdentityTests(object):
|
|||
TEST_COMPUTE_INTERNAL = TEST_COMPUTE_BASE + '/novapi/internal'
|
||||
TEST_COMPUTE_ADMIN = TEST_COMPUTE_BASE + '/novapi/admin'
|
||||
|
||||
TEST_VOLUME_V2_BASE = 'http://cinder'
|
||||
TEST_VOLUME_V2_SERVICE_PUBLIC = TEST_VOLUME_V2_BASE + '/public/volumev2'
|
||||
TEST_VOLUME_V2_SERVICE_INTERNAL = (
|
||||
TEST_VOLUME_V2_BASE + '/internal/volumev2')
|
||||
TEST_VOLUME_V2_SERVICE_ADMIN = TEST_VOLUME_V2_BASE + '/admin/volumev2'
|
||||
TEST_VOLUME_V2_CATALOG_PUBLIC = (
|
||||
TEST_VOLUME_V2_SERVICE_PUBLIC + '/{project_id}')
|
||||
TEST_VOLUME_V2_CATALOG_INTERNAL = (
|
||||
TEST_VOLUME_V2_SERVICE_INTERNAL + '/{project_id}')
|
||||
TEST_VOLUME_V2_CATALOG_ADMIN = (
|
||||
TEST_VOLUME_V2_SERVICE_ADMIN + '/{project_id}')
|
||||
|
||||
TEST_VOLUME_V3_BASE = 'http://cinder'
|
||||
TEST_VOLUME_V3_SERVICE_PUBLIC = TEST_VOLUME_V3_BASE + '/public/volumev3'
|
||||
TEST_VOLUME_V3_SERVICE_INTERNAL = (
|
||||
TEST_VOLUME_V3_BASE + '/internal/volumev3')
|
||||
TEST_VOLUME_V3_SERVICE_ADMIN = TEST_VOLUME_V3_BASE + '/admin/volumev3'
|
||||
TEST_VOLUME_V3_CATALOG_PUBLIC = (
|
||||
TEST_VOLUME_V3_SERVICE_PUBLIC + '/{project_id}')
|
||||
TEST_VOLUME_V3_CATALOG_INTERNAL = (
|
||||
TEST_VOLUME_V3_SERVICE_INTERNAL + '/{project_id}')
|
||||
TEST_VOLUME_V3_CATALOG_ADMIN = (
|
||||
TEST_VOLUME_V3_SERVICE_ADMIN + '/{project_id}')
|
||||
|
||||
TEST_PASS = uuid.uuid4().hex
|
||||
|
||||
def setUp(self):
|
||||
|
@ -498,6 +522,118 @@ class CommonIdentityTests(object):
|
|||
self.assertEqual(v3_compute, v3_data.service_url)
|
||||
self.assertEqual(self.TEST_COMPUTE_ADMIN, v3_data.catalog_url)
|
||||
|
||||
def test_get_versioned_data_project_id(self):
|
||||
|
||||
# need to construct list this way for relative
|
||||
disc = fixture.DiscoveryList(v2=False, v3=False)
|
||||
# The version discovery dict will not have a project_id
|
||||
disc.add_microversion(
|
||||
href=self.TEST_VOLUME_V3_SERVICE_PUBLIC,
|
||||
id='v3.0', status='CURRENT',
|
||||
min_version='3.0', max_version='3.20')
|
||||
# Adding a v2 version to a service named volumev3 is not
|
||||
# an error. The service itself is cinder and has more than
|
||||
# one major version.
|
||||
disc.add_microversion(
|
||||
href=self.TEST_VOLUME_V2_SERVICE_PUBLIC,
|
||||
id='v2.0', status='SUPPORTED')
|
||||
|
||||
# We should only try to fetch the non-project_id url and only
|
||||
# once
|
||||
resps = [{'json': disc}, {'status_code': 500}]
|
||||
self.requests_mock.get(self.TEST_VOLUME_V3_SERVICE_PUBLIC, resps)
|
||||
|
||||
body = 'SUCCESS'
|
||||
self.stub_url('GET', ['path'], text=body)
|
||||
|
||||
a = self.create_auth_plugin()
|
||||
s = session.Session(auth=a)
|
||||
|
||||
v2_catalog_url = self.TEST_VOLUME_V2_CATALOG_PUBLIC.format(
|
||||
project_id=self.project_id)
|
||||
v3_catalog_url = self.TEST_VOLUME_V3_CATALOG_PUBLIC.format(
|
||||
project_id=self.project_id)
|
||||
|
||||
data = a.get_endpoint_data(session=s,
|
||||
service_type='volumev3',
|
||||
interface='public')
|
||||
self.assertEqual(v3_catalog_url, data.url)
|
||||
|
||||
v3_data = data.get_versioned_data(
|
||||
s, version='3.0', project_id=self.project_id)
|
||||
self.assertEqual(v3_catalog_url, v3_data.url)
|
||||
self.assertEqual(v3_catalog_url, v3_data.service_url)
|
||||
self.assertEqual(v3_catalog_url, v3_data.catalog_url)
|
||||
self.assertEqual((3, 0), v3_data.min_microversion)
|
||||
self.assertEqual((3, 20), v3_data.max_microversion)
|
||||
|
||||
v2_data = data.get_versioned_data(
|
||||
s, version='2.0', 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
|
||||
# the project_id to the URL
|
||||
self.assertEqual(v2_catalog_url, v2_data.url)
|
||||
self.assertEqual(v2_catalog_url, v2_data.service_url)
|
||||
self.assertEqual(v3_catalog_url, v2_data.catalog_url)
|
||||
self.assertEqual(None, v2_data.min_microversion)
|
||||
self.assertEqual(None, v2_data.max_microversion)
|
||||
|
||||
def test_get_versioned_data_compute_project_id(self):
|
||||
|
||||
# need to construct list this way for relative
|
||||
disc = fixture.DiscoveryList(v2=False, v3=False)
|
||||
# The version discovery dict will not have a project_id
|
||||
disc.add_nova_microversion(
|
||||
href=self.TEST_VOLUME_V3_SERVICE_PUBLIC,
|
||||
id='v3.0', status='CURRENT',
|
||||
min_version='3.0', version='3.20')
|
||||
# Adding a v2 version to a service named volumev3 is not
|
||||
# an error. The service itself is cinder and has more than
|
||||
# one major version.
|
||||
disc.add_nova_microversion(
|
||||
href=self.TEST_VOLUME_V2_SERVICE_PUBLIC,
|
||||
id='v2.0', status='SUPPORTED')
|
||||
|
||||
# We should only try to fetch the non-project_id url and only
|
||||
# once
|
||||
resps = [{'json': disc}, {'status_code': 500}]
|
||||
self.requests_mock.get(self.TEST_VOLUME_V3_SERVICE_PUBLIC, resps)
|
||||
|
||||
body = 'SUCCESS'
|
||||
self.stub_url('GET', ['path'], text=body)
|
||||
|
||||
a = self.create_auth_plugin()
|
||||
s = session.Session(auth=a)
|
||||
|
||||
v2_catalog_url = self.TEST_VOLUME_V2_CATALOG_PUBLIC.format(
|
||||
project_id=self.project_id)
|
||||
v3_catalog_url = self.TEST_VOLUME_V3_CATALOG_PUBLIC.format(
|
||||
project_id=self.project_id)
|
||||
|
||||
data = a.get_endpoint_data(session=s,
|
||||
service_type='volumev3',
|
||||
interface='public')
|
||||
self.assertEqual(v3_catalog_url, data.url)
|
||||
|
||||
v3_data = data.get_versioned_data(
|
||||
s, version='3.0', project_id=self.project_id)
|
||||
self.assertEqual(v3_catalog_url, v3_data.url)
|
||||
self.assertEqual(v3_catalog_url, v3_data.service_url)
|
||||
self.assertEqual(v3_catalog_url, v3_data.catalog_url)
|
||||
self.assertEqual((3, 0), v3_data.min_microversion)
|
||||
self.assertEqual((3, 20), v3_data.max_microversion)
|
||||
|
||||
v2_data = data.get_versioned_data(
|
||||
s, version='2.0', 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
|
||||
# the project_id to the URL
|
||||
self.assertEqual(v2_catalog_url, v2_data.url)
|
||||
self.assertEqual(v2_catalog_url, v2_data.service_url)
|
||||
self.assertEqual(v3_catalog_url, v2_data.catalog_url)
|
||||
self.assertEqual(None, v2_data.min_microversion)
|
||||
self.assertEqual(None, v2_data.max_microversion)
|
||||
|
||||
def test_asking_for_auth_endpoint_ignores_checks(self):
|
||||
a = self.create_auth_plugin()
|
||||
s = session.Session(auth=a)
|
||||
|
@ -619,6 +755,7 @@ class V3(CommonIdentityTests, utils.TestCase):
|
|||
return 'v3'
|
||||
|
||||
def get_auth_data(self, **kwargs):
|
||||
kwargs.setdefault('project_id', uuid.uuid4().hex)
|
||||
token = fixture.V3Token(**kwargs)
|
||||
region = 'RegionOne'
|
||||
|
||||
|
@ -631,6 +768,20 @@ class V3(CommonIdentityTests, utils.TestCase):
|
|||
internal=self.TEST_COMPUTE_INTERNAL,
|
||||
region=region)
|
||||
|
||||
svc = token.add_service('volumev2')
|
||||
svc.add_standard_endpoints(
|
||||
admin=self.TEST_VOLUME_V2_CATALOG_ADMIN.format(**kwargs),
|
||||
public=self.TEST_VOLUME_V2_CATALOG_PUBLIC.format(**kwargs),
|
||||
internal=self.TEST_VOLUME_V2_CATALOG_INTERNAL.format(**kwargs),
|
||||
region=region)
|
||||
|
||||
svc = token.add_service('volumev3')
|
||||
svc.add_standard_endpoints(
|
||||
admin=self.TEST_VOLUME_V3_CATALOG_ADMIN.format(**kwargs),
|
||||
public=self.TEST_VOLUME_V3_CATALOG_PUBLIC.format(**kwargs),
|
||||
internal=self.TEST_VOLUME_V3_CATALOG_INTERNAL.format(**kwargs),
|
||||
region=region)
|
||||
|
||||
return token
|
||||
|
||||
def stub_auth(self, subject_token=None, **kwargs):
|
||||
|
@ -671,6 +822,7 @@ class V2(CommonIdentityTests, utils.TestCase):
|
|||
return identity.V2Password(**kwargs)
|
||||
|
||||
def get_auth_data(self, **kwargs):
|
||||
kwargs.setdefault('tenant_id', uuid.uuid4().hex)
|
||||
token = fixture.V2Token(**kwargs)
|
||||
region = 'RegionOne'
|
||||
|
||||
|
@ -683,6 +835,21 @@ class V2(CommonIdentityTests, utils.TestCase):
|
|||
admin=self.TEST_COMPUTE_ADMIN,
|
||||
region=region)
|
||||
|
||||
kwargs['project_id'] = kwargs['tenant_id']
|
||||
svc = token.add_service('volumev2')
|
||||
svc.add_endpoint(
|
||||
admin=self.TEST_VOLUME_V2_CATALOG_ADMIN.format(**kwargs),
|
||||
public=self.TEST_VOLUME_V2_CATALOG_PUBLIC.format(**kwargs),
|
||||
internal=self.TEST_VOLUME_V2_CATALOG_INTERNAL.format(**kwargs),
|
||||
region=region)
|
||||
|
||||
svc = token.add_service('volumev3')
|
||||
svc.add_endpoint(
|
||||
admin=self.TEST_VOLUME_V3_CATALOG_ADMIN.format(**kwargs),
|
||||
public=self.TEST_VOLUME_V3_CATALOG_PUBLIC.format(**kwargs),
|
||||
internal=self.TEST_VOLUME_V3_CATALOG_INTERNAL.format(**kwargs),
|
||||
region=region)
|
||||
|
||||
return token
|
||||
|
||||
def stub_auth(self, **kwargs):
|
||||
|
@ -743,6 +910,7 @@ class CatalogHackTests(utils.TestCase):
|
|||
json=token)
|
||||
|
||||
self.stub_url('GET', [], base_url=self.BASE_URL, status_code=404)
|
||||
self.stub_url('GET', [], base_url=self.V2_URL, status_code=404)
|
||||
|
||||
v2_auth = identity.V2Password(self.V2_URL,
|
||||
username=uuid.uuid4().hex,
|
||||
|
|
|
@ -148,6 +148,23 @@ CINDER_EXAMPLES = {
|
|||
"rel": "self"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"status": "CURRENT",
|
||||
"updated": "2012-11-21T11:33:21Z",
|
||||
"id": "v3.0",
|
||||
"version": "3.27",
|
||||
"min_version": "3.0",
|
||||
"links": [
|
||||
{
|
||||
"href": BASE_URL,
|
||||
"rel": "collection"
|
||||
},
|
||||
{
|
||||
"href": "%sv3/" % BASE_URL,
|
||||
"rel": "self"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -277,6 +294,7 @@ class DiscoverUtils(utils.TestCase):
|
|||
assertVersion('1', (1, 0))
|
||||
assertVersion(1, (1, 0))
|
||||
assertVersion(5.2, (5, 2))
|
||||
assertVersion('3.20', (3, 20))
|
||||
assertVersion((6, 1), (6, 1))
|
||||
assertVersion([1, 4], (1, 4))
|
||||
|
||||
|
@ -390,7 +408,7 @@ class VersionDataTests(utils.TestCase):
|
|||
raw_data = disc.raw_version_data()
|
||||
clean_data = disc.version_data()
|
||||
|
||||
self.assertEqual(2, len(raw_data))
|
||||
self.assertEqual(3, len(raw_data))
|
||||
|
||||
for v in raw_data:
|
||||
self.assertEqual(v['status'], 'CURRENT')
|
||||
|
@ -398,23 +416,40 @@ class VersionDataTests(utils.TestCase):
|
|||
self.assertEqual(v['updated'], '2012-01-04T11:33:21Z')
|
||||
elif v['id'] == 'v2.0':
|
||||
self.assertEqual(v['updated'], '2012-11-21T11:33:21Z')
|
||||
elif v['id'] == 'v3.0':
|
||||
self.assertEqual(v['updated'], '2012-11-21T11:33:21Z')
|
||||
else:
|
||||
self.fail("Invalid version found")
|
||||
|
||||
v1_url = "%sv1/" % BASE_URL
|
||||
v2_url = "%sv2/" % BASE_URL
|
||||
v3_url = "%sv3/" % BASE_URL
|
||||
|
||||
self.assertEqual(clean_data, [
|
||||
{
|
||||
'collection': None,
|
||||
'max_microversion': None,
|
||||
'min_microversion': None,
|
||||
'version': (1, 0),
|
||||
'url': v1_url,
|
||||
'raw_status': 'CURRENT',
|
||||
},
|
||||
{
|
||||
'collection': None,
|
||||
'max_microversion': None,
|
||||
'min_microversion': None,
|
||||
'version': (2, 0),
|
||||
'url': v2_url,
|
||||
'raw_status': 'CURRENT',
|
||||
},
|
||||
{
|
||||
'collection': BASE_URL,
|
||||
'max_microversion': (3, 27),
|
||||
'min_microversion': (3, 0),
|
||||
'version': (3, 0),
|
||||
'url': v3_url,
|
||||
'raw_status': 'CURRENT',
|
||||
},
|
||||
])
|
||||
|
||||
version = disc.data_for('v2.0')
|
||||
|
@ -427,7 +462,8 @@ class VersionDataTests(utils.TestCase):
|
|||
self.assertEqual('CURRENT', version['raw_status'])
|
||||
self.assertEqual(v1_url, version['url'])
|
||||
|
||||
self.assertIsNone(disc.url_for('v3'))
|
||||
self.assertIsNone(disc.url_for('v4'))
|
||||
self.assertEqual(v3_url, disc.url_for('v3'))
|
||||
self.assertEqual(v2_url, disc.url_for('v2'))
|
||||
self.assertEqual(v1_url, disc.url_for('v1'))
|
||||
|
||||
|
@ -457,26 +493,41 @@ class VersionDataTests(utils.TestCase):
|
|||
|
||||
self.assertEqual(clean_data, [
|
||||
{
|
||||
'collection': None,
|
||||
'max_microversion': None,
|
||||
'min_microversion': None,
|
||||
'version': (1, 0),
|
||||
'url': v1_url,
|
||||
'raw_status': 'SUPPORTED',
|
||||
},
|
||||
{
|
||||
'collection': None,
|
||||
'max_microversion': None,
|
||||
'min_microversion': None,
|
||||
'version': (1, 1),
|
||||
'url': v1_url,
|
||||
'raw_status': 'CURRENT',
|
||||
},
|
||||
{
|
||||
'collection': None,
|
||||
'max_microversion': None,
|
||||
'min_microversion': None,
|
||||
'version': (2, 0),
|
||||
'url': v2_url,
|
||||
'raw_status': 'SUPPORTED',
|
||||
},
|
||||
{
|
||||
'collection': None,
|
||||
'max_microversion': None,
|
||||
'min_microversion': None,
|
||||
'version': (2, 1),
|
||||
'url': v2_url,
|
||||
'raw_status': 'SUPPORTED',
|
||||
},
|
||||
{
|
||||
'collection': None,
|
||||
'max_microversion': None,
|
||||
'min_microversion': None,
|
||||
'version': (2, 2),
|
||||
'url': v2_url,
|
||||
'raw_status': 'CURRENT',
|
||||
|
|
Loading…
Reference in New Issue