mcpu: Verify node allocation lock mode
[ganeti-local] / test / ganeti.mcpu_unittest.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2009, 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 """Script for unittesting the mcpu module"""
23
24
25 import unittest
26 import itertools
27
28 from ganeti import mcpu
29 from ganeti import opcodes
30 from ganeti import cmdlib
31 from ganeti import locking
32 from ganeti import constants
33 from ganeti.constants import \
34     LOCK_ATTEMPTS_TIMEOUT, \
35     LOCK_ATTEMPTS_MAXWAIT, \
36     LOCK_ATTEMPTS_MINWAIT
37
38 import testutils
39
40
41 REQ_BGL_WHITELIST = frozenset([
42   opcodes.OpClusterActivateMasterIp,
43   opcodes.OpClusterDeactivateMasterIp,
44   opcodes.OpClusterDestroy,
45   opcodes.OpClusterPostInit,
46   opcodes.OpClusterRename,
47   opcodes.OpInstanceRename,
48   opcodes.OpNodeAdd,
49   opcodes.OpNodeRemove,
50   opcodes.OpTestAllocator,
51   ])
52
53
54 class TestLockAttemptTimeoutStrategy(unittest.TestCase):
55   def testConstants(self):
56     tpa = mcpu.LockAttemptTimeoutStrategy._TIMEOUT_PER_ATTEMPT
57     self.assert_(len(tpa) > LOCK_ATTEMPTS_TIMEOUT / LOCK_ATTEMPTS_MAXWAIT)
58     self.assert_(sum(tpa) >= LOCK_ATTEMPTS_TIMEOUT)
59
60     self.assertTrue(LOCK_ATTEMPTS_TIMEOUT >= 1800,
61                     msg="Waiting less than half an hour per priority")
62     self.assertTrue(LOCK_ATTEMPTS_TIMEOUT <= 3600,
63                     msg="Waiting more than an hour per priority")
64
65   def testSimple(self):
66     strat = mcpu.LockAttemptTimeoutStrategy(_random_fn=lambda: 0.5,
67                                             _time_fn=lambda: 0.0)
68
69     prev = None
70     for i in range(len(strat._TIMEOUT_PER_ATTEMPT)):
71       timeout = strat.NextAttempt()
72       self.assert_(timeout is not None)
73
74       self.assert_(timeout <= LOCK_ATTEMPTS_MAXWAIT)
75       self.assert_(timeout >= LOCK_ATTEMPTS_MINWAIT)
76       self.assert_(prev is None or timeout >= prev)
77
78       prev = timeout
79
80     for _ in range(10):
81       self.assert_(strat.NextAttempt() is None)
82
83
84 class TestDispatchTable(unittest.TestCase):
85   def test(self):
86     for opcls in opcodes.OP_MAPPING.values():
87       if not opcls.WITH_LU:
88         continue
89       self.assertTrue(opcls in mcpu.Processor.DISPATCH_TABLE,
90                       msg="%s missing handler class" % opcls)
91
92       # Check against BGL whitelist
93       lucls = mcpu.Processor.DISPATCH_TABLE[opcls]
94       if lucls.REQ_BGL:
95         self.assertTrue(opcls in REQ_BGL_WHITELIST,
96                         msg=("%s not whitelisted for BGL" % opcls.OP_ID))
97       else:
98         self.assertFalse(opcls in REQ_BGL_WHITELIST,
99                          msg=("%s whitelisted for BGL, but doesn't use it" %
100                               opcls.OP_ID))
101
102
103 class TestProcessResult(unittest.TestCase):
104   def setUp(self):
105     self._submitted = []
106     self._count = itertools.count(200)
107
108   def _Submit(self, jobs):
109     job_ids = [self._count.next() for _ in jobs]
110     self._submitted.extend(zip(job_ids, jobs))
111     return job_ids
112
113   def testNoJobs(self):
114     for i in [object(), [], False, True, None, 1, 929, {}]:
115       self.assertEqual(mcpu._ProcessResult(NotImplemented, NotImplemented, i),
116                        i)
117
118   def testDefaults(self):
119     src = opcodes.OpTestDummy()
120
121     res = mcpu._ProcessResult(self._Submit, src, cmdlib.ResultWithJobs([[
122       opcodes.OpTestDelay(),
123       opcodes.OpTestDelay(),
124       ], [
125       opcodes.OpTestDelay(),
126       ]]))
127
128     self.assertEqual(res, {
129       constants.JOB_IDS_KEY: [200, 201],
130       })
131
132     (_, (op1, op2)) = self._submitted.pop(0)
133     (_, (op3, )) = self._submitted.pop(0)
134     self.assertRaises(IndexError, self._submitted.pop)
135
136     for op in [op1, op2, op3]:
137       self.assertTrue("OP_TEST_DUMMY" in op.comment)
138       self.assertFalse(hasattr(op, "priority"))
139       self.assertFalse(hasattr(op, "debug_level"))
140
141   def testParams(self):
142     src = opcodes.OpTestDummy(priority=constants.OP_PRIO_HIGH,
143                               debug_level=3)
144
145     res = mcpu._ProcessResult(self._Submit, src, cmdlib.ResultWithJobs([[
146       opcodes.OpTestDelay(priority=constants.OP_PRIO_LOW),
147       ], [
148       opcodes.OpTestDelay(comment="foobar", debug_level=10),
149       ]], other=True, value=range(10)))
150
151     self.assertEqual(res, {
152       constants.JOB_IDS_KEY: [200, 201],
153       "other": True,
154       "value": range(10),
155       })
156
157     (_, (op1, )) = self._submitted.pop(0)
158     (_, (op2, )) = self._submitted.pop(0)
159     self.assertRaises(IndexError, self._submitted.pop)
160
161     self.assertEqual(op1.priority, constants.OP_PRIO_LOW)
162     self.assertTrue("OP_TEST_DUMMY" in op1.comment)
163     self.assertEqual(op1.debug_level, 3)
164
165     self.assertEqual(op2.priority, constants.OP_PRIO_HIGH)
166     self.assertEqual(op2.comment, "foobar")
167     self.assertEqual(op2.debug_level, 3)
168
169
170 class _FakeLuWithLocks:
171   def __init__(self, needed_locks, share_locks):
172     self.needed_locks = needed_locks
173     self.share_locks = share_locks
174
175
176 class _FakeGlm:
177   def __init__(self, owning_nal):
178     self._owning_nal = owning_nal
179
180   def check_owned(self, level, names):
181     assert level == locking.LEVEL_NODE_ALLOC
182     assert names == locking.NAL
183     return self._owning_nal
184
185   def owning_all(self, level):
186     return False
187
188
189 class TestVerifyLocks(unittest.TestCase):
190   def testNoLocks(self):
191     lu = _FakeLuWithLocks({}, {})
192     glm = _FakeGlm(False)
193     mcpu._VerifyLocks(lu, glm,
194                       _mode_whitelist=NotImplemented,
195                       _nal_whitelist=NotImplemented)
196
197   def testNotAllSameMode(self):
198     for level in [locking.LEVEL_NODE, locking.LEVEL_NODE_RES]:
199       lu = _FakeLuWithLocks({
200         level: ["foo"],
201         }, {
202         level: 0,
203         locking.LEVEL_NODE_ALLOC: 0,
204         })
205       glm = _FakeGlm(False)
206       mcpu._VerifyLocks(lu, glm, _mode_whitelist=[], _nal_whitelist=[])
207
208   def testDifferentMode(self):
209     for level in [locking.LEVEL_NODE, locking.LEVEL_NODE_RES]:
210       lu = _FakeLuWithLocks({
211         level: ["foo"],
212         }, {
213         level: 0,
214         locking.LEVEL_NODE_ALLOC: 1,
215         })
216       glm = _FakeGlm(False)
217       try:
218         mcpu._VerifyLocks(lu, glm, _mode_whitelist=[], _nal_whitelist=[])
219       except AssertionError, err:
220         self.assertTrue("using the same mode as nodes" in str(err))
221       else:
222         self.fail("Exception not raised")
223
224       # Once more with the whitelist
225       mcpu._VerifyLocks(lu, glm, _mode_whitelist=[_FakeLuWithLocks],
226                         _nal_whitelist=[])
227
228   def testSameMode(self):
229     for level in [locking.LEVEL_NODE, locking.LEVEL_NODE_RES]:
230       lu = _FakeLuWithLocks({
231         level: ["foo"],
232         locking.LEVEL_NODE_ALLOC: locking.ALL_SET,
233         }, {
234         level: 1,
235         locking.LEVEL_NODE_ALLOC: 1,
236         })
237       glm = _FakeGlm(True)
238
239       try:
240         mcpu._VerifyLocks(lu, glm, _mode_whitelist=[_FakeLuWithLocks],
241                           _nal_whitelist=[])
242       except AssertionError, err:
243         self.assertTrue("whitelisted to use different modes" in str(err))
244       else:
245         self.fail("Exception not raised")
246
247       # Once more without the whitelist
248       mcpu._VerifyLocks(lu, glm, _mode_whitelist=[], _nal_whitelist=[])
249
250   def testAllWithoutAllocLock(self):
251     for level in [locking.LEVEL_NODE, locking.LEVEL_NODE_RES]:
252       lu = _FakeLuWithLocks({
253         level: locking.ALL_SET,
254         }, {
255         level: 0,
256         locking.LEVEL_NODE_ALLOC: 0,
257         })
258       glm = _FakeGlm(False)
259       try:
260         mcpu._VerifyLocks(lu, glm, _mode_whitelist=[], _nal_whitelist=[])
261       except AssertionError, err:
262         self.assertTrue("allocation lock must be used if" in str(err))
263       else:
264         self.fail("Exception not raised")
265
266       # Once more with the whitelist
267       mcpu._VerifyLocks(lu, glm, _mode_whitelist=[],
268                         _nal_whitelist=[_FakeLuWithLocks])
269
270   def testAllWithAllocLock(self):
271     for level in [locking.LEVEL_NODE, locking.LEVEL_NODE_RES]:
272       lu = _FakeLuWithLocks({
273         level: locking.ALL_SET,
274         locking.LEVEL_NODE_ALLOC: locking.ALL_SET,
275         }, {
276         level: 0,
277         locking.LEVEL_NODE_ALLOC: 0,
278         })
279       glm = _FakeGlm(True)
280
281       try:
282         mcpu._VerifyLocks(lu, glm, _mode_whitelist=[],
283                           _nal_whitelist=[_FakeLuWithLocks])
284       except AssertionError, err:
285         self.assertTrue("whitelisted for not acquiring" in str(err))
286       else:
287         self.fail("Exception not raised")
288
289       # Once more without the whitelist
290       mcpu._VerifyLocks(lu, glm, _mode_whitelist=[], _nal_whitelist=[])
291
292
293 if __name__ == "__main__":
294   testutils.GanetiTestProgram()