diff --git a/cinder/api/openstack/wsgi.py b/cinder/api/openstack/wsgi.py index 99ba8e49094..77ac41715e9 100644 --- a/cinder/api/openstack/wsgi.py +++ b/cinder/api/openstack/wsgi.py @@ -65,6 +65,80 @@ class Request(webob.Request): """Add some OpenStack API-specific logic to the base webob.Request.""" + def __init__(self, *args, **kwargs): + super(Request, self).__init__(*args, **kwargs) + self._resource_cache = {} + + def cache_resource(self, resource_to_cache, id_attribute='id', name=None): + """Cache the given resource. + + Allow API methods to cache objects, such as results from a DB query, + to be used by API extensions within the same API request. + + The resource_to_cache can be a list or an individual resource, + but ultimately resources are cached individually using the given + id_attribute. + + Different resources types might need to be cached during the same + request, they can be cached using the name parameter. For example: + + Controller 1: + request.cache_resource(db_volumes, 'volumes') + request.cache_resource(db_volume_types, 'types') + Controller 2: + db_volumes = request.cached_resource('volumes') + db_type_1 = request.cached_resource_by_id('1', 'types') + + If no name is given, a default name will be used for the resource. + + An instance of this class only lives for the lifetime of a + single API request, so there's no need to implement full + cache management. + """ + if not isinstance(resource_to_cache, list): + resource_to_cache = [resource_to_cache] + if not name: + name = self.path + cached_resources = self._resource_cache.setdefault(name, {}) + for resource in resource_to_cache: + cached_resources[resource[id_attribute]] = resource + + def cached_resource(self, name=None): + """Get the cached resources cached under the given resource name. + + Allow an API extension to get previously stored objects within + the same API request. + + Note that the object data will be slightly stale. + + :returns: a dict of id_attribute to the resource from the cached + resources, an empty map if an empty collection was cached, + or None if nothing has been cached yet under this name + """ + if not name: + name = self.path + if name not in self._resource_cache: + # Nothing has been cached for this key yet + return None + return self._resource_cache[name] + + def cached_resource_by_id(self, resource_id, name=None): + """Get a resource by ID cached under the given resource name. + + Allow an API extension to get a previously stored object + within the same API request. This is basically a convenience method + to lookup by ID on the dictionary of all cached resources. + + Note that the object data will be slightly stale. + + :returns: the cached resource or None if the item is not in the cache + """ + resources = self.cached_resource(name) + if not resources: + # Nothing has been cached yet for this key yet + return None + return resources.get(resource_id) + def best_match_content_type(self): """Determine the requested response content-type.""" if 'cinder.best_content_type' not in self.environ: diff --git a/cinder/tests/api/openstack/test_wsgi.py b/cinder/tests/api/openstack/test_wsgi.py index bce238d45d4..3e00810ee78 100644 --- a/cinder/tests/api/openstack/test_wsgi.py +++ b/cinder/tests/api/openstack/test_wsgi.py @@ -107,6 +107,40 @@ def fake_best_match(self, offers, default_match=None): request.headers.pop('Accept-Language') self.assertEqual(request.best_match_language(), None) + def test_cache_and_retrieve_resources(self): + request = wsgi.Request.blank('/foo') + # Test that trying to retrieve a cached object on + # an empty cache fails gracefully + self.assertIsNone(request.cached_resource()) + self.assertIsNone(request.cached_resource_by_id('r-0')) + + resources = [] + for x in xrange(3): + resources.append({'id': 'r-%s' % x}) + + # Cache an empty list of resources using the default name + request.cache_resource([]) + self.assertEqual({}, request.cached_resource()) + self.assertIsNone(request.cached_resource('r-0')) + # Cache some resources + request.cache_resource(resources[:2]) + # Cache one resource + request.cache_resource(resources[2]) + # Cache a different resource name + other_resource = {'id': 'o-0'} + request.cache_resource(other_resource, name='other-resource') + + self.assertEqual(resources[0], request.cached_resource_by_id('r-0')) + self.assertEqual(resources[1], request.cached_resource_by_id('r-1')) + self.assertEqual(resources[2], request.cached_resource_by_id('r-2')) + self.assertIsNone(request.cached_resource_by_id('r-3')) + self.assertEqual({'r-0': resources[0], + 'r-1': resources[1], + 'r-2': resources[2]}, request.cached_resource()) + self.assertEqual(other_resource, + request.cached_resource_by_id('o-0', + name='other-resource')) + class ActionDispatcherTest(test.TestCase): def test_dispatch(self):