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,
19 from datetime
import datetime
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
):
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.
45 __LAZY_CACHE_REFRESH
= None
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
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
):
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
67 cluster
, chaff
= Cluster
.objects
.get_or_create(hostname
='test.foo.org')
68 obj
= self
.Model(cluster
=cluster
, **kwargs
)
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')
78 def test_trivial(self
):
80 trivial test to instantiate class
84 def test_cached_object_init(self
):
86 Trivial test to init model
89 * info is not loaded for new instance
90 * info is loaded either by refresh or cached info for existing
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:
109 * sqlite - only 5 digits of precision
112 obj
= self
.create_model()
113 timestamp
= 1285883000.123456
114 dt
= datetime
.fromtimestamp(timestamp
)
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']))
128 Tests retrieving and setting info
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)
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
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
):
164 * transient info is parsed
165 * persistent info is parsed
167 obj
= self
.create_model(id=1)
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
180 * Object specific refresh is called
183 * Cache time is updated
185 object = object if object else self
.create_model()
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
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
)
212 self
.assertEqual(msg
, object.error
)
214 # clear the error and retry
215 object.throw_error
= None
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
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()
234 object._refresh
.assertCalled(self
)
235 object._refresh
.reset()
237 # cached, but not expired
239 object._refresh
.assertNotCalled(self
)
240 object.parse_transient_info
.assertCalled(self
)
241 object.parse_transient_info
.reset()
243 # sleep to let cache expire
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()
255 * null Model.mtime causes save()
256 * newer info.mtime causes save()
257 * equal mtime causes update
259 object = self
.create_model()
263 # mtime is None (object was never refreshed or parsed)
265 object.save
.assertCalled(self
)
268 # mtime still the same as static data, no save
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
279 object.save
.assertCalled(self
)
281 def test_cache_disabled(self
):
283 Tests that CachedClusterObjectBase.ignore_cache causes the cache to be
287 * rapi call made even if mtime has not passed
288 * object is still updated if mtime is new
290 object = self
.create_model()
295 object._refresh
.assertCalled(self
)
296 object._refresh
.reset()
300 object._refresh
.assertNotCalled(self
)
302 # enable ignore cache, refresh should be called each time
303 object.ignore_cache
=True
305 object._refresh
.assertCalled(self
)
306 object._refresh
.reset()
308 object._refresh
.assertCalled(self
)
309 object._refresh
.reset()
311 # cache re-enabled, cache should be used instead
312 object.ignore_cache
=False
314 object._refresh
.assertNotCalled(self
)
317 class TestCachedClusterObject(CachedClusterObjectBase
):