Update default instance kernel version
[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, False) for name in cls._all_slots()])
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._all_slots()
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._all_slots()
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           self.assertFalse(callable(aval()),
195                            msg="Default value returned by function is callable")
196
197       # If any parameter has documentation, all others need to have it as well
198       has_doc = [doc is not None for (_, _, _, doc) in cls.OP_PARAMS]
199       self.assertTrue(not compat.any(has_doc) or compat.all(has_doc),
200                       msg="%s does not document all parameters" % cls)
201
202   def testValidateNoModification(self):
203     class OpTest(opcodes.OpCode):
204       OP_PARAMS = [
205         ("nodef", ht.NoDefault, ht.TMaybeString, None),
206         ("wdef", "default", ht.TMaybeString, None),
207         ("number", 0, ht.TInt, None),
208         ("notype", None, ht.NoType, None),
209         ]
210
211     # Missing required parameter "nodef"
212     op = OpTest()
213     before = op.__getstate__()
214     self.assertRaises(errors.OpPrereqError, op.Validate, False)
215     self.assertFalse(hasattr(op, "nodef"))
216     self.assertFalse(hasattr(op, "wdef"))
217     self.assertFalse(hasattr(op, "number"))
218     self.assertFalse(hasattr(op, "notype"))
219     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
220
221     # Required parameter "nodef" is provided
222     op = OpTest(nodef="foo")
223     before = op.__getstate__()
224     op.Validate(False)
225     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
226     self.assertEqual(op.nodef, "foo")
227     self.assertFalse(hasattr(op, "wdef"))
228     self.assertFalse(hasattr(op, "number"))
229     self.assertFalse(hasattr(op, "notype"))
230
231     # Missing required parameter "nodef"
232     op = OpTest(wdef="hello", number=999)
233     before = op.__getstate__()
234     self.assertRaises(errors.OpPrereqError, op.Validate, False)
235     self.assertFalse(hasattr(op, "nodef"))
236     self.assertFalse(hasattr(op, "notype"))
237     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
238
239     # Wrong type for "nodef"
240     op = OpTest(nodef=987)
241     before = op.__getstate__()
242     self.assertRaises(errors.OpPrereqError, op.Validate, False)
243     self.assertEqual(op.nodef, 987)
244     self.assertFalse(hasattr(op, "notype"))
245     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
246
247     # Testing different types for "notype"
248     op = OpTest(nodef="foo", notype=[1, 2, 3])
249     before = op.__getstate__()
250     op.Validate(False)
251     self.assertEqual(op.nodef, "foo")
252     self.assertEqual(op.notype, [1, 2, 3])
253     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
254
255     op = OpTest(nodef="foo", notype="Hello World")
256     before = op.__getstate__()
257     op.Validate(False)
258     self.assertEqual(op.nodef, "foo")
259     self.assertEqual(op.notype, "Hello World")
260     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
261
262   def testValidateSetDefaults(self):
263     class OpTest(opcodes.OpCode):
264       OP_PARAMS = [
265         # Static default value
266         ("value1", "default", ht.TMaybeString, None),
267
268         # Default value callback
269         ("value2", lambda: "result", ht.TMaybeString, None),
270         ]
271
272     op = OpTest()
273     before = op.__getstate__()
274     op.Validate(True)
275     self.assertNotEqual(op.__getstate__(), before,
276                         msg="Opcode was not modified")
277     self.assertEqual(op.value1, "default")
278     self.assertEqual(op.value2, "result")
279     self.assert_(op.dry_run is None)
280     self.assert_(op.debug_level is None)
281     self.assertEqual(op.priority, constants.OP_PRIO_DEFAULT)
282
283     op = OpTest(value1="hello", value2="world", debug_level=123)
284     before = op.__getstate__()
285     op.Validate(True)
286     self.assertNotEqual(op.__getstate__(), before,
287                         msg="Opcode was not modified")
288     self.assertEqual(op.value1, "hello")
289     self.assertEqual(op.value2, "world")
290     self.assertEqual(op.debug_level, 123)
291
292
293 class TestOpcodeDepends(unittest.TestCase):
294   def test(self):
295     check_relative = opcodes._BuildJobDepCheck(True)
296     check_norelative = opcodes.TNoRelativeJobDependencies
297
298     for fn in [check_relative, check_norelative]:
299       self.assertTrue(fn(None))
300       self.assertTrue(fn([]))
301       self.assertTrue(fn([(1, [])]))
302       self.assertTrue(fn([(719833, [])]))
303       self.assertTrue(fn([("24879", [])]))
304       self.assertTrue(fn([(2028, [constants.JOB_STATUS_ERROR])]))
305       self.assertTrue(fn([
306         (2028, [constants.JOB_STATUS_ERROR]),
307         (18750, []),
308         (5063, [constants.JOB_STATUS_SUCCESS, constants.JOB_STATUS_ERROR]),
309         ]))
310
311       self.assertFalse(fn(1))
312       self.assertFalse(fn([(9, )]))
313       self.assertFalse(fn([(15194, constants.JOB_STATUS_ERROR)]))
314
315     for i in [
316       [(-1, [])],
317       [(-27740, [constants.JOB_STATUS_CANCELED, constants.JOB_STATUS_ERROR]),
318        (-1, [constants.JOB_STATUS_ERROR]),
319        (9921, [])],
320       ]:
321       self.assertTrue(check_relative(i))
322       self.assertFalse(check_norelative(i))
323
324
325 class TestResultChecks(unittest.TestCase):
326   def testJobIdList(self):
327     for i in [[], [(False, "error")], [(False, "")],
328               [(True, 123), (True, "999")]]:
329       self.assertTrue(opcodes.TJobIdList(i))
330
331     for i in ["", [("x", 1)], [[], []], [[False, "", None], [True, 123]]]:
332       self.assertFalse(opcodes.TJobIdList(i))
333
334   def testJobIdListOnly(self):
335     self.assertTrue(opcodes.TJobIdListOnly({
336       constants.JOB_IDS_KEY: [],
337       }))
338     self.assertTrue(opcodes.TJobIdListOnly({
339       constants.JOB_IDS_KEY: [(True, "9282")],
340       }))
341
342     self.assertFalse(opcodes.TJobIdListOnly({
343       "x": None,
344       }))
345     self.assertFalse(opcodes.TJobIdListOnly({
346       constants.JOB_IDS_KEY: [],
347       "x": None,
348       }))
349     self.assertFalse(opcodes.TJobIdListOnly({
350       constants.JOB_IDS_KEY: [("foo", "bar")],
351       }))
352     self.assertFalse(opcodes.TJobIdListOnly({
353       constants.JOB_IDS_KEY: [("one", "two", "three")],
354       }))
355
356
357 class TestClusterOsList(unittest.TestCase):
358   def test(self):
359     good = [
360       None,
361       [],
362       [(constants.DDM_ADD, "dos"),
363        (constants.DDM_REMOVE, "linux")],
364       ]
365
366     for i in good:
367       self.assertTrue(opcodes._TestClusterOsList(i))
368
369     wrong = ["", 0, "xy", ["Hello World"], object(),
370       [("foo", "bar")],
371       [("", "")],
372       [[constants.DDM_ADD]],
373       [(constants.DDM_ADD, "")],
374       [(constants.DDM_REMOVE, "")],
375       [(constants.DDM_ADD, None)],
376       [(constants.DDM_REMOVE, None)],
377       ]
378
379     for i in wrong:
380       self.assertFalse(opcodes._TestClusterOsList(i))
381
382
383 class TestOpInstanceSetParams(unittest.TestCase):
384   def _GenericTests(self, fn):
385     self.assertTrue(fn([]))
386     self.assertTrue(fn([(constants.DDM_ADD, {})]))
387     self.assertTrue(fn([(constants.DDM_REMOVE, {})]))
388     for i in [0, 1, 2, 3, 9, 10, 1024]:
389       self.assertTrue(fn([(i, {})]))
390
391     self.assertFalse(fn(None))
392     self.assertFalse(fn({}))
393     self.assertFalse(fn(""))
394     self.assertFalse(fn(0))
395     self.assertFalse(fn([(-100, {})]))
396     self.assertFalse(fn([(constants.DDM_ADD, 2, 3)]))
397     self.assertFalse(fn([[constants.DDM_ADD]]))
398
399   def testNicModifications(self):
400     fn = opcodes.OpInstanceSetParams.TestNicModifications
401     self._GenericTests(fn)
402
403     for param in constants.INIC_PARAMS:
404       self.assertTrue(fn([[constants.DDM_ADD, {param: None}]]))
405       self.assertTrue(fn([[constants.DDM_ADD, {param: param}]]))
406
407   def testDiskModifications(self):
408     fn = opcodes.OpInstanceSetParams.TestDiskModifications
409     self._GenericTests(fn)
410
411     for param in constants.IDISK_PARAMS:
412       self.assertTrue(fn([[constants.DDM_ADD, {param: 0}]]))
413       self.assertTrue(fn([[constants.DDM_ADD, {param: param}]]))
414
415
416 if __name__ == "__main__":
417   testutils.GanetiTestProgram()