Cache the User's Project by Token ID

Project list fetched for each request. The patches caches the
project list and uses the token as the key in the cache. When
the user logout or switch project, the project list is removed
from the cache.

Change-Id: I2386d7a342cf02a0252e97cc48c5349ccab8a9eb
Closes-bug: 1241838
This commit is contained in:
Lin Hua Cheng
2013-10-24 15:23:51 -07:00
committed by lin-hua-cheng
parent 58da8b38a9
commit bd9fd598e6
3 changed files with 131 additions and 0 deletions

View File

@@ -489,6 +489,51 @@ class OpenStackAuthTestsV2(test.TestCase):
debug=False)
self.assertEqual(tenant_list, expected_tenants)
def test_tenant_list_caching(self):
tenants = [self.data.tenant_two, self.data.tenant_one]
expected_tenants = [self.data.tenant_one, self.data.tenant_two]
user = self.data.user
unscoped = self.data.unscoped_access_info
self.mox.StubOutWithMock(self.ks_client_module, "Client")
self.mox.StubOutWithMock(self.keystone_client_unscoped.tenants, "list")
self.ks_client_module.Client(user_id=user.id,
auth_url=settings.OPENSTACK_KEYSTONE_URL,
token=unscoped.auth_token,
insecure=False,
cacert=None,
debug=False)\
.AndReturn(self.keystone_client_unscoped)
self.keystone_client_unscoped.tenants.list().AndReturn(tenants)
self.mox.ReplayAll()
tenant_list = utils.get_project_list(
user_id=user.id,
auth_url=settings.OPENSTACK_KEYSTONE_URL,
token=unscoped.auth_token,
insecure=False,
cacert=None,
debug=False)
self.assertEqual(tenant_list, expected_tenants)
# Test to validate that requesting the project list again results
# to using the cache and will not make a Keystone call.
self.assertEqual(utils._PROJECT_CACHE.get(unscoped.auth_token),
expected_tenants)
tenant_list = utils.get_project_list(
user_id=user.id,
auth_url=settings.OPENSTACK_KEYSTONE_URL,
token=unscoped.auth_token,
insecure=False,
cacert=None,
debug=False)
self.assertEqual(tenant_list, expected_tenants)
utils.remove_project_cache(unscoped.auth_token)
self.assertIsNone(utils._PROJECT_CACHE.get(unscoped.auth_token))
def EndpointMetaFactory(endpoint_type):
def endpoint_wrapper(func):
@@ -979,6 +1024,53 @@ class OpenStackAuthTestsV3(test.TestCase):
debug=False)
self.assertEqual(project_list, expected_projects)
def test_tenant_list_caching(self):
projects = [self.data.project_two, self.data.project_one]
expected_projects = [self.data.project_one, self.data.project_two]
user = self.data.user
unscoped = self.data.unscoped_access_info
self.mox.StubOutWithMock(self.ks_client_module, "Client")
self.mox.StubOutWithMock(self.keystone_client_unscoped.projects,
"list")
self.ks_client_module.Client(user_id=user.id,
auth_url=settings.OPENSTACK_KEYSTONE_URL,
token=unscoped.auth_token,
insecure=False,
cacert=None,
debug=False)\
.AndReturn(self.keystone_client_unscoped)
self.keystone_client_unscoped.projects.list(user=user.id) \
.AndReturn(projects)
self.mox.ReplayAll()
project_list = utils.get_project_list(
user_id=user.id,
auth_url=settings.OPENSTACK_KEYSTONE_URL,
token=unscoped.auth_token,
insecure=False,
cacert=None,
debug=False)
self.assertEqual(project_list, expected_projects)
# Test to validate that requesting the project list again results
# to using the cache and will not make a Keystone call.
self.assertEqual(utils._PROJECT_CACHE.get(unscoped.auth_token),
expected_projects)
project_list = utils.get_project_list(
user_id=user.id,
auth_url=settings.OPENSTACK_KEYSTONE_URL,
token=unscoped.auth_token,
insecure=False,
cacert=None,
debug=False)
self.assertEqual(project_list, expected_projects)
utils.remove_project_cache(unscoped.auth_token)
self.assertIsNone(utils._PROJECT_CACHE.get(unscoped.auth_token))
class OpenStackAuthTestsV3WithPublicURL(OpenStackAuthTestsV3):
"""Test V3 with settings.OPENSTACK_ENDPOINT_TYPE = 'publicURL'."""

View File

@@ -11,18 +11,24 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import functools
from six.moves.urllib import parse as urlparse
from django.conf import settings
from django.contrib import auth
from django.contrib.auth import middleware
from django.contrib.auth import models
from django.utils import decorators
from django.utils import timezone
from keystoneclient.v2_0 import client as client_v2
from keystoneclient.v3 import client as client_v3
_PROJECT_CACHE = {}
"""
We need the request object to get the user, so we'll slightly modify the
existing django.contrib.auth.get_user method. To do so we update the
@@ -91,6 +97,37 @@ def is_safe_url(url, host=None):
return not netloc or netloc == host
def memoize_by_keyword_arg(cache, kw_keys):
"""Memoize a function using the list of keyword argument name as its key.
Wrap a function so that results for any keyword argument tuple are stored
in 'cache'. Note that the keyword args to the function must be usable as
dictionary keys.
:param cache: Dictionary object to store the results.
:param kw_keys: List of keyword arguments names. The values are used
for generating the key in the cache.
"""
def _decorator(func):
@functools.wraps(func, assigned=decorators.available_attrs(func))
def wrapper(*args, **kwargs):
mem_args = [kwargs[key] for key in kw_keys if key in kwargs]
mem_args = '__'.join(str(mem_arg) for mem_arg in mem_args)
if not mem_args:
return func(*args, **kwargs)
if mem_args in cache:
return cache[mem_args]
result = func(*args, **kwargs)
cache[mem_args] = result
return result
return wrapper
return _decorator
def remove_project_cache(token):
_PROJECT_CACHE.pop(token, None)
# Helper for figuring out keystone version
# Implementation will change when API version discovery is available
def get_keystone_version():
@@ -104,6 +141,7 @@ def get_keystone_client():
return client_v3
@memoize_by_keyword_arg(_PROJECT_CACHE, ('token', ))
def get_project_list(*args, **kwargs):
if get_keystone_version() < 3:
auth_url = kwargs.get('auth_url', '').replace('v3', 'v2.0')

View File

@@ -119,6 +119,7 @@ def delete_token(endpoint, token_id):
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
ca_cert = getattr(settings, "OPENSTACK_SSL_CACERT", None)
utils.remove_project_cache(token_id)
try:
if utils.get_keystone_version() < 3:
client = keystone_client_v2.Client(