verify-disks: Explicitely state nothing has to be done
[ganeti-local] / lib / cache.py
1 #
2 #
3
4 # Copyright (C) 2011 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21
22 """This module implements caching."""
23
24
25 import time
26
27 from ganeti import locking
28 from ganeti import serializer
29
30
31 TIMESTAMP = "timestamp"
32 TTL = "ttl"
33 VALUE = "value"
34
35
36 class CacheBase:
37   """This is the base class for all caches.
38
39   """
40   def __init__(self):
41     """Base init method.
42
43     """
44
45   def Store(self, key, value, ttl=0):
46     """Stores key with value in the cache.
47
48     @param key: The key to associate this cached value
49     @param value: The value to cache
50     @param ttl: TTL in seconds after when this entry is considered outdated
51     @returns: L{True} on success, L{False} on failure
52
53     """
54     raise NotImplementedError
55
56   def GetMulti(self, keys):
57     """Retrieve multiple values from the cache.
58
59     @param keys: The keys to retrieve
60     @returns: The list of values
61
62     """
63     raise NotImplementedError
64
65   def Get(self, key):
66     """Retrieve the value from the cache.
67
68     @param key: The key to retrieve
69     @returns: The value or L{None} if not found
70
71     """
72     raise NotImplementedError
73
74   def Invalidate(self, keys):
75     """Invalidate given keys.
76
77     @param keys: The list of keys to invalidate
78     @returns: L{True} on success, L{False} otherwise
79
80     """
81     raise NotImplementedError
82
83   def Flush(self):
84     """Invalidates all of the keys and flushes the cache.
85
86     """
87     raise NotImplementedError
88
89   def ResetState(self):
90     """Used to reset the state of the cache.
91
92     This can be used to reinstantiate connection or any other state refresh
93
94     """
95
96   def Cleanup(self):
97     """Cleanup the cache from expired entries.
98
99     """
100
101
102 class SimpleCache(CacheBase):
103   """Implements a very simple, dict base cache.
104
105   """
106   CLEANUP_ROUND = 1800
107   _LOCK = "lock"
108
109   def __init__(self, _time_fn=time.time):
110     """Initialize this class.
111
112     @param _time_fn: Function used to return time (unittest only)
113
114     """
115     CacheBase.__init__(self)
116
117     self._time_fn = _time_fn
118
119     self.cache = {}
120     self.lock = locking.SharedLock("SimpleCache")
121     self.last_cleanup = self._time_fn()
122
123   def _UnlockedCleanup(self):
124     """Does cleanup of the cache.
125
126     """
127     check_time = self._time_fn()
128     if (self.last_cleanup + self.CLEANUP_ROUND) <= check_time:
129       keys = []
130       for key, value in self.cache.items():
131         if not value[TTL]:
132           continue
133
134         expired = value[TIMESTAMP] + value[TTL]
135         if expired < check_time:
136           keys.append(key)
137       self._UnlockedInvalidate(keys)
138       self.last_cleanup = check_time
139
140   @locking.ssynchronized(_LOCK)
141   def Cleanup(self):
142     """Cleanup our cache.
143
144     """
145     self._UnlockedCleanup()
146
147   @locking.ssynchronized(_LOCK)
148   def Store(self, key, value, ttl=0):
149     """Stores a value at key in the cache.
150
151     See L{CacheBase.Store} for parameter description
152
153     """
154     assert ttl >= 0
155     self._UnlockedCleanup()
156     val = serializer.Dump(value)
157     cache_val = {
158       TIMESTAMP: self._time_fn(),
159       TTL: ttl,
160       VALUE: val
161       }
162     self.cache[key] = cache_val
163     return True
164
165   @locking.ssynchronized(_LOCK, shared=1)
166   def GetMulti(self, keys):
167     """Retrieve the values of keys from cache.
168
169     See L{CacheBase.GetMulti} for parameter description
170
171     """
172     return [self._ExtractValue(key) for key in keys]
173
174   @locking.ssynchronized(_LOCK, shared=1)
175   def Get(self, key):
176     """Retrieve the value of key from cache.
177
178     See L{CacheBase.Get} for parameter description
179
180     """
181     return self._ExtractValue(key)
182
183   @locking.ssynchronized(_LOCK)
184   def Invalidate(self, keys):
185     """Invalidates value for keys in cache.
186
187     See L{CacheBase.Invalidate} for parameter description
188
189     """
190     return self._UnlockedInvalidate(keys)
191
192   @locking.ssynchronized(_LOCK)
193   def Flush(self):
194     """Invalidates all keys and values in cache.
195
196     See L{CacheBase.Flush} for parameter description
197
198     """
199     self.cache.clear()
200     self.last_cleanup = self._time_fn()
201
202   def _UnlockedInvalidate(self, keys):
203     """Invalidate keys in cache.
204
205     This is the unlocked version, see L{Invalidate} for parameter description
206
207     """
208     for key in keys:
209       self.cache.pop(key, None)
210
211     return True
212
213   def _ExtractValue(self, key):
214     """Extracts just the value for a key.
215
216     This method is taking care if the value did not expire ans returns it
217
218     @param key: The key to look for
219     @returns: The value if key is not expired, L{None} otherwise
220
221     """
222     try:
223       cache_val = self.cache[key]
224     except KeyError:
225       return None
226     else:
227       if cache_val[TTL] == 0:
228         return serializer.Load(cache_val[VALUE])
229       else:
230         expired = cache_val[TIMESTAMP] + cache_val[TTL]
231
232         if self._time_fn() <= expired:
233           return serializer.Load(cache_val[VALUE])
234         else:
235           return None