Rework discovery cache

Allow the user to pass in a cache dict that will be used
in addition to the session and auth level caches. Make Session
always have a discovery_cache attribute and allow the user to
provide the cache at Session creation time. Finally, rename
the private variable to _discovery_cache from _endpoint_cache
since it's caching discovery objects, not endpoints.

Co-Authored-By: Samuel de Medeiros Queiroz <samueldmq@gmail.com>
Change-Id: I0a0f489fd3bbecc4596e99acafcde1bff4e181f7
This commit is contained in:
Monty Taylor 2017-06-24 09:41:58 -04:00
parent 0a62f6acd5
commit 68e0fe5179
No known key found for this signature in database
GPG Key ID: 7BAE94BC7141A594
3 changed files with 90 additions and 17 deletions

View File

@ -44,7 +44,7 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
self.auth_ref = None
self.reauthenticate = reauthenticate
self._endpoint_cache = {}
self._discovery_cache = {}
self._lock = threading.Lock()
@abc.abstractmethod
@ -368,18 +368,21 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
:returns: A discovery object with the results of looking up that URL.
"""
# NOTE(jamielennox): we want to cache endpoints on the session as well
# so that they maintain sharing between auth plugins. Create a cache on
# the session if it doesn't exist already.
try:
session_endpoint_cache = session._identity_endpoint_cache
except AttributeError:
session_endpoint_cache = session._identity_endpoint_cache = {}
# There are between one and three different caches. The user may have
# passed one in. There is definitely one on the session, and there is
# one on the auth plugin if the Session has an auth plugin.
caches = []
# NOTE(jamielennox): There is a cache located on both the session
# object and the auth plugin object so that they can be shared and the
# cache is still usable
for cache in (self._endpoint_cache, session_endpoint_cache):
# If the session has a cache, check it first, since it could have been
# provided by the user at Session creation time.
if not hasattr(session, '_discovery_cache'):
session._discovery_cache = {}
caches.append(session._discovery_cache)
# Check the auth cache
caches.append(self._discovery_cache)
for cache in caches:
disc = cache.get(url)
if disc:
@ -387,8 +390,13 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
else:
disc = discover.Discover(session, url,
authenticated=authenticated)
self._endpoint_cache[url] = disc
session_endpoint_cache[url] = disc
if disc:
# Whether we get one from fetching or from cache, set it in the
# caches. This assures that if we combine sessions and auth plugins
# that we don't make unnecesary calls.
for cache in caches:
cache[url] = disc
return disc

View File

@ -221,6 +221,13 @@ class Session(object):
will be added to the user agent. This
can be used by libraries that are part
of the communication process.
:param dict discovery_cache: A dict to be used for caching of discovery
information. This is normally managed
transparently, but if the user wants to
share a single cache across multiple sessions
that do not share an auth plugin, it can
be provided here. (optional, defaults to
None which means automatically manage)
"""
user_agent = None
@ -233,7 +240,8 @@ class Session(object):
def __init__(self, auth=None, session=None, original_ip=None, verify=True,
cert=None, timeout=None, user_agent=None,
redirect=_DEFAULT_REDIRECT_LIMIT, additional_headers=None,
app_name=None, app_version=None, additional_user_agent=None):
app_name=None, app_version=None, additional_user_agent=None,
discovery_cache=None):
self.auth = auth
self.session = _construct_session(session)
@ -247,6 +255,9 @@ class Session(object):
self.app_version = app_version
self.additional_user_agent = additional_user_agent or []
self._determined_user_agent = None
if discovery_cache is None:
discovery_cache = {}
self._discovery_cache = discovery_cache
if timeout is not None:
self.timeout = float(timeout)

View File

@ -110,7 +110,7 @@ class CommonIdentityTests(object):
self.assertEqual(200, resp.status_code)
self.assertEqual(new_body, resp.text)
def test_discovery_uses_session_cache(self):
def test_discovery_uses_provided_session_cache(self):
# register responses such that if the discovery URL is hit more than
# once then the response will be invalid and not point to COMPUTE_ADMIN
resps = [{'json': self.TEST_DISCOVERY}, {'status_code': 500}]
@ -119,9 +119,10 @@ class CommonIdentityTests(object):
body = 'SUCCESS'
self.stub_url('GET', ['path'], text=body)
cache = {}
# now either of the two plugins I use, it should not cause a second
# request to the discovery url.
s = session.Session()
s = session.Session(discovery_cache=cache)
a = self.create_auth_plugin()
b = self.create_auth_plugin()
@ -134,6 +135,35 @@ class CommonIdentityTests(object):
self.assertEqual(200, resp.status_code)
self.assertEqual(body, resp.text)
self.assertIn(self.TEST_COMPUTE_ADMIN, cache.keys())
def test_discovery_uses_session_cache(self):
# register responses such that if the discovery URL is hit more than
# once then the response will be invalid and not point to COMPUTE_ADMIN
resps = [{'json': self.TEST_DISCOVERY}, {'status_code': 500}]
self.requests_mock.get(self.TEST_COMPUTE_ADMIN, resps)
body = 'SUCCESS'
self.stub_url('GET', ['path'], text=body)
filter = {'service_type': 'compute', 'interface': 'admin',
'version': self.version}
# create a session and call the endpoint, causing its cache to be set
sess = session.Session()
sess.get('/path', auth=self.create_auth_plugin(),
endpoint_filter=filter)
self.assertIn(self.TEST_COMPUTE_ADMIN, sess._discovery_cache.keys())
# now either of the two plugins I use, it should not cause a second
# request to the discovery url.
a = self.create_auth_plugin()
b = self.create_auth_plugin()
for auth in (a, b):
resp = sess.get('/path', auth=auth, endpoint_filter=filter)
self.assertEqual(200, resp.status_code)
self.assertEqual(body, resp.text)
def test_discovery_uses_plugin_cache(self):
# register responses such that if the discovery URL is hit more than
@ -160,6 +190,30 @@ class CommonIdentityTests(object):
self.assertEqual(200, resp.status_code)
self.assertEqual(body, resp.text)
def test_discovery_uses_session_plugin_cache(self):
# register responses such that if the discovery URL is hit more than
# once then the response will be invalid and not point to COMPUTE_ADMIN
resps = [{'json': self.TEST_DISCOVERY}, {'status_code': 500}]
self.requests_mock.get(self.TEST_COMPUTE_ADMIN, resps)
body = 'SUCCESS'
self.stub_url('GET', ['path'], text=body)
filter = {'service_type': 'compute', 'interface': 'admin',
'version': self.version}
# create a plugin and call the endpoint, causing its cache to be set
plugin = self.create_auth_plugin()
session.Session().get('/path', auth=plugin, endpoint_filter=filter)
self.assertIn(self.TEST_COMPUTE_ADMIN, plugin._discovery_cache.keys())
# with the plugin in the session, no more calls to the discovery URL
sess = session.Session(auth=plugin)
for auth in (plugin, self.create_auth_plugin()):
resp = sess.get('/path', auth=auth, endpoint_filter=filter)
self.assertEqual(200, resp.status_code)
self.assertEqual(body, resp.text)
def test_discovering_with_no_data(self):
# which returns discovery information pointing to TEST_URL but there is
# no data there.