Ticket #3957 (partial) - ganeti_webmgr uses ganeti python namespace:
[ganeti_webmgr.git] / ganeti_web / tests / cached_cluster_object.py
blobfe427b66cf8f9c5bf6c1e8c2a1703bdbacb085a5
1 # Copyright (C) 2010 Oregon State University et al.
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
16 # USA.
18 import cPickle
19 from datetime import datetime
20 import time
22 from django.conf import settings
23 from django.test import TestCase
25 from util import client
26 from ganeti_web.tests.rapi_proxy import RapiProxy
27 from ganeti_web.tests.call_proxy import CallProxy
28 from ganeti_web import models
30 Cluster = models.Cluster
31 CachedClusterObject = models.CachedClusterObject
32 TestModel = models.TestModel
34 __all__ = ('TestCachedClusterObject',)
37 class CachedClusterObjectBase(TestCase):
38 """
39 Base class for building testcases for CachedClusterObjects. By extending
40 this class and setting Model equal to the class to be tested this TestCase
41 adds a series of tests to verify the caching mechanisms are working as
42 intended for that model.
43 """
45 __LAZY_CACHE_REFRESH = None
47 def setUp(self):
48 self.__GanetiRapiClient = models.client.GanetiRapiClient
49 models.client.GanetiRapiClient = RapiProxy
50 self.__LAZY_CACHE_REFRESH = settings.LAZY_CACHE_REFRESH
51 settings.LAZY_CACHE_REFRESH = 50
53 def tearDown(self):
54 TestModel.objects.all().delete()
55 Cluster.objects.all().delete()
57 models.client.GanetiRapiClient = self.__GanetiRapiClient
59 if self.__LAZY_CACHE_REFRESH:
60 settings.LAZY_CACHE_REFRESH = self.__LAZY_CACHE_REFRESH
62 def create_model(self, **kwargs):
63 """
64 create an instance of the model being tested, this will instrument
65 some methods of the model to check if they have been called
66 """
67 cluster, chaff = Cluster.objects.get_or_create(hostname='test.foo.org')
68 obj = self.Model(cluster=cluster, **kwargs)
70 # patch model class
71 CallProxy.patch(obj, 'parse_transient_info')
72 CallProxy.patch(obj, 'parse_persistent_info')
73 CallProxy.patch(obj, '_refresh')
74 CallProxy.patch(obj, 'load_info')
75 CallProxy.patch(obj, 'save')
76 return obj
78 def test_trivial(self):
79 """
80 trivial test to instantiate class
81 """
82 self.create_model()
84 def test_cached_object_init(self):
85 """
86 Trivial test to init model
88 Verifies:
89 * info is not loaded for new instance
90 * info is loaded either by refresh or cached info for existing
91 model
92 """
93 object = self.create_model()
94 object.load_info.assertNotCalled(self)
96 # XXX simulate loading existing instance by calling __init__ again and
97 # passing a value for id
98 object = self.create_model(id=1)
99 object.__init__(1, cluster=object.cluster)
100 object.load_info.assertCalled(self)
102 def test_timestamp_precision(self):
104 Tests that timestamps can be stored with microsecond precision using
105 PreciseDateTimeField.
107 This may be database specific:
108 * mysql - supported
109 * sqlite - only 5 digits of precision
110 * postgresql -
112 obj = self.create_model()
113 timestamp = 1285883000.123456
114 dt = datetime.fromtimestamp(timestamp)
116 obj.mtime = dt
117 obj.cached = dt
118 obj.save()
120 # XXX query values only. otherwise they may be updated
121 values = TestModel.objects.filter(pk=obj.id).values('mtime','cached')[0]
123 self.assertEqual(timestamp, float(values['mtime']))
124 self.assertEqual(timestamp, float(values['cached']))
126 def test_info(self):
128 Tests retrieving and setting info
130 Verifies:
131 * If serialized is available it will be deserialized and returned
132 * If serialized info is not available, None will be returned
133 * Setting info, clears serialized info. (delayed till save)
134 * Setting info triggers info to be parsed
136 object = self.create_model()
137 data = TestModel.data
138 serialized_info = cPickle.dumps(data)
140 # no serialized data, check twice for caching mechanism
141 self.assertEqual(object.info, None)
142 self.assertEqual(object.info, None)
144 # set info
145 object.info = data
146 self.assertEqual(None, object.serialized_info)
147 object.parse_transient_info.assertCalled(self)
148 object.parse_persistent_info.assertCalled(self)
150 # save causes serialization
151 object.save()
152 self.assertEqual(serialized_info, object.serialized_info)
154 # serialized data, check twice for caching mechanism
155 object.serialized_info = serialized_info
156 self.assertEqual(object.info, data)
157 self.assertEqual(object.info, data)
159 def test_parse_info(self):
161 Test parsing info
163 Verifies:
164 * transient info is parsed
165 * persistent info is parsed
167 obj = self.create_model(id=1)
168 obj.parse_info()
169 obj.parse_transient_info.assertCalled(self)
170 obj.parse_persistent_info.assertCalled(self)
172 self.assertEqual(obj.ctime, datetime.fromtimestamp(1285799513.4741000))
173 self.assertEqual(obj.mtime, datetime.fromtimestamp(1285883187.8692000))
175 def test_refresh(self, object=None):
177 Test forced refresh of cached data
179 Verifies:
180 * Object specific refresh is called
181 * Info is parsed
182 * Object is saved
183 * Cache time is updated
185 object = object if object else self.create_model()
186 now = datetime.now()
187 object.refresh()
189 object._refresh.assertCalled(self)
190 object.parse_transient_info.assertCalled(self)
191 object.parse_persistent_info.assertCalled(self)
192 self.assertEqual(1, len(object.parse_persistent_info.calls))
193 self.assert_(object.id)
194 self.assertNotEqual(None, object.cached)
195 self.assert_(now < object.cached, "Cache time should be newer")
197 def test_refresh_error(self):
199 Test an error during refresh
201 Verifies:
202 * error will be saved in object.error
203 * successful refresh after will clear error
205 object = self.create_model()
206 msg = "SIMULATING AN ERROR"
208 # force an error to test its capture
209 object.throw_error = client.GanetiApiError(msg)
210 object.save()
211 object.refresh()
212 self.assertEqual(msg, object.error)
214 # clear the error and retry
215 object.throw_error = None
216 object.save()
217 self.test_refresh(object)
218 self.assertEqual(None, object.error)
220 def test_lazy_cache(self):
222 Test that the lazy caching mechanism works
224 Verifies:
225 * If object.cached is None, refresh
226 * If cache has timed out, refresh
227 * otherwise parse cached transient info only
229 object = self.create_model()
230 object.save()
232 # no cache time
233 object.load_info()
234 object._refresh.assertCalled(self)
235 object._refresh.reset()
237 # cached, but not expired
238 object.load_info()
239 object._refresh.assertNotCalled(self)
240 object.parse_transient_info.assertCalled(self)
241 object.parse_transient_info.reset()
243 # sleep to let cache expire
244 time.sleep(.1)
245 object.load_info()
246 object._refresh.assertCalled(self)
247 object._refresh.reset()
249 def test_no_change_quick_update(self):
251 Tests that if no change has been made (signified by mtime), then an
252 update query is used instead of Model.save()
254 Verifies:
255 * null Model.mtime causes save()
256 * newer info.mtime causes save()
257 * equal mtime causes update
259 object = self.create_model()
260 object.save()
261 object.save.reset()
263 # mtime is None (object was never refreshed or parsed)
264 object.refresh()
265 object.save.assertCalled(self)
266 object.save.reset()
268 # mtime still the same as static data, no save
269 object.refresh()
270 object.save.assertNotCalled(self)
272 # info.mtime newer, Model.save() called
273 # XXX make a copy of the data to ensure this test does not conflict
274 # with any other tests.
275 data = object.data.copy()
276 data['mtime'] = object.data['mtime'] + 100
277 object.data = data
278 object.refresh()
279 object.save.assertCalled(self)
281 def test_cache_disabled(self):
283 Tests that CachedClusterObjectBase.ignore_cache causes the cache to be
284 ignored
286 Verifies:
287 * rapi call made even if mtime has not passed
288 * object is still updated if mtime is new
290 object = self.create_model()
291 object.save()
293 # no cache time
294 object.load_info()
295 object._refresh.assertCalled(self)
296 object._refresh.reset()
298 # cache enabled
299 object.load_info()
300 object._refresh.assertNotCalled(self)
302 # enable ignore cache, refresh should be called each time
303 object.ignore_cache=True
304 object.load_info()
305 object._refresh.assertCalled(self)
306 object._refresh.reset()
307 object.load_info()
308 object._refresh.assertCalled(self)
309 object._refresh.reset()
311 # cache re-enabled, cache should be used instead
312 object.ignore_cache=False
313 object.load_info()
314 object._refresh.assertNotCalled(self)
317 class TestCachedClusterObject(CachedClusterObjectBase):
318 Model = TestModel