Add flags to turn discovery on and off
If a user does not specify a version, that means they want whatever is in the catalog. However, they may still want discovery to be run for things like microversion information. The new parameter "discover_versions", if set to True, will run discovery even with no version parameter. The inverse of this is "skip_discovery" which will tell keystoneauth to not run discovery even if a version has been given. Note: This adds some parameters to some methods that get removed by change I54053336edf1b3c2bd35a77dbd78f56388b8e806 so we should not actually land this one until we're ready to land the stack up to and including that one. Note: This adds two new methods that will have behavior changes in patch I8ba948a712002775098b0a86c70f05e0c68250f5. Change-Id: I897c39743089c5994b51336a4ad44eebed33ec35
This commit is contained in:
parent
a4066a86b5
commit
cdc10d8741
|
@ -286,6 +286,10 @@ class Discover(object):
|
|||
def data_for(self, version, **kwargs):
|
||||
"""Return endpoint data for a version.
|
||||
|
||||
NOTE: This method raises a TypeError if version is None. It is
|
||||
kept for backwards compatability. New code should use
|
||||
versioned_data_for instead.
|
||||
|
||||
:param tuple 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.
|
||||
|
@ -306,6 +310,10 @@ class Discover(object):
|
|||
def url_for(self, version, **kwargs):
|
||||
"""Get the endpoint url for a version.
|
||||
|
||||
NOTE: This method raises a TypeError if version is None. It is
|
||||
kept for backwards compatability. New code should use
|
||||
versioned_url_for instead.
|
||||
|
||||
:param tuple 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.
|
||||
|
@ -316,6 +324,47 @@ 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, **kwargs):
|
||||
"""Return endpoint data that matches the version or url.
|
||||
|
||||
: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.
|
||||
|
||||
: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
|
||||
"""
|
||||
if version:
|
||||
version = normalize_version_number(version)
|
||||
if url:
|
||||
url = url.rstrip('/') + '/'
|
||||
|
||||
for data in self.version_data(reverse=True, **kwargs):
|
||||
if url and data['url'] and data['url'].rstrip('/') + '/' == url:
|
||||
return data
|
||||
if version and version_match(version, data['version']):
|
||||
return data
|
||||
|
||||
return None
|
||||
|
||||
def versioned_url_for(self, version, **kwargs):
|
||||
"""Get the endpoint url for a version.
|
||||
|
||||
:param tuple 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.
|
||||
|
||||
:returns: The url for the specified version or None if no match.
|
||||
:rtype: str
|
||||
"""
|
||||
data = self.versioned_data_for(version, **kwargs)
|
||||
return data['url'] if data else None
|
||||
|
||||
|
||||
class EndpointData(object):
|
||||
"""Normalized information about a discovered endpoint.
|
||||
|
@ -383,7 +432,8 @@ class EndpointData(object):
|
|||
@positional(3)
|
||||
def get_versioned_data(self, session, version,
|
||||
authenticated=False, allow=None, cache=None,
|
||||
allow_version_hack=True, project_id=None):
|
||||
allow_version_hack=True, project_id=None,
|
||||
discover_versions=False):
|
||||
"""Run version discovery for the service described.
|
||||
|
||||
Performs Version Discovery and returns a new EndpointData object with
|
||||
|
@ -406,6 +456,10 @@ class EndpointData(object):
|
|||
(optional)
|
||||
:param bool authenticated: Include a token in the discovery call.
|
||||
(optional) Defaults to False.
|
||||
:param bool discover_versions: Whether to perform version discovery
|
||||
even if a version string wasn't
|
||||
requested. This is useful for getting
|
||||
microversion information.
|
||||
|
||||
:raises keystoneauth1.exceptions.http.HttpError: An error from an
|
||||
invalid HTTP response.
|
||||
|
@ -416,7 +470,7 @@ class EndpointData(object):
|
|||
# This method should always return a new EndpointData
|
||||
new_data = copy.copy(self)
|
||||
|
||||
if not version:
|
||||
if not 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
|
||||
# defaulting to the most recent version.
|
||||
|
@ -425,12 +479,25 @@ class EndpointData(object):
|
|||
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)
|
||||
project_id=project_id, discover_versions=discover_versions)
|
||||
return new_data
|
||||
|
||||
def _set_version_info(self, session, version,
|
||||
authenticated=False, allow=None, cache=None,
|
||||
allow_version_hack=True, project_id=None):
|
||||
allow_version_hack=True, project_id=None,
|
||||
discover_versions=False):
|
||||
match_url = None
|
||||
if not 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
|
||||
# defaulting to the most recent version.
|
||||
return
|
||||
elif not version and discover_versions:
|
||||
# We want to run discovery, but we don't want to find different
|
||||
# endpoints than what's in the catalog
|
||||
allow_version_hack = False
|
||||
match_url = self.catalog_url
|
||||
|
||||
if project_id:
|
||||
self.project_id = project_id
|
||||
|
||||
|
@ -490,7 +557,8 @@ class EndpointData(object):
|
|||
# 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)
|
||||
discovered_data = disc.versioned_data_for(
|
||||
version, url=match_url, **allow)
|
||||
if not discovered_data:
|
||||
raise exceptions.DiscoveryFailure(
|
||||
"Version {version} requested, but was not found".format(
|
||||
|
|
|
@ -158,7 +158,9 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
|
|||
|
||||
def get_endpoint_data(self, session, service_type=None, interface=None,
|
||||
region_name=None, service_name=None, version=None,
|
||||
allow={}, allow_version_hack=True, **kwargs):
|
||||
allow={}, allow_version_hack=True,
|
||||
discover_versions=False, skip_discovery=False,
|
||||
**kwargs):
|
||||
"""Return a valid endpoint data for a service.
|
||||
|
||||
If a valid token is not present then a new one will be fetched using
|
||||
|
@ -185,6 +187,15 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
|
|||
:param bool allow_version_hack: Allow keystoneauth to hack up catalog
|
||||
URLS to support older schemes.
|
||||
(optional, default True)
|
||||
:param bool discover_versions: Whether to perform version discovery
|
||||
even if a version string wasn't
|
||||
requested. This is useful for getting
|
||||
microversion information.
|
||||
:param bool skip_discovery: Whether to skip version discovery even
|
||||
if a version has been given. This is useful
|
||||
if endpoint_override or similar has been
|
||||
given and grabbing additional information
|
||||
about the endpoint is not useful.
|
||||
|
||||
:raises keystoneauth1.exceptions.http.HttpError: An error from an
|
||||
invalid HTTP response.
|
||||
|
@ -226,21 +237,32 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
|
|||
if not endpoint_data:
|
||||
return None
|
||||
|
||||
if skip_discovery:
|
||||
return endpoint_data
|
||||
|
||||
try:
|
||||
return endpoint_data.get_versioned_data(
|
||||
session, version,
|
||||
project_id=project_id,
|
||||
authenticated=False,
|
||||
cache=self._discovery_cache,
|
||||
discover_versions=discover_versions,
|
||||
allow_version_hack=allow_version_hack, allow=allow)
|
||||
except (exceptions.DiscoveryFailure,
|
||||
exceptions.HttpError,
|
||||
exceptions.ConnectionError):
|
||||
return None
|
||||
# If a version was requested, we didn't find it, return
|
||||
# None.
|
||||
if version:
|
||||
return None
|
||||
# If one wasn't, then the endpoint_data we already have
|
||||
# should be fine
|
||||
return endpoint_data
|
||||
|
||||
def get_endpoint(self, session, service_type=None, interface=None,
|
||||
region_name=None, service_name=None, version=None,
|
||||
allow={}, allow_version_hack=True, **kwargs):
|
||||
allow={}, allow_version_hack=True,
|
||||
discover_versions=False, skip_discovery=False, **kwargs):
|
||||
"""Return a valid endpoint for a service.
|
||||
|
||||
If a valid token is not present then a new one will be fetched using
|
||||
|
@ -267,6 +289,15 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
|
|||
:param bool allow_version_hack: Allow keystoneauth to hack up catalog
|
||||
URLS to support older schemes.
|
||||
(optional, default True)
|
||||
:param bool discover_versions: Whether to perform version discovery
|
||||
even if a version string wasn't
|
||||
requested. This is useful for getting
|
||||
microversion information.
|
||||
:param bool skip_discovery: Whether to skip version discovery even
|
||||
if a version has been given. This is useful
|
||||
if endpoint_override or similar has been
|
||||
given and grabbing additional information
|
||||
about the endpoint is not useful.
|
||||
|
||||
:raises keystoneauth1.exceptions.http.HttpError: An error from an
|
||||
invalid HTTP response.
|
||||
|
@ -278,6 +309,8 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
|
|||
session, service_type=service_type, interface=interface,
|
||||
region_name=region_name, service_name=service_name,
|
||||
version=version, allow=allow,
|
||||
discover_versions=discover_versions,
|
||||
skip_discovery=skip_discovery,
|
||||
allow_version_hack=allow_version_hack, **kwargs)
|
||||
return endpoint_data.url if endpoint_data else None
|
||||
|
||||
|
|
|
@ -924,6 +924,122 @@ class CatalogHackTests(utils.TestCase):
|
|||
|
||||
self.assertEqual(self.V2_URL, endpoint)
|
||||
|
||||
def test_returns_original_skipping_discovery(self):
|
||||
token = fixture.V2Token()
|
||||
service = token.add_service(self.IDENTITY)
|
||||
service.add_endpoint(public=self.V2_URL,
|
||||
admin=self.V2_URL,
|
||||
internal=self.V2_URL)
|
||||
|
||||
self.stub_url('POST',
|
||||
['tokens'],
|
||||
base_url=self.V2_URL,
|
||||
json=token)
|
||||
|
||||
v2_auth = identity.V2Password(self.V2_URL,
|
||||
username=uuid.uuid4().hex,
|
||||
password=uuid.uuid4().hex)
|
||||
|
||||
sess = session.Session(auth=v2_auth)
|
||||
|
||||
endpoint = sess.get_endpoint(service_type=self.IDENTITY,
|
||||
interface='public',
|
||||
skip_discovery=True,
|
||||
version=(3, 0))
|
||||
|
||||
self.assertEqual(self.V2_URL, endpoint)
|
||||
|
||||
def test_forcing_discovery(self):
|
||||
v2_disc = fixture.V2Discovery(self.V2_URL)
|
||||
common_disc = fixture.DiscoveryList(href=self.BASE_URL)
|
||||
|
||||
v2_m = self.stub_url('GET',
|
||||
['v2.0'],
|
||||
base_url=self.BASE_URL,
|
||||
status_code=200,
|
||||
json={'version': v2_disc})
|
||||
|
||||
common_m = self.stub_url('GET',
|
||||
[],
|
||||
base_url=self.BASE_URL,
|
||||
status_code=300,
|
||||
json=common_disc)
|
||||
|
||||
token = fixture.V2Token()
|
||||
service = token.add_service(self.IDENTITY)
|
||||
service.add_endpoint(public=self.V2_URL,
|
||||
admin=self.V2_URL,
|
||||
internal=self.V2_URL)
|
||||
|
||||
self.stub_url('POST',
|
||||
['tokens'],
|
||||
base_url=self.V2_URL,
|
||||
json=token)
|
||||
|
||||
v2_auth = identity.V2Password(self.V2_URL,
|
||||
username=uuid.uuid4().hex,
|
||||
password=uuid.uuid4().hex)
|
||||
|
||||
sess = session.Session(auth=v2_auth)
|
||||
|
||||
# v2 auth with v2 url doesn't make any discovery calls.
|
||||
self.assertFalse(v2_m.called)
|
||||
self.assertFalse(common_m.called)
|
||||
|
||||
endpoint = sess.get_endpoint(service_type=self.IDENTITY,
|
||||
discover_versions=True)
|
||||
|
||||
# We should get the v2 document, but not the unversioned
|
||||
self.assertTrue(v2_m.called)
|
||||
self.assertFalse(common_m.called)
|
||||
|
||||
# got v2 url
|
||||
self.assertEqual(self.V2_URL, endpoint)
|
||||
|
||||
def test_forcing_discovery_list_returns_url(self):
|
||||
common_disc = fixture.DiscoveryList(href=self.BASE_URL)
|
||||
|
||||
# 2.0 doesn't usually return a list. This is testing that if
|
||||
# the catalog url returns an endpoint that has a discovery document
|
||||
# with more than one URL and that a different url would be returned
|
||||
# by "return the latest" rules, that we get the info of the url from
|
||||
# the catalog if we don't provide a version but do provide
|
||||
# discover_versions
|
||||
v2_m = self.stub_url('GET',
|
||||
['v2.0'],
|
||||
base_url=self.BASE_URL,
|
||||
status_code=200,
|
||||
json=common_disc)
|
||||
|
||||
token = fixture.V2Token()
|
||||
service = token.add_service(self.IDENTITY)
|
||||
service.add_endpoint(public=self.V2_URL,
|
||||
admin=self.V2_URL,
|
||||
internal=self.V2_URL)
|
||||
|
||||
self.stub_url('POST',
|
||||
['tokens'],
|
||||
base_url=self.V2_URL,
|
||||
json=token)
|
||||
|
||||
v2_auth = identity.V2Password(self.V2_URL,
|
||||
username=uuid.uuid4().hex,
|
||||
password=uuid.uuid4().hex)
|
||||
|
||||
sess = session.Session(auth=v2_auth)
|
||||
|
||||
# v2 auth with v2 url doesn't make any discovery calls.
|
||||
self.assertFalse(v2_m.called)
|
||||
|
||||
endpoint = sess.get_endpoint(service_type=self.IDENTITY,
|
||||
discover_versions=True)
|
||||
|
||||
# We should make the one call
|
||||
self.assertTrue(v2_m.called)
|
||||
|
||||
# got v2 url
|
||||
self.assertEqual(self.V2_URL, endpoint)
|
||||
|
||||
def test_getting_endpoints_on_auth_interface(self):
|
||||
disc = fixture.DiscoveryList(href=self.BASE_URL)
|
||||
self.stub_url('GET',
|
||||
|
|
|
@ -359,6 +359,32 @@ class VersionDataTests(utils.TestCase):
|
|||
|
||||
self.assertTrue(mock.called_once)
|
||||
|
||||
def test_data_for_url(self):
|
||||
mock = self.requests_mock.get(V3_URL,
|
||||
status_code=200,
|
||||
json=V3_VERSION_ENTRY)
|
||||
|
||||
disc = discover.Discover(self.session, V3_URL)
|
||||
for url in (V3_URL, V3_URL + '/'):
|
||||
data = disc.versioned_data_for(url=url)
|
||||
self.assertEqual(data['version'], (3, 0))
|
||||
self.assertEqual(data['raw_status'], 'stable')
|
||||
self.assertEqual(data['url'], V3_URL)
|
||||
|
||||
self.assertTrue(mock.called_once)
|
||||
|
||||
def test_data_for_no_version(self):
|
||||
mock = self.requests_mock.get(V3_URL,
|
||||
status_code=200,
|
||||
json=V3_VERSION_ENTRY)
|
||||
|
||||
disc = discover.Discover(self.session, V3_URL)
|
||||
|
||||
self.assertIsNone(disc.versioned_data_for(version=None))
|
||||
self.assertRaises(TypeError, disc.data_for, version=None)
|
||||
|
||||
self.assertTrue(mock.called_once)
|
||||
|
||||
def test_keystone_version_data(self):
|
||||
mock = self.requests_mock.get(BASE_URL,
|
||||
status_code=300,
|
||||
|
@ -383,19 +409,21 @@ class VersionDataTests(utils.TestCase):
|
|||
self.assertIn(v['version'], ((2, 0), (3, 0)))
|
||||
self.assertEqual(v['raw_status'], 'stable')
|
||||
|
||||
version = disc.data_for('v3.0')
|
||||
self.assertEqual((3, 0), version['version'])
|
||||
self.assertEqual('stable', version['raw_status'])
|
||||
self.assertEqual(V3_URL, version['url'])
|
||||
for meth in (disc.data_for, disc.versioned_data_for):
|
||||
version = meth('v3.0')
|
||||
self.assertEqual((3, 0), version['version'])
|
||||
self.assertEqual('stable', version['raw_status'])
|
||||
self.assertEqual(V3_URL, version['url'])
|
||||
|
||||
version = disc.data_for(2)
|
||||
self.assertEqual((2, 0), version['version'])
|
||||
self.assertEqual('stable', version['raw_status'])
|
||||
self.assertEqual(V2_URL, version['url'])
|
||||
version = meth(2)
|
||||
self.assertEqual((2, 0), version['version'])
|
||||
self.assertEqual('stable', version['raw_status'])
|
||||
self.assertEqual(V2_URL, version['url'])
|
||||
|
||||
self.assertIsNone(disc.url_for('v4'))
|
||||
self.assertEqual(V3_URL, disc.url_for('v3'))
|
||||
self.assertEqual(V2_URL, disc.url_for('v2'))
|
||||
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.assertTrue(mock.called_once)
|
||||
|
||||
|
@ -452,20 +480,22 @@ class VersionDataTests(utils.TestCase):
|
|||
},
|
||||
])
|
||||
|
||||
version = disc.data_for('v2.0')
|
||||
self.assertEqual((2, 0), version['version'])
|
||||
self.assertEqual('CURRENT', version['raw_status'])
|
||||
self.assertEqual(v2_url, version['url'])
|
||||
for meth in (disc.data_for, disc.versioned_data_for):
|
||||
version = meth('v2.0')
|
||||
self.assertEqual((2, 0), version['version'])
|
||||
self.assertEqual('CURRENT', version['raw_status'])
|
||||
self.assertEqual(v2_url, version['url'])
|
||||
|
||||
version = disc.data_for(1)
|
||||
self.assertEqual((1, 0), version['version'])
|
||||
self.assertEqual('CURRENT', version['raw_status'])
|
||||
self.assertEqual(v1_url, version['url'])
|
||||
version = meth(1)
|
||||
self.assertEqual((1, 0), version['version'])
|
||||
self.assertEqual('CURRENT', version['raw_status'])
|
||||
self.assertEqual(v1_url, version['url'])
|
||||
|
||||
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'))
|
||||
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.assertTrue(mock.called_once)
|
||||
|
||||
|
@ -534,22 +564,24 @@ class VersionDataTests(utils.TestCase):
|
|||
},
|
||||
])
|
||||
|
||||
for ver in (2, 2.1, 2.2):
|
||||
version = disc.data_for(ver)
|
||||
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 meth in (disc.data_for, disc.versioned_data_for):
|
||||
for ver in (2, 2.1, 2.2):
|
||||
version = meth(ver)
|
||||
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 = disc.data_for(ver)
|
||||
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 ver in (1, 1.1):
|
||||
version = meth(ver)
|
||||
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))
|
||||
|
||||
self.assertIsNone(disc.url_for('v3'))
|
||||
self.assertIsNone(disc.url_for('v2.3'))
|
||||
for meth in (disc.url_for, disc.versioned_url_for):
|
||||
self.assertIsNone(meth('v3'))
|
||||
self.assertIsNone(meth('v2.3'))
|
||||
|
||||
self.assertTrue(mock.called_once)
|
||||
|
||||
|
|
Loading…
Reference in New Issue