Implemented DictCacheBackend

This backend is based on dictionary and memorycache client from
https://github.com/openstack/oslo-incubator/blob/master/openstack/common/memorycache.py

This backend was implemented to be used in Nova
because there is no appropriate default backend in dogpile.

Co-Authored-By: Pavel Kholkin <pkholkin@mirantis.com>

Change-Id: Ie3e6c8a3671bc28bfd6967e8758c1419c3c8d501
This commit is contained in:
Sergey Nikitin 2015-07-03 11:47:15 +03:00
parent e4c7731a78
commit 4b0cac8fb6
4 changed files with 185 additions and 1 deletions

View File

@ -16,3 +16,6 @@
.. automodule:: oslo_cache.backends.noop
:members:
.. automodule:: oslo_cache.backends.dictionary
:members:

View File

@ -0,0 +1,84 @@
# Copyright 2015 Mirantis Inc
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""dogpile.cache backend that uses dictionary as a storage"""
from dogpile.cache import api
from oslo_utils import timeutils
__all__ = [
'DictCacheBackend'
]
# Value for nonexistent and expired keys
NO_VALUE = api.NO_VALUE
class DictCacheBackend(api.CacheBackend):
"""A DictCacheBackend based on dictionary.
Arguments accepted in the arguments dictionary:
:param expiration_time: interval in seconds to indicate maximum
time-to-live value. This parameter is common for a single backend
instance. Default expiration_time value is 0, that means that all keys
have infinite time-to-live value.
:type expiration_time: real
"""
def __init__(self, arguments):
self.expiration_time = arguments.get('expiration_time', 0)
self.cache = {}
def get(self, key):
"""Retrieves the value for a key or NO_VALUE for nonexistent and
expired keys.
:param key: dictionary key
"""
(value, timeout) = self.cache.get(key, (NO_VALUE, 0))
if self.expiration_time > 0 and timeutils.utcnow_ts() >= timeout:
self.cache.pop(key, None)
return NO_VALUE
return value
def set(self, key, value):
"""Sets the value for a key.
Expunges expired keys during each set.
:param key: dictionary key
:param value: value associated with the key
"""
self._clear()
timeout = 0
if self.expiration_time > 0:
timeout = timeutils.utcnow_ts() + self.expiration_time
self.cache[key] = (value, timeout)
def delete(self, key):
"""Deletes the value associated with the key if it exists.
:param key: dictionary key
"""
self.cache.pop(key, None)
def _clear(self):
"""Expunges expired keys."""
now = timeutils.utcnow_ts()
for k in list(self.cache):
(_value, timeout) = self.cache[k]
if timeout > 0 and now >= timeout:
del self.cache[k]

View File

@ -21,6 +21,8 @@ When this library is imported, it registers the following backends in
* ``oslo_cache.mongo`` - :class:`oslo_cache.backends.mongo.MongoCacheBackend`
* ``oslo_cache.memcache_pool`` -
:class:`oslo_cache.backends.memcache_pool.PooledMemcachedBackend`
* ``oslo_cache.dict`` -
:class:`oslo_cache.backends.dictionary.DictCacheBackend`
To use this library:
@ -62,7 +64,8 @@ _BACKENDS = [
('oslo_cache.noop', 'oslo_cache.backends.noop', 'NoopCacheBackend'),
('oslo_cache.mongo', 'oslo_cache.backends.mongo', 'MongoCacheBackend'),
('oslo_cache.memcache_pool', 'oslo_cache.backends.memcache_pool',
'PooledMemcachedBackend')
'PooledMemcachedBackend'),
('oslo_cache.dict', 'oslo_cache.backends.dictionary', 'DictCacheBackend'),
]
for backend in _BACKENDS:

View File

@ -0,0 +1,94 @@
# Copyright 2015 Mirantis Inc
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from dogpile.cache import api
from dogpile.cache import region as dp_region
from oslo_cache.tests import test_cache
from oslo_config import fixture as config_fixture
from oslo_utils import fixture as time_fixture
NO_VALUE = api.NO_VALUE
KEY = 'test_key'
VALUE = 'test_value'
class CacheDictBackendTest(test_cache.BaseTestCase):
def setUp(self):
super(CacheDictBackendTest, self).setUp()
self.config_fixture = self.useFixture(config_fixture.Config())
self.config_fixture.config(group='cache', backend='oslo_cache.dict')
self.time_fixture = self.useFixture(time_fixture.TimeFixture())
self.region = dp_region.make_region()
self.region.configure(
'oslo_cache.dict', arguments={'expiration_time': 0.5})
def test_dict_backend(self):
self.assertIs(NO_VALUE, self.region.get(KEY))
self.region.set(KEY, VALUE)
self.assertEqual(VALUE, self.region.get(KEY))
self.region.delete(KEY)
self.assertIs(NO_VALUE, self.region.get(KEY))
def test_dict_backend_expiration_time(self):
self.region.set(KEY, VALUE)
self.assertEqual(VALUE, self.region.get(KEY))
self.time_fixture.advance_time_seconds(1)
self.assertIs(NO_VALUE, self.region.get(KEY))
def test_dict_backend_clear_cache(self):
self.region.set(KEY, VALUE)
self.time_fixture.advance_time_seconds(1)
self.assertEqual(1, len(self.region.backend.cache))
self.region.backend._clear()
self.assertEqual(0, len(self.region.backend.cache))
def test_dict_backend_zero_expiration_time(self):
self.region = dp_region.make_region()
self.region.configure(
'oslo_cache.dict', arguments={'expiration_time': 0})
self.region.set(KEY, VALUE)
self.time_fixture.advance_time_seconds(1)
self.assertEqual(VALUE, self.region.get(KEY))
self.assertEqual(1, len(self.region.backend.cache))
self.region.backend._clear()
self.assertEqual(VALUE, self.region.get(KEY))
self.assertEqual(1, len(self.region.backend.cache))
def test_dict_backend_multi_keys(self):
self.region.set('key1', 'value1')
self.region.set('key2', 'value2')
self.time_fixture.advance_time_seconds(1)
self.region.set('key3', 'value3')
self.assertEqual(1, len(self.region.backend.cache))
self.assertIs(NO_VALUE, self.region.get('key1'))
self.assertIs(NO_VALUE, self.region.get('key2'))
self.assertEqual('value3', self.region.get('key3'))
def test_dict_backend_rewrite_value(self):
self.region.set(KEY, 'value1')
self.region.set(KEY, 'value2')
self.assertEqual('value2', self.region.get(KEY))