1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
|
""" ``Bcfg2.Server.Cache`` is an implementation of a simple
memory-backed cache. Right now this doesn't provide many features, but
more (time-based expiration, etc.) can be added as necessary.
The normal workflow is to get a Cache object, which is simply a dict
interface to the unified cache that automatically uses a certain tag
set. For instance:
.. code-block:: python
groupcache = Bcfg2.Server.Cache.Cache("Probes", "probegroups")
groupcache['foo.example.com'] = ['group1', 'group2']
This would create a Cache object that automatically tags its entries
with ``frozenset(["Probes", "probegroups"])``, and store the list
``['group1', 'group1']`` with the *additional* tag
``foo.example.com``. So the unified backend cache would then contain
a single entry:
.. code-block:: python
{frozenset(["Probes", "probegroups", "foo.example.com"]):
['group1', 'group2']}
In addition to the dict interface, Cache objects (returned from
:func:`Bcfg2.Server.Cache.Cache`) have one additional method,
``expire()``, which is mostly identical to
:func:`Bcfg2.Server.Cache.expire`, except that it is specific to the
tag set of the cache object. E.g., to expire all ``foo.example.com``
records for a given cache, you could do:
.. code-block:: python
groupcache = Bcfg2.Server.Cache.Cache("Probes", "probegroups")
groupcache.expire("foo.example.com")
This is mostly functionally identical to:
.. code-block:: python
Bcfg2.Server.Cache.expire("Probes", "probegroups", "foo.example.com")
It's not completely identical, though; the first example will expire,
at most, exactly one item from the cache. The second example will
expire all items that are tagged with a superset of the given tags.
To illustrate the difference, consider the following two examples:
.. code-block:: python
groupcache = Bcfg2.Server.Cache.Cache("Probes")
groupcache.expire("probegroups")
Bcfg2.Server.Cache.expire("Probes", "probegroups")
The former will not expire any data, because there is no single datum
tagged with ``"Probes", "probegroups"``. The latter will expire *all*
items tagged with ``"Probes", "probegroups"`` -- i.e., the entire
cache. In this case, the latter call is equivalent to:
.. code-block:: python
groupcache = Bcfg2.Server.Cache.Cache("Probes", "probegroups")
groupcache.expire()
"""
from Bcfg2.Compat import MutableMapping
class _Cache(MutableMapping):
""" The object returned by :func:`Bcfg2.Server.Cache.Cache` that
presents a dict-like interface to the portion of the unified cache
that uses the specified tags. """
def __init__(self, registry, tags):
self._registry = registry
self._tags = tags
def __getitem__(self, key):
return self._registry[self._tags | set([key])]
def __setitem__(self, key, value):
self._registry[self._tags | set([key])] = value
def __delitem__(self, key):
del self._registry[self._tags | set([key])]
def __iter__(self):
for item in self._registry.iterate(*self._tags):
yield list(item.difference(self._tags))[0]
def keys(self):
""" List cache keys """
return list(iter(self))
def __len__(self):
return len(list(iter(self)))
def expire(self, key=None):
""" expire all items, or a specific item, from the cache """
if key is None:
expire(*self._tags)
else:
tags = self._tags | set([key])
# py 2.5 doesn't support mixing *args and explicit keyword
# args
kwargs = dict(exact=True)
expire(*tags, **kwargs)
def __repr__(self):
return repr(dict(self))
def __str__(self):
return str(dict(self))
class _CacheRegistry(dict):
""" The grand unified cache backend which contains all cache
items. """
def iterate(self, *tags):
""" Iterate over all items that match the given tags *and*
have exactly one additional tag. This is used to get items
for :class:`Bcfg2.Server.Cache._Cache` objects that have been
instantiated via :func:`Bcfg2.Server.Cache.Cache`. """
tags = frozenset(tags)
for key in self.keys():
if key.issuperset(tags) and len(key.difference(tags)) == 1:
yield key
def iter_all(self, *tags):
""" Iterate over all items that match the given tags,
regardless of how many additional tags they have (or don't
have). This is used to expire all cache data that matches a
set of tags. """
tags = frozenset(tags)
for key in list(self.keys()):
if key.issuperset(tags):
yield key
_cache = _CacheRegistry() # pylint: disable=C0103
_hooks = [] # pylint: disable=C0103
def Cache(*tags): # pylint: disable=C0103
""" A dict interface to the cache data tagged with the given
tags. """
return _Cache(_cache, frozenset(tags))
def expire(*tags, **kwargs):
""" Expire all items, a set of items, or one specific item from
the cache. If ``exact`` is set to True, then if the given tag set
doesn't match exactly one item in the cache, nothing will be
expired. """
exact = kwargs.pop("exact", False)
count = 0
if not tags:
count = len(_cache)
_cache.clear()
elif exact:
if frozenset(tags) in _cache:
count = 1
del _cache[frozenset(tags)]
else:
for match in _cache.iter_all(*tags):
count += 1
del _cache[match]
for hook in _hooks:
hook(tags, exact, count)
def add_expire_hook(func):
""" Add a hook that will be called when an item is expired from
the cache. The callable passed in must take three options: the
first will be the tag set that was expired; the second will be the
state of the ``exact`` flag (True or False); and the third will be
the number of items that were expired from the cache. """
_hooks.append(func)
|