Replace frozenset with compat.UniqueFrozenset
[ganeti-local] / test / ganeti.opcodes_unittest.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2010, 2011, 2012 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 = compat.UniqueFrozenset([
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 testSummaryFormatter(self):
125     class OpTest(opcodes.OpCode):
126       OP_DSC_FIELD = "data"
127       OP_DSC_FORMATTER = lambda _, v: "a"
128       OP_PARAMS = [
129         ("data", ht.NoDefault, ht.TString, None),
130         ]
131     self.assertEqual(OpTest(data="").Summary(), "TEST(a)")
132     self.assertEqual(OpTest(data="b").Summary(), "TEST(a)")
133
134   def testTinySummary(self):
135     self.assertFalse(utils.FindDuplicates(opcodes._SUMMARY_PREFIX.values()))
136     self.assertTrue(compat.all(prefix.endswith("_") and supplement.endswith("_")
137                                for (prefix, supplement) in
138                                  opcodes._SUMMARY_PREFIX.items()))
139
140     self.assertEqual(opcodes.OpClusterPostInit().TinySummary(), "C_POST_INIT")
141     self.assertEqual(opcodes.OpNodeRemove().TinySummary(), "N_REMOVE")
142     self.assertEqual(opcodes.OpInstanceMigrate().TinySummary(), "I_MIGRATE")
143     self.assertEqual(opcodes.OpGroupQuery().TinySummary(), "G_QUERY")
144     self.assertEqual(opcodes.OpTestJqueue().TinySummary(), "TEST_JQUEUE")
145
146   def testListSummary(self):
147     class OpTest(opcodes.OpCode):
148       OP_DSC_FIELD = "data"
149       OP_PARAMS = [
150         ("data", ht.NoDefault, ht.TList, None),
151         ]
152
153     self.assertEqual(OpTest(data=["a", "b", "c"]).Summary(),
154                      "TEST(a,b,c)")
155     self.assertEqual(OpTest(data=["a", None, "c"]).Summary(),
156                      "TEST(a,None,c)")
157     self.assertEqual(OpTest(data=[1, 2, 3, 4]).Summary(), "TEST(1,2,3,4)")
158
159   def testOpId(self):
160     self.assertFalse(utils.FindDuplicates(cls.OP_ID
161                                           for cls in opcodes._GetOpList()))
162     self.assertEqual(len(opcodes._GetOpList()), len(opcodes.OP_MAPPING))
163
164   def testParams(self):
165     supported_by_all = set(["debug_level", "dry_run", "priority"])
166
167     self.assertTrue(opcodes.BaseOpCode not in opcodes.OP_MAPPING.values())
168     self.assertTrue(opcodes.OpCode not in opcodes.OP_MAPPING.values())
169
170     for cls in opcodes.OP_MAPPING.values() + [opcodes.OpCode]:
171       all_slots = cls.GetAllSlots()
172
173       self.assertEqual(len(set(all_slots) & supported_by_all), 3,
174                        msg=("Opcode %s doesn't support all base"
175                             " parameters (%r)" % (cls.OP_ID, supported_by_all)))
176
177       # All opcodes must have OP_PARAMS
178       self.assert_(hasattr(cls, "OP_PARAMS"),
179                    msg="%s doesn't have OP_PARAMS" % cls.OP_ID)
180
181       param_names = [name for (name, _, _, _) in cls.GetAllParams()]
182
183       self.assertEqual(all_slots, param_names)
184
185       # Without inheritance
186       self.assertEqual(cls.__slots__,
187                        [name for (name, _, _, _) in cls.OP_PARAMS])
188
189       # This won't work if parameters are converted to a dictionary
190       duplicates = utils.FindDuplicates(param_names)
191       self.assertFalse(duplicates,
192                        msg=("Found duplicate parameters %r in %s" %
193                             (duplicates, cls.OP_ID)))
194
195       # Check parameter definitions
196       for attr_name, aval, test, doc in cls.GetAllParams():
197         self.assert_(attr_name)
198         self.assert_(test is None or test is ht.NoType or callable(test),
199                      msg=("Invalid type check for %s.%s" %
200                           (cls.OP_ID, attr_name)))
201         self.assertTrue(doc is None or isinstance(doc, basestring))
202
203         if callable(aval):
204           default_value = aval()
205           self.assertFalse(callable(default_value),
206                            msg=("Default value of %s.%s returned by function"
207                                 " is callable" % (cls.OP_ID, attr_name)))
208         else:
209           self.assertFalse(isinstance(aval, (list, dict, set)),
210                            msg=("Default value of %s.%s is mutable (%s)" %
211                                 (cls.OP_ID, attr_name, repr(aval))))
212
213           default_value = aval
214
215         if aval is not ht.NoDefault and test is not ht.NoType:
216           self.assertTrue(test(default_value),
217                           msg=("Default value of %s.%s does not verify" %
218                                (cls.OP_ID, attr_name)))
219
220       # If any parameter has documentation, all others need to have it as well
221       has_doc = [doc is not None for (_, _, _, doc) in cls.OP_PARAMS]
222       self.assertTrue(not compat.any(has_doc) or compat.all(has_doc),
223                       msg="%s does not document all parameters" % cls)
224
225   def testValidateNoModification(self):
226     class OpTest(opcodes.OpCode):
227       OP_PARAMS = [
228         ("nodef", ht.NoDefault, ht.TMaybeString, None),
229         ("wdef", "default", ht.TMaybeString, None),
230         ("number", 0, ht.TInt, None),
231         ("notype", None, ht.NoType, None),
232         ]
233
234     # Missing required parameter "nodef"
235     op = OpTest()
236     before = op.__getstate__()
237     self.assertRaises(errors.OpPrereqError, op.Validate, False)
238     self.assertFalse(hasattr(op, "nodef"))
239     self.assertFalse(hasattr(op, "wdef"))
240     self.assertFalse(hasattr(op, "number"))
241     self.assertFalse(hasattr(op, "notype"))
242     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
243
244     # Required parameter "nodef" is provided
245     op = OpTest(nodef="foo")
246     before = op.__getstate__()
247     op.Validate(False)
248     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
249     self.assertEqual(op.nodef, "foo")
250     self.assertFalse(hasattr(op, "wdef"))
251     self.assertFalse(hasattr(op, "number"))
252     self.assertFalse(hasattr(op, "notype"))
253
254     # Missing required parameter "nodef"
255     op = OpTest(wdef="hello", number=999)
256     before = op.__getstate__()
257     self.assertRaises(errors.OpPrereqError, op.Validate, False)
258     self.assertFalse(hasattr(op, "nodef"))
259     self.assertFalse(hasattr(op, "notype"))
260     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
261
262     # Wrong type for "nodef"
263     op = OpTest(nodef=987)
264     before = op.__getstate__()
265     self.assertRaises(errors.OpPrereqError, op.Validate, False)
266     self.assertEqual(op.nodef, 987)
267     self.assertFalse(hasattr(op, "notype"))
268     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
269
270     # Testing different types for "notype"
271     op = OpTest(nodef="foo", notype=[1, 2, 3])
272     before = op.__getstate__()
273     op.Validate(False)
274     self.assertEqual(op.nodef, "foo")
275     self.assertEqual(op.notype, [1, 2, 3])
276     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
277
278     op = OpTest(nodef="foo", notype="Hello World")
279     before = op.__getstate__()
280     op.Validate(False)
281     self.assertEqual(op.nodef, "foo")
282     self.assertEqual(op.notype, "Hello World")
283     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
284
285   def testValidateSetDefaults(self):
286     class OpTest(opcodes.OpCode):
287       OP_PARAMS = [
288         # Static default value
289         ("value1", "default", ht.TMaybeString, None),
290
291         # Default value callback
292         ("value2", lambda: "result", ht.TMaybeString, None),
293         ]
294
295     op = OpTest()
296     before = op.__getstate__()
297     op.Validate(True)
298     self.assertNotEqual(op.__getstate__(), before,
299                         msg="Opcode was not modified")
300     self.assertEqual(op.value1, "default")
301     self.assertEqual(op.value2, "result")
302     self.assert_(op.dry_run is None)
303     self.assert_(op.debug_level is None)
304     self.assertEqual(op.priority, constants.OP_PRIO_DEFAULT)
305
306     op = OpTest(value1="hello", value2="world", debug_level=123)
307     before = op.__getstate__()
308     op.Validate(True)
309     self.assertNotEqual(op.__getstate__(), before,
310                         msg="Opcode was not modified")
311     self.assertEqual(op.value1, "hello")
312     self.assertEqual(op.value2, "world")
313     self.assertEqual(op.debug_level, 123)
314
315   def testOpInstanceMultiAlloc(self):
316     inst = dict([(name, []) for name in opcodes.OpInstanceCreate.GetAllSlots()])
317     inst_op = opcodes.OpInstanceCreate(**inst)
318     inst_state = inst_op.__getstate__()
319
320     multialloc = opcodes.OpInstanceMultiAlloc(instances=[inst_op])
321     state = multialloc.__getstate__()
322     self.assertEquals(state["instances"], [inst_state])
323     loaded_multialloc = opcodes.OpCode.LoadOpCode(state)
324     (loaded_inst,) = loaded_multialloc.instances
325     self.assertNotEquals(loaded_inst, inst_op)
326     self.assertEquals(loaded_inst.__getstate__(), inst_state)
327
328
329 class TestOpcodeDepends(unittest.TestCase):
330   def test(self):
331     check_relative = opcodes._BuildJobDepCheck(True)
332     check_norelative = opcodes.TNoRelativeJobDependencies
333
334     for fn in [check_relative, check_norelative]:
335       self.assertTrue(fn(None))
336       self.assertTrue(fn([]))
337       self.assertTrue(fn([(1, [])]))
338       self.assertTrue(fn([(719833, [])]))
339       self.assertTrue(fn([("24879", [])]))
340       self.assertTrue(fn([(2028, [constants.JOB_STATUS_ERROR])]))
341       self.assertTrue(fn([
342         (2028, [constants.JOB_STATUS_ERROR]),
343         (18750, []),
344         (5063, [constants.JOB_STATUS_SUCCESS, constants.JOB_STATUS_ERROR]),
345         ]))
346
347       self.assertFalse(fn(1))
348       self.assertFalse(fn([(9, )]))
349       self.assertFalse(fn([(15194, constants.JOB_STATUS_ERROR)]))
350
351     for i in [
352       [(-1, [])],
353       [(-27740, [constants.JOB_STATUS_CANCELED, constants.JOB_STATUS_ERROR]),
354        (-1, [constants.JOB_STATUS_ERROR]),
355        (9921, [])],
356       ]:
357       self.assertTrue(check_relative(i))
358       self.assertFalse(check_norelative(i))
359
360
361 class TestResultChecks(unittest.TestCase):
362   def testJobIdList(self):
363     for i in [[], [(False, "error")], [(False, "")],
364               [(True, 123), (True, "999")]]:
365       self.assertTrue(opcodes.TJobIdList(i))
366
367     for i in ["", [("x", 1)], [[], []], [[False, "", None], [True, 123]]]:
368       self.assertFalse(opcodes.TJobIdList(i))
369
370   def testJobIdListOnly(self):
371     self.assertTrue(opcodes.TJobIdListOnly({
372       constants.JOB_IDS_KEY: [],
373       }))
374     self.assertTrue(opcodes.TJobIdListOnly({
375       constants.JOB_IDS_KEY: [(True, "9282")],
376       }))
377
378     self.assertFalse(opcodes.TJobIdListOnly({
379       "x": None,
380       }))
381     self.assertFalse(opcodes.TJobIdListOnly({
382       constants.JOB_IDS_KEY: [],
383       "x": None,
384       }))
385     self.assertFalse(opcodes.TJobIdListOnly({
386       constants.JOB_IDS_KEY: [("foo", "bar")],
387       }))
388     self.assertFalse(opcodes.TJobIdListOnly({
389       constants.JOB_IDS_KEY: [("one", "two", "three")],
390       }))
391
392
393 class TestClusterOsList(unittest.TestCase):
394   def test(self):
395     good = [
396       None,
397       [],
398       [(constants.DDM_ADD, "dos"),
399        (constants.DDM_REMOVE, "linux")],
400       ]
401
402     for i in good:
403       self.assertTrue(opcodes._TestClusterOsList(i))
404
405     wrong = ["", 0, "xy", ["Hello World"], object(),
406       [("foo", "bar")],
407       [("", "")],
408       [[constants.DDM_ADD]],
409       [(constants.DDM_ADD, "")],
410       [(constants.DDM_REMOVE, "")],
411       [(constants.DDM_ADD, None)],
412       [(constants.DDM_REMOVE, None)],
413       ]
414
415     for i in wrong:
416       self.assertFalse(opcodes._TestClusterOsList(i))
417
418
419 class TestOpInstanceSetParams(unittest.TestCase):
420   def _GenericTests(self, fn):
421     self.assertTrue(fn([]))
422     self.assertTrue(fn([(constants.DDM_ADD, {})]))
423     self.assertTrue(fn([(constants.DDM_REMOVE, {})]))
424     for i in [0, 1, 2, 3, 9, 10, 1024]:
425       self.assertTrue(fn([(i, {})]))
426
427     self.assertFalse(fn(None))
428     self.assertFalse(fn({}))
429     self.assertFalse(fn(""))
430     self.assertFalse(fn(0))
431     self.assertFalse(fn([(-100, {})]))
432     self.assertFalse(fn([(constants.DDM_ADD, 2, 3)]))
433     self.assertFalse(fn([[constants.DDM_ADD]]))
434
435   def testNicModifications(self):
436     fn = opcodes.OpInstanceSetParams.TestNicModifications
437     self._GenericTests(fn)
438
439     for param in constants.INIC_PARAMS:
440       self.assertTrue(fn([[constants.DDM_ADD, {param: None}]]))
441       self.assertTrue(fn([[constants.DDM_ADD, {param: param}]]))
442
443   def testDiskModifications(self):
444     fn = opcodes.OpInstanceSetParams.TestDiskModifications
445     self._GenericTests(fn)
446
447     for param in constants.IDISK_PARAMS:
448       self.assertTrue(fn([[constants.DDM_ADD, {param: 0}]]))
449       self.assertTrue(fn([[constants.DDM_ADD, {param: param}]]))
450
451
452 if __name__ == "__main__":
453   testutils.GanetiTestProgram()