Merge "Add caching to get_catalog"

This commit is contained in:
Jenkins 2015-11-05 22:39:21 +00:00 committed by Gerrit Code Review
commit 6a56c3b1b6
8 changed files with 88 additions and 11 deletions

View File

@ -18,6 +18,7 @@
import abc
import itertools
from oslo_cache import core as oslo_cache
from oslo_config import cfg
from oslo_log import log
import six
@ -35,12 +36,23 @@ from keystone import notifications
CONF = cfg.CONF
LOG = log.getLogger(__name__)
MEMOIZE = cache.get_memoization_decorator(group='catalog')
WHITELISTED_PROPERTIES = [
'tenant_id', 'user_id', 'public_bind_host', 'admin_bind_host',
'compute_host', 'admin_port', 'public_port',
'public_endpoint', 'admin_endpoint', ]
# This is a general cache region for catalog administration (CRUD operations).
MEMOIZE = cache.get_memoization_decorator(group='catalog')
# This builds a discrete cache region dedicated to complete service catalogs
# computed for a given user + project pair. Any write operation to create,
# modify or delete elements of the service catalog should invalidate this
# entire cache region.
COMPUTED_CATALOG_REGION = oslo_cache.create_region()
MEMOIZE_COMPUTED_CATALOG = cache.get_memoization_decorator(
group='catalog',
region=COMPUTED_CATALOG_REGION)
def format_url(url, substitutions, silent_keyerror_failures=None):
"""Formats a user-defined URL with the given substitutions.
@ -147,6 +159,7 @@ class Manager(manager.Manager):
raise exception.RegionNotFound(region_id=parent_region_id)
notifications.Audit.created(self._REGION, ret['id'], initiator)
COMPUTED_CATALOG_REGION.invalidate()
return ret
@MEMOIZE
@ -165,6 +178,7 @@ class Manager(manager.Manager):
ref = self.driver.update_region(region_id, region_ref)
notifications.Audit.updated(self._REGION, region_id, initiator)
self.get_region.invalidate(self, region_id)
COMPUTED_CATALOG_REGION.invalidate()
return ref
def delete_region(self, region_id, initiator=None):
@ -172,6 +186,7 @@ class Manager(manager.Manager):
ret = self.driver.delete_region(region_id)
notifications.Audit.deleted(self._REGION, region_id, initiator)
self.get_region.invalidate(self, region_id)
COMPUTED_CATALOG_REGION.invalidate()
return ret
except exception.NotFound:
raise exception.RegionNotFound(region_id=region_id)
@ -185,6 +200,7 @@ class Manager(manager.Manager):
service_ref.setdefault('name', '')
ref = self.driver.create_service(service_id, service_ref)
notifications.Audit.created(self._SERVICE, service_id, initiator)
COMPUTED_CATALOG_REGION.invalidate()
return ref
@MEMOIZE
@ -198,6 +214,7 @@ class Manager(manager.Manager):
ref = self.driver.update_service(service_id, service_ref)
notifications.Audit.updated(self._SERVICE, service_id, initiator)
self.get_service.invalidate(self, service_id)
COMPUTED_CATALOG_REGION.invalidate()
return ref
def delete_service(self, service_id, initiator=None):
@ -209,6 +226,7 @@ class Manager(manager.Manager):
for endpoint in endpoints:
if endpoint['service_id'] == service_id:
self.get_endpoint.invalidate(self, endpoint['id'])
COMPUTED_CATALOG_REGION.invalidate()
return ret
except exception.NotFound:
raise exception.ServiceNotFound(service_id=service_id)
@ -239,6 +257,7 @@ class Manager(manager.Manager):
ref = self.driver.create_endpoint(endpoint_id, endpoint_ref)
notifications.Audit.created(self._ENDPOINT, endpoint_id, initiator)
COMPUTED_CATALOG_REGION.invalidate()
return ref
def update_endpoint(self, endpoint_id, endpoint_ref, initiator=None):
@ -247,6 +266,7 @@ class Manager(manager.Manager):
ref = self.driver.update_endpoint(endpoint_id, endpoint_ref)
notifications.Audit.updated(self._ENDPOINT, endpoint_id, initiator)
self.get_endpoint.invalidate(self, endpoint_id)
COMPUTED_CATALOG_REGION.invalidate()
return ref
def delete_endpoint(self, endpoint_id, initiator=None):
@ -254,6 +274,7 @@ class Manager(manager.Manager):
ret = self.driver.delete_endpoint(endpoint_id)
notifications.Audit.deleted(self._ENDPOINT, endpoint_id, initiator)
self.get_endpoint.invalidate(self, endpoint_id)
COMPUTED_CATALOG_REGION.invalidate()
return ret
except exception.NotFound:
raise exception.EndpointNotFound(endpoint_id=endpoint_id)
@ -269,12 +290,17 @@ class Manager(manager.Manager):
def list_endpoints(self, hints=None):
return self.driver.list_endpoints(hints or driver_hints.Hints())
@MEMOIZE_COMPUTED_CATALOG
def get_catalog(self, user_id, tenant_id):
try:
return self.driver.get_catalog(user_id, tenant_id)
except exception.NotFound:
raise exception.NotFound('Catalog not found for user and tenant')
@MEMOIZE_COMPUTED_CATALOG
def get_v3_catalog(self, user_id, tenant_id):
return self.driver.get_v3_catalog(user_id, tenant_id)
@six.add_metaclass(abc.ABCMeta)
class CatalogDriverV8(object):

View File

@ -23,12 +23,16 @@ CONF = cfg.CONF
CACHE_REGION = cache.create_region()
def configure_cache():
cache.configure_cache_region(CONF, CACHE_REGION)
def configure_cache(region=None):
if region is None:
region = CACHE_REGION
cache.configure_cache_region(CONF, region)
def get_memoization_decorator(group, expiration_group=None):
return cache.get_memoization_decorator(CONF, CACHE_REGION, group,
def get_memoization_decorator(group, expiration_group=None, region=None):
if region is None:
region = CACHE_REGION
return cache.get_memoization_decorator(CONF, region, group,
expiration_group=expiration_group)

View File

@ -20,6 +20,7 @@ from oslo_config import cfg
from oslo_log import log
import six
from keystone import catalog
from keystone.common import dependency
from keystone.common import extension
from keystone.common import manager
@ -63,6 +64,25 @@ class Manager(manager.Manager):
def __init__(self):
super(Manager, self).__init__(CONF.endpoint_filter.driver)
def add_endpoint_to_project(self, endpoint_id, project_id):
self.driver.add_endpoint_to_project(endpoint_id, project_id)
catalog.COMPUTED_CATALOG_REGION.invalidate()
def remove_endpoint_to_project(self, endpoint_id, project_id):
self.driver.remove_endpoint_to_project(endpoint_id, project_id)
catalog.COMPUTED_CATALOG_REGION.invalidate()
def add_endpoint_group_to_project(self, endpoint_group_id, project_id):
self.driver.add_endpoint_group_to_project(
endpoint_group_id, project_id)
catalog.COMPUTED_CATALOG_REGION.invalidate()
def remove_endpoint_group_from_project(self, endpoint_group_id,
project_id):
self.driver.remove_endpoint_group_from_project(
endpoint_group_id, project_id)
catalog.COMPUTED_CATALOG_REGION.invalidate()
@six.add_metaclass(abc.ABCMeta)
class EndpointFilterDriverV8(object):

View File

@ -31,6 +31,7 @@ def load_backends():
# Configure and build the cache
cache.configure_cache()
cache.configure_cache(region=catalog.COMPUTED_CATALOG_REGION)
# Ensure that the identity driver is created before the assignment manager
# and that the assignment driver is created before the resource manager.

View File

@ -208,6 +208,22 @@ def skip_if_cache_disabled(*sections):
return wrapper
def skip_if_cache_is_enabled(*sections):
def wrapper(f):
@functools.wraps(f)
def inner(*args, **kwargs):
if CONF.cache.enabled:
for s in sections:
conf_sec = getattr(CONF, s, None)
if conf_sec is not None:
if getattr(conf_sec, 'caching', True):
raise testcase.TestSkipped('%s caching enabled.' %
s)
return f(*args, **kwargs)
return inner
return wrapper
def skip_if_no_multiple_domains_support(f):
"""Decorator to skip tests for identity drivers limited to one domain."""
@functools.wraps(f)

View File

@ -13,9 +13,13 @@
import fixtures
from keystone import catalog
from keystone.common import cache
CACHE_REGIONS = (cache.CACHE_REGION, catalog.COMPUTED_CATALOG_REGION)
class Cache(fixtures.Fixture):
"""A fixture for setting up the cache between test cases.
@ -31,8 +35,9 @@ class Cache(fixtures.Fixture):
# NOTE(morganfainberg): The only way to reconfigure the CacheRegion
# object on each setUp() call is to remove the .backend property.
if cache.CACHE_REGION.is_configured:
del cache.CACHE_REGION.backend
for region in CACHE_REGIONS:
if region.is_configured:
del region.backend
# ensure the cache region instance is setup
cache.configure_cache()
# ensure the cache region instance is setup
cache.configure_cache(region=region)

