Add memcache proxy preparing to factor out caching
Signed-off-by: martin f. krafft <madduck@madduck.net>
diff --git a/reclass/storage/memcache_proxy.py b/reclass/storage/memcache_proxy.py
new file mode 100644
index 0000000..7d9ab5e
--- /dev/null
+++ b/reclass/storage/memcache_proxy.py
@@ -0,0 +1,65 @@
+#
+# -*- coding: utf-8 -*-
+#
+# This file is part of reclass (http://github.com/madduck/reclass)
+#
+# Copyright © 2007–14 martin f. krafft <madduck@madduck.net>
+# Released under the terms of the Artistic Licence 2.0
+#
+
+from reclass.storage import NodeStorageBase
+
+STORAGE_NAME = 'memcache_proxy'
+
+class MemcacheProxy(NodeStorageBase):
+
+ def __init__(self, real_storage, cache_classes=True, cache_nodes=True,
+ cache_nodelist=True):
+ name = '{0}({1})'.format(STORAGE_NAME, real_storage.name)
+ super(MemcacheProxy, self).__init__(name)
+ self._real_storage = real_storage
+ self._cache_classes = cache_classes
+ if cache_classes:
+ self._classes_cache = {}
+ self._cache_nodes = cache_nodes
+ if cache_nodes:
+ self._nodes_cache = {}
+ self._cache_nodelist = cache_nodelist
+ if cache_nodelist:
+ self._nodelist_cache = None
+
+ name = property(lambda self: self._real_storage.name)
+
+ @staticmethod
+ def _cache_proxy(name, cache, getter):
+ try:
+ ret = cache[name]
+
+ except KeyError, e:
+ ret = getter(name)
+ cache[name] = ret
+
+ return ret
+
+ def get_node(self, name):
+ if not self._cache_nodes:
+ return self._real_storage.get_node(name)
+
+ return MemcacheProxy._cache_proxy(name, self._nodes_cache,
+ self._real_storage.get_node)
+
+ def get_class(self, name):
+ if not self._cache_classes:
+ return self._real_storage.get_class(name)
+
+ return MemcacheProxy._cache_proxy(name, self._classes_cache,
+ self._real_storage.get_class)
+
+ def enumerate_nodes(self):
+ if not self._cache_nodelist:
+ return self._real_storage.enumerate_nodes()
+
+ elif self._nodelist_cache is None:
+ self._nodelist_cache = self._real_storage.enumerate_nodes()
+
+ return self._nodelist_cache
diff --git a/reclass/storage/tests/__init__.py b/reclass/storage/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/reclass/storage/tests/__init__.py
diff --git a/reclass/storage/tests/test_memcache_proxy.py b/reclass/storage/tests/test_memcache_proxy.py
new file mode 100644
index 0000000..066c27e
--- /dev/null
+++ b/reclass/storage/tests/test_memcache_proxy.py
@@ -0,0 +1,85 @@
+#
+# -*- coding: utf-8 -*-
+#
+# This file is part of reclass (http://github.com/madduck/reclass)
+#
+# Copyright © 2007–14 martin f. krafft <madduck@madduck.net>
+# Released under the terms of the Artistic Licence 2.0
+#
+from reclass.storage.memcache_proxy import MemcacheProxy
+from reclass.storage import NodeStorageBase
+
+import unittest
+try:
+ import unittest.mock as mock
+except ImportError:
+ import mock
+
+class TestMemcacheProxy(unittest.TestCase):
+
+ def setUp(self):
+ self._storage = mock.MagicMock(spec_set=NodeStorageBase)
+
+ def test_no_nodes_caching(self):
+ p = MemcacheProxy(self._storage, cache_nodes=False)
+ NAME = 'foo'; NAME2 = 'bar'; RET = 'baz'
+ self._storage.get_node.return_value = RET
+ self.assertEqual(p.get_node(NAME), RET)
+ self.assertEqual(p.get_node(NAME), RET)
+ self.assertEqual(p.get_node(NAME2), RET)
+ self.assertEqual(p.get_node(NAME2), RET)
+ expected = [mock.call(NAME), mock.call(NAME),
+ mock.call(NAME2), mock.call(NAME2)]
+ self.assertListEqual(self._storage.get_node.call_args_list, expected)
+
+ def test_nodes_caching(self):
+ p = MemcacheProxy(self._storage, cache_nodes=True)
+ NAME = 'foo'; NAME2 = 'bar'; RET = 'baz'
+ self._storage.get_node.return_value = RET
+ self.assertEqual(p.get_node(NAME), RET)
+ self.assertEqual(p.get_node(NAME), RET)
+ self.assertEqual(p.get_node(NAME2), RET)
+ self.assertEqual(p.get_node(NAME2), RET)
+ expected = [mock.call(NAME), mock.call(NAME2)] # called once each
+ self.assertListEqual(self._storage.get_node.call_args_list, expected)
+
+ def test_no_classes_caching(self):
+ p = MemcacheProxy(self._storage, cache_classes=False)
+ NAME = 'foo'; NAME2 = 'bar'; RET = 'baz'
+ self._storage.get_class.return_value = RET
+ self.assertEqual(p.get_class(NAME), RET)
+ self.assertEqual(p.get_class(NAME), RET)
+ self.assertEqual(p.get_class(NAME2), RET)
+ self.assertEqual(p.get_class(NAME2), RET)
+ expected = [mock.call(NAME), mock.call(NAME),
+ mock.call(NAME2), mock.call(NAME2)]
+ self.assertListEqual(self._storage.get_class.call_args_list, expected)
+
+ def test_classes_caching(self):
+ p = MemcacheProxy(self._storage, cache_classes=True)
+ NAME = 'foo'; NAME2 = 'bar'; RET = 'baz'
+ self._storage.get_class.return_value = RET
+ self.assertEqual(p.get_class(NAME), RET)
+ self.assertEqual(p.get_class(NAME), RET)
+ self.assertEqual(p.get_class(NAME2), RET)
+ self.assertEqual(p.get_class(NAME2), RET)
+ expected = [mock.call(NAME), mock.call(NAME2)] # called once each
+ self.assertListEqual(self._storage.get_class.call_args_list, expected)
+
+ def test_nodelist_no_caching(self):
+ p = MemcacheProxy(self._storage, cache_nodelist=False)
+ p.enumerate_nodes()
+ p.enumerate_nodes()
+ expected = [mock.call(), mock.call()]
+ self.assertListEqual(self._storage.enumerate_nodes.call_args_list, expected)
+
+ def test_nodelist_caching(self):
+ p = MemcacheProxy(self._storage, cache_nodelist=True)
+ p.enumerate_nodes()
+ p.enumerate_nodes()
+ expected = [mock.call()] # once only
+ self.assertListEqual(self._storage.enumerate_nodes.call_args_list, expected)
+
+
+if __name__ == '__main__':
+ unittest.main()