locking: Change locking order, move NAL after instances
[ganeti-local] / test / ganeti.opcodes_unittest.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2010, 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 testing ganeti.backend"""
23
24 import os
25 import sys
26 import unittest
27
28 from ganeti import utils
29 from ganeti import opcodes
30 from ganeti import ht
31 from ganeti import constants
32 from ganeti import errors
33 from ganeti import compat
34
35 import testutils
36
37
38 #: Unless an opcode is included in the following list it must have a result
39 #: check of some sort
40 MISSING_RESULT_CHECK = frozenset([
41   opcodes.OpTestAllocator,
42   opcodes.OpTestDelay,
43   opcodes.OpTestDummy,
44   opcodes.OpTestJqueue,
45   ])
46
47
48 class TestOpcodes(unittest.TestCase):
49   def test(self):
50     self.assertRaises(ValueError, opcodes.OpCode.LoadOpCode, None)
51     self.assertRaises(ValueError, opcodes.OpCode.LoadOpCode, "")
52     self.assertRaises(ValueError, opcodes.OpCode.LoadOpCode, {})
53     self.assertRaises(ValueError, opcodes.OpCode.LoadOpCode, {"OP_ID": ""})
54
55     for cls in opcodes.OP_MAPPING.values():
56       self.assert_(cls.OP_ID.startswith("OP_"))
57       self.assert_(len(cls.OP_ID) > 3)
58       self.assertEqual(cls.OP_ID, cls.OP_ID.upper())
59       self.assertEqual(cls.OP_ID, opcodes._NameToId(cls.__name__))
60       self.assertFalse(compat.any(cls.OP_ID.startswith(prefix)
61                                   for prefix in opcodes._SUMMARY_PREFIX.keys()))
62       if cls in MISSING_RESULT_CHECK:
63         self.assertTrue(cls.OP_RESULT is None,
64                         msg=("%s is listed to not have a result check" %
65                              cls.OP_ID))
66       else:
67         self.assertTrue(callable(cls.OP_RESULT),
68                         msg=("%s should have a result check" % cls.OP_ID))
69
70       self.assertRaises(TypeError, cls, unsupported_parameter="some value")
71
72       args = [
73         # No variables
74         {},
75
76         # Variables supported by all opcodes
77         {"dry_run": False, "debug_level": 0, },
78
79         # All variables
80         dict([(name, []) for name in cls.GetAllSlots()])
81         ]
82
83       for i in args:
84         op = cls(**i)
85
86         self.assertEqual(op.OP_ID, cls.OP_ID)
87         self._checkSummary(op)
88
89         # Try a restore
90         state = op.__getstate__()
91         self.assert_(isinstance(state, dict))
92
93         restored = opcodes.OpCode.LoadOpCode(state)
94         self.assert_(isinstance(restored, cls))
95         self._checkSummary(restored)
96
97         for name in ["x_y_z", "hello_world"]:
98           assert name not in cls.GetAllSlots()
99           for value in [None, True, False, [], "Hello World"]:
100             self.assertRaises(AttributeError, setattr, op, name, value)
101
102   def _checkSummary(self, op):
103     summary = op.Summary()
104
105     if hasattr(op, "OP_DSC_FIELD"):
106       self.assert_(("OP_%s" % summary).startswith("%s(" % op.OP_ID))
107       self.assert_(summary.endswith(")"))
108     else:
109       self.assertEqual("OP_%s" % summary, op.OP_ID)
110
111   def testSummary(self):
112     class OpTest(opcodes.OpCode):
113       OP_DSC_FIELD = "data"
114       OP_PARAMS = [
115         ("data", ht.NoDefault, ht.TString, None),
116         ]
117
118     self.assertEqual(OpTest(data="").Summary(), "TEST()")
119     self.assertEqual(OpTest(data="Hello World").Summary(),
120                      "TEST(Hello World)")
121     self.assertEqual(OpTest(data="node1.example.com").Summary(),
122                      "TEST(node1.example.com)")
123
124   def testTinySummary(self):
125     self.assertFalse(utils.FindDuplicates(opcodes._SUMMARY_PREFIX.values()))
126     self.assertTrue(compat.all(prefix.endswith("_") and supplement.endswith("_")
127                                for (prefix, supplement) in
128                                  opcodes._SUMMARY_PREFIX.items()))
129
130     self.assertEqual(opcodes.OpClusterPostInit().TinySummary(), "C_POST_INIT")
131     self.assertEqual(opcodes.OpNodeRemove().TinySummary(), "N_REMOVE")
132     self.assertEqual(opcodes.OpInstanceMigrate().TinySummary(), "I_MIGRATE")
133     self.assertEqual(opcodes.OpGroupQuery().TinySummary(), "G_QUERY")
134     self.assertEqual(opcodes.OpTestJqueue().TinySummary(), "TEST_JQUEUE")
135
136   def testListSummary(self):
137     class OpTest(opcodes.OpCode):
138       OP_DSC_FIELD = "data"
139       OP_PARAMS = [
140         ("data", ht.NoDefault, ht.TList, None),
141         ]
142
143     self.assertEqual(OpTest(data=["a", "b", "c"]).Summary(),
144                      "TEST(a,b,c)")
145     self.assertEqual(OpTest(data=["a", None, "c"]).Summary(),
146                      "TEST(a,None,c)")
147     self.assertEqual(OpTest(data=[1, 2, 3, 4]).Summary(), "TEST(1,2,3,4)")
148
149   def testOpId(self):
150     self.assertFalse(utils.FindDuplicates(cls.OP_ID
151                                           for cls in opcodes._GetOpList()))
152     self.assertEqual(len(opcodes._GetOpList()), len(opcodes.OP_MAPPING))
153
154   def testParams(self):
155     supported_by_all = set(["debug_level", "dry_run", "priority"])
156
157     self.assertTrue(opcodes.BaseOpCode not in opcodes.OP_MAPPING.values())
158     self.assertTrue(opcodes.OpCode not in opcodes.OP_MAPPING.values())
159
160     for cls in opcodes.OP_MAPPING.values() + [opcodes.OpCode]:
161       all_slots = cls.GetAllSlots()
162
163       self.assertEqual(len(set(all_slots) & supported_by_all), 3,
164                        msg=("Opcode %s doesn't support all base"
165                             " parameters (%r)" % (cls.OP_ID, supported_by_all)))
166
167       # All opcodes must have OP_PARAMS
168       self.assert_(hasattr(cls, "OP_PARAMS"),
169                    msg="%s doesn't have OP_PARAMS" % cls.OP_ID)
170
171       param_names = [name for (name, _, _, _) in cls.GetAllParams()]
172
173       self.assertEqual(all_slots, param_names)
174
175       # Without inheritance
176       self.assertEqual(cls.__slots__,
177                        [name for (name, _, _, _) in cls.OP_PARAMS])
178
179       # This won't work if parameters are converted to a dictionary
180       duplicates = utils.FindDuplicates(param_names)
181       self.assertFalse(duplicates,
182                        msg=("Found duplicate parameters %r in %s" %
183                             (duplicates, cls.OP_ID)))
184
185       # Check parameter definitions
186       for attr_name, aval, test, doc in cls.GetAllParams():
187         self.assert_(attr_name)
188         self.assert_(test is None or test is ht.NoType or callable(test),
189                      msg=("Invalid type check for %s.%s" %
190                           (cls.OP_ID, attr_name)))
191         self.assertTrue(doc is None or isinstance(doc, basestring))
192
193         if callable(aval):
194           default_value = aval()
195           self.assertFalse(callable(default_value),
196                            msg="Default value returned by function is callable")
197         else:
198           default_value = aval
199
200         if aval is not ht.NoDefault and test is not ht.NoType:
201           self.assertTrue(test(default_value),
202                           msg=("Default value of '%s.%s' does not verify" %
203                                (cls.OP_ID, attr_name)))
204
205       # If any parameter has documentation, all others need to have it as well
206       has_doc = [doc is not None for (_, _, _, doc) in cls.OP_PARAMS]
207       self.assertTrue(not compat.any(has_doc) or compat.all(has_doc),
208                       msg="%s does not document all parameters" % cls)
209
210   def testValidateNoModification(self):
211     class OpTest(opcodes.OpCode):
212       OP_PARAMS = [
213         ("nodef", ht.NoDefault, ht.TMaybeString, None),
214         ("wdef", "default", ht.TMaybeString, None),
215         ("number", 0, ht.TInt, None),
216         ("notype", None, ht.NoType, None),
217         ]
218
219     # Missing required parameter "nodef"
220     op = OpTest()
221     before = op.__getstate__()
222     self.assertRaises(errors.OpPrereqError, op.Validate, False)
223     self.assertFalse(hasattr(op, "nodef"))
224     self.assertFalse(hasattr(op, "wdef"))
225     self.assertFalse(hasattr(op, "number"))
226     self.assertFalse(hasattr(op, "notype"))
227     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
228
229     # Required parameter "nodef" is provided
230     op = OpTest(nodef="foo")
231     before = op.__getstate__()
232     op.Validate(False)
233     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
234     self.assertEqual(op.nodef, "foo")
235     self.assertFalse(hasattr(op, "wdef"))
236     self.assertFalse(hasattr(op, "number"))
237     self.assertFalse(hasattr(op, "notype"))
238
239     # Missing required parameter "nodef"
240     op = OpTest(wdef="hello", number=999)
241     before = op.__getstate__()
242     self.assertRaises(errors.OpPrereqError, op.Validate, False)
243     self.assertFalse(hasattr(op, "nodef"))
244     self.assertFalse(hasattr(op, "notype"))
245     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
246
247     # Wrong type for "nodef"
248     op = OpTest(nodef=987)
249     before = op.__getstate__()
250     self.assertRaises(errors.OpPrereqError, op.Validate, False)
251     self.assertEqual(op.nodef, 987)
252     self.assertFalse(hasattr(op, "notype"))
253     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
254
255     # Testing different types for "notype"
256     op = OpTest(nodef="foo", notype=[1, 2, 3])
257     before = op.__getstate__()
258     op.Validate(False)
259     self.assertEqual(op.nodef, "foo")
260     self.assertEqual(op.notype, [1, 2, 3])
261     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
262
263     op = OpTest(nodef="foo", notype="Hello World")
264     before = op.__getstate__()
265     op.Validate(False)
266     self.assertEqual(op.nodef, "foo")
267     self.assertEqual(op.notype, "Hello World")
268     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
269
270   def testValidateSetDefaults(self):
271     class OpTest(opcodes.OpCode):
272       OP_PARAMS = [
273         # Static default value
274         ("value1", "default", ht.TMaybeString, None),
275
276         # Default value callback
277         ("value2", lambda: "result", ht.TMaybeString, None),
278         ]
279
280     op = OpTest()
281     before = op.__getstate__()
282     op.Validate(True)
283     self.assertNotEqual(op.__getstate__(), before,
284                         msg="Opcode was not modified")
285     self.assertEqual(op.value1, "default")
286     self.assertEqual(op.value2, "result")
287     self.assert_(op.dry_run is None)
288     self.assert_(op.debug_level is None)
289     self.assertEqual(op.priority, constants.OP_PRIO_DEFAULT)
290
291     op = OpTest(value1="hello", value2="world", debug_level=123)
292     before = op.__getstate__()
293     op.Validate(True)
294     self.assertNotEqual(op.__getstate__(), before,
295                         msg="Opcode was not modified")
296     self.assertEqual(op.value1, "hello")
297     self.assertEqual(op.value2, "world")
298     self.assertEqual(op.debug_level, 123)
299
300
301   def testOpInstanceMultiAlloc(self):
302     inst = dict([(name, []) for name in opcodes.OpInstanceCreate.GetAllSlots()])
303     inst_op = opcodes.OpInstanceCreate(**inst)
304     inst_state = inst_op.__getstate__()
305
306     multialloc = opcodes.OpInstanceMultiAlloc(instances=[inst_op])
307     state = multialloc.__getstate__()
308     self.assertEquals(state["instances"], [inst_state])
309     loaded_multialloc = opcodes.OpCode.LoadOpCode(state)
310     (loaded_inst,) = loaded_multialloc.instances
311     self.assertNotEquals(loaded_inst, inst_op)
312     self.assertEquals(loaded_inst.__getstate__(), inst_state)
313
314
315 class TestOpcodeDepends(unittest.TestCase):
316   def test(self):
317     check_relative = opcodes._BuildJobDepCheck(True)
318     check_norelative = opcodes.TNoRelativeJobDependencies
319
320     for fn in [check_relative, check_norelative]:
321       self.assertTrue(fn(None))
322       self.assertTrue(fn([]))
323       self.assertTrue(fn([(1, [])]))
324       self.assertTrue(fn([(719833, [])]))
325       self.assertTrue(fn([("24879", [])]))
326       self.assertTrue(fn([(2028, [constants.JOB_STATUS_ERROR])]))
327       self.assertTrue(fn([
328         (2028, [constants.JOB_STATUS_ERROR]),
329         (18750, []),
330         (5063, [constants.JOB_STATUS_SUCCESS, constants.JOB_STATUS_ERROR]),
331         ]))
332
333       self.assertFalse(fn(1))
334       self.assertFalse(fn([(9, )]))
335       self.assertFalse(fn([(15194, constants.JOB_STATUS_ERROR)]))
336
337     for i in [
338       [(-1, [])],
339       [(-27740, [constants.JOB_STATUS_CANCELED, constants.JOB_STATUS_ERROR]),
340        (-1, [constants.JOB_STATUS_ERROR]),
341        (9921, [])],
342       ]:
343       self.assertTrue(check_relative(i))
344       self.assertFalse(check_norelative(i))
345
346
347 class TestResultChecks(unittest.TestCase):
348   def testJobIdList(self):
349     for i in [[], [(False, "error")], [(False, "")],
350               [(True, 123), (True, "999")]]:
351       self.assertTrue(opcodes.TJobIdList(i))
352
353     for i in ["", [("x", 1)], [[], []], [[False, "", None], [True, 123]]]:
354       self.assertFalse(opcodes.TJobIdList(i))
355
356   def testJobIdListOnly(self):
357     self.assertTrue(opcodes.TJobIdListOnly({
358       constants.JOB_IDS_KEY: [],
359       }))
360     self.assertTrue(opcodes.TJobIdListOnly({
361       constants.JOB_IDS_KEY: [(True, "9282")],
362       }))
363
364     self.assertFalse(opcodes.TJobIdListOnly({
365       "x": None,
366       }))
367     self.assertFalse(opcodes.TJobIdListOnly({
368       constants.JOB_IDS_KEY: [],
369       "x": None,
370       }))
371     self.assertFalse(opcodes.TJobIdListOnly({
372       constants.JOB_IDS_KEY: [("foo", "bar")],
373       }))
374     self.assertFalse(opcodes.TJobIdListOnly({
375       constants.JOB_IDS_KEY: [("one", "two", "three")],
376       }))
377
378
379 class TestClusterOsList(unittest.TestCase):
380   def test(self):
381     good = [
382       None,
383       [],
384       [(constants.DDM_ADD, "dos"),
385        (constants.DDM_REMOVE, "linux")],
386       ]
387
388     for i in good:
389       self.assertTrue(opcodes._TestClusterOsList(i))
390
391     wrong = ["", 0, "xy", ["Hello World"], object(),
392       [("foo", "bar")],
393       [("", "")],
394       [[constants.DDM_ADD]],
395       [(constants.DDM_ADD, "")],
396       [(constants.DDM_REMOVE, "")],
397       [(constants.DDM_ADD, None)],
398       [(constants.DDM_REMOVE, None)],
399       ]
400
401     for i in wrong:
402       self.assertFalse(opcodes._TestClusterOsList(i))
403
404
405 class TestOpInstanceSetParams(unittest.TestCase):
406   def _GenericTests(self, fn):
407     self.assertTrue(fn([]))
408     self.assertTrue(fn([(constants.DDM_ADD, {})]))
409     self.assertTrue(fn([(constants.DDM_REMOVE, {})]))
410     for i in [0, 1, 2, 3, 9, 10, 1024]:
411       self.assertTrue(fn([(i, {})]))
412
413     self.assertFalse(fn(None))
414     self.assertFalse(fn({}))
415     self.assertFalse(fn(""))
416     self.assertFalse(fn(0))
417     self.assertFalse(fn([(-100, {})]))
418     self.assertFalse(fn([(constants.DDM_ADD, 2, 3)]))
419     self.assertFalse(fn([[constants.DDM_ADD]]))
420
421   def testNicModifications(self):
422     fn = opcodes.OpInstanceSetParams.TestNicModifications
423     self._GenericTests(fn)
424
425     for param in constants.INIC_PARAMS:
426       self.assertTrue(fn([[constants.DDM_ADD, {param: None}]]))
427       self.assertTrue(fn([[constants.DDM_ADD, {param: param}]]))
428
429   def testDiskModifications(self):
430     fn = opcodes.OpInstanceSetParams.TestDiskModifications
431     self._GenericTests(fn)
432
433     for param in constants.IDISK_PARAMS:
434       self.assertTrue(fn([[constants.DDM_ADD, {param: 0}]]))
435       self.assertTrue(fn([[constants.DDM_ADD, {param: param}]]))
436
437
438 if __name__ == "__main__":
439   testutils.GanetiTestProgram()