View File

@ -66,6 +66,9 @@ class TestTemplatedCatalog(unit.TestCase, test_backend.CatalogTests):
catalog_ref = self.catalog_api.get_catalog('foo', 'bar')
self.assertDictEqual(self.DEFAULT_FIXTURE, catalog_ref)
# NOTE(lbragstad): This test is skipped because the catalog is being
# modified within the test and not through the API.
@unit.skip_if_cache_is_enabled('catalog')
def test_catalog_ignored_malformed_urls(self):
# both endpoints are in the catalog
catalog_ref = self.catalog_api.get_catalog('foo', 'bar')
@ -126,9 +129,10 @@ class TestTemplatedCatalog(unit.TestCase, test_backend.CatalogTests):
def test_get_catalog_ignores_endpoints_with_invalid_urls(self):
user_id = uuid.uuid4().hex
tenant_id = None
# If the URL has no 'tenant_id' to substitute, we will skip the
# endpoint which contains this kind of URL.
catalog_ref = self.catalog_api.get_v3_catalog(user_id, tenant_id=None)
catalog_ref = self.catalog_api.get_v3_catalog(user_id, tenant_id)
exp_catalog = [
{'endpoints': [],
'type': 'compute',

View File

@ -847,7 +847,8 @@ class TestCatalogAPISQL(unit.TestCase):
# If the URL has no 'tenant_id' to substitute, we will skip the
# endpoint which contains this kind of URL, negative check.
catalog = self.catalog_api.get_v3_catalog(user_id, tenant_id=None)
tenant_id = None
catalog = self.catalog_api.get_v3_catalog(user_id, tenant_id)
self.assertThat(catalog[0]['endpoints'], matchers.HasLength(1))
def test_get_catalog_always_returns_service_name(self):