Add Memory Isolating Cache Proxy
Created a specialized dogpile.cache proxy to isolate in-memory data in the cache region from modificaiton during tests using liberal application of copy.deepcopy for get/set operations. partial-blueprint: caching-layer-for-driver-calls Change-Id: Id6d27219b75ea7e6709d220542dd31c4a6c7aa04
This commit is contained in:
parent
8fdfbf04ba
commit
6dce45b1d2
@ -231,11 +231,14 @@ behavior is that subsystem caching is enabled, but the global toggle is set to d
|
||||
* ``dogpile.cache.memory`` - in-memory cache
|
||||
|
||||
.. WARNING::
|
||||
``dogpile.cache.memory`` is not suitable for use outside of testing or
|
||||
small workloads as it does not cleanup it's internal cache on cache
|
||||
expiration and does not share cache between processes. This means
|
||||
that caching and cache invalidation will not be consistent or reliable
|
||||
when using ``Keystone`` under HTTPD or similar configurations.
|
||||
``dogpile.cache.memory`` is not suitable for use outside of unit testing
|
||||
as it does not cleanup it's internal cache on cache expiration, does
|
||||
not provide isolation to the cached data (values in the store can be
|
||||
inadvertently changed without extra layers of data protection added),
|
||||
and does not share cache between processes. This means that caching
|
||||
and cache invalidation will not be consistent or reliable
|
||||
when using ``Keystone`` and the ``dogpile.cache.memory`` backend under
|
||||
any real workload.
|
||||
|
||||
* ``expiration_time`` - int, the default length of time to cache a specific value. A value of ``0``
|
||||
indicates to not cache anything. It is recommended that the ``enabled`` option be used to disable
|
||||
|
1
keystone/common/cache/core.py
vendored
1
keystone/common/cache/core.py
vendored
@ -17,7 +17,6 @@
|
||||
"""Keystone Caching Layer Implementation."""
|
||||
|
||||
import dogpile.cache
|
||||
|
||||
from dogpile.cache import proxy
|
||||
from dogpile.cache import util
|
||||
|
||||
|
@ -14,6 +14,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
|
||||
from dogpile.cache import api
|
||||
from dogpile.cache import proxy
|
||||
|
||||
@ -27,9 +29,52 @@ NO_VALUE = api.NO_VALUE
|
||||
SHOULD_CACHE = cache.should_cache_fn('cache')
|
||||
|
||||
|
||||
def _copy_value(value):
|
||||
if value is not NO_VALUE:
|
||||
value = copy.deepcopy(value)
|
||||
return value
|
||||
|
||||
|
||||
# NOTE(morganfainberg): WARNING - It is not recommended to use the Memory
|
||||
# backend for dogpile.cache in a real deployment under any circumstances. The
|
||||
# backend does no cleanup of expired values and therefore will leak memory. The
|
||||
# backend is not implemented in a way to share data across processes (e.g.
|
||||
# Keystone in HTTPD. This proxy is a hack to get around the lack of isolation
|
||||
# of values in memory. Currently it blindly stores and retrieves the values
|
||||
# from the cache, and modifications to dicts/lists/etc returned can result in
|
||||
# changes to the cached values. In short, do not use the dogpile.cache.memory
|
||||
# backend unless you are running tests or expecting odd/strange results.
|
||||
class CacheIsolatingProxy(proxy.ProxyBackend):
|
||||
"""Proxy that forces a memory copy of stored values.
|
||||
The default in-memory cache-region does not perform a copy on values it
|
||||
is meant to cache. Therefore if the value is modified after set or after
|
||||
get, the cached value also is modified. This proxy does a copy as the last
|
||||
thing before storing data.
|
||||
"""
|
||||
def get(self, key):
|
||||
value = _copy_value(self.proxied.get(key))
|
||||
return value
|
||||
|
||||
def get_multi(self, keys):
|
||||
values = []
|
||||
for value in self.proxied.get_multi(keys):
|
||||
value = _copy_value(value)
|
||||
values.append(value)
|
||||
return values
|
||||
|
||||
def set(self, key, value):
|
||||
value = _copy_value(value)
|
||||
self.proxied.set(key, value)
|
||||
|
||||
def set_multi(self, mapping):
|
||||
for key, value in mapping.items():
|
||||
value = _copy_value(value)
|
||||
self._cache[key] = value
|
||||
|
||||
|
||||
class TestProxy(proxy.ProxyBackend):
|
||||
def get(self, key):
|
||||
value = self.proxied.get(key)
|
||||
value = _copy_value(self.proxied.get(key))
|
||||
if value != NO_VALUE:
|
||||
value[0].cached = True
|
||||
return value
|
||||
|
@ -18,6 +18,7 @@ driver = keystone.token.backends.kvs.Token
|
||||
backend = dogpile.cache.memory
|
||||
enabled = True
|
||||
debug_cache_backend = True
|
||||
proxies = keystone.tests.test_cache.CacheIsolatingProxy
|
||||
|
||||
[oauth1]
|
||||
driver = keystone.contrib.oauth1.backends.kvs.OAuth1
|
||||
|
Loading…
Reference in New Issue
Block a user