Add support for classic queries
[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           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   def testOpInstanceMultiAlloc(self):
294     inst = dict([(name, []) for name in opcodes.OpInstanceCreate.GetAllSlots()])
295     inst_op = opcodes.OpInstanceCreate(**inst)
296     inst_state = inst_op.__getstate__()
297
298     multialloc = opcodes.OpInstanceMultiAlloc(instances=[inst_op])
299     state = multialloc.__getstate__()
300     self.assertEquals(state["instances"], [inst_state])
301     loaded_multialloc = opcodes.OpCode.LoadOpCode(state)
302     (loaded_inst,) = loaded_multialloc.instances
303     self.assertNotEquals(loaded_inst, inst_op)
304     self.assertEquals(loaded_inst.__getstate__(), inst_state)
305
306
307 class TestOpcodeDepends(unittest.TestCase):
308   def test(self):
309     check_relative = opcodes._BuildJobDepCheck(True)
310     check_norelative = opcodes.TNoRelativeJobDependencies
311
312     for fn in [check_relative, check_norelative]:
313       self.assertTrue(fn(None))
314       self.assertTrue(fn([]))
315       self.assertTrue(fn([(1, [])]))
316       self.assertTrue(fn([(719833, [])]))
317       self.assertTrue(fn([("24879", [])]))
318       self.assertTrue(fn([(2028, [constants.JOB_STATUS_ERROR])]))
319       self.assertTrue(fn([
320         (2028, [constants.JOB_STATUS_ERROR]),
321         (18750, []),
322         (5063, [constants.JOB_STATUS_SUCCESS, constants.JOB_STATUS_ERROR]),
323         ]))
324
325       self.assertFalse(fn(1))
326       self.assertFalse(fn([(9, )]))
327       self.assertFalse(fn([(15194, constants.JOB_STATUS_ERROR)]))
328
329     for i in [
330       [(-1, [])],
331       [(-27740, [constants.JOB_STATUS_CANCELED, constants.JOB_STATUS_ERROR]),
332        (-1, [constants.JOB_STATUS_ERROR]),
333        (9921, [])],
334       ]:
335       self.assertTrue(check_relative(i))
336       self.assertFalse(check_norelative(i))
337
338
339 class TestResultChecks(unittest.TestCase):
340   def testJobIdList(self):
341     for i in [[], [(False, "error")], [(False, "")],
342               [(True, 123), (True, "999")]]:
343       self.assertTrue(opcodes.TJobIdList(i))
344
345     for i in ["", [("x", 1)], [[], []], [[False, "", None], [True, 123]]]:
346       self.assertFalse(opcodes.TJobIdList(i))
347
348   def testJobIdListOnly(self):
349     self.assertTrue(opcodes.TJobIdListOnly({
350       constants.JOB_IDS_KEY: [],
351       }))
352     self.assertTrue(opcodes.TJobIdListOnly({
353       constants.JOB_IDS_KEY: [(True, "9282")],
354       }))
355
356     self.assertFalse(opcodes.TJobIdListOnly({
357       "x": None,
358       }))
359     self.assertFalse(opcodes.TJobIdListOnly({
360       constants.JOB_IDS_KEY: [],
361       "x": None,
362       }))
363     self.assertFalse(opcodes.TJobIdListOnly({
364       constants.JOB_IDS_KEY: [("foo", "bar")],
365       }))
366     self.assertFalse(opcodes.TJobIdListOnly({
367       constants.JOB_IDS_KEY: [("one", "two", "three")],
368       }))
369
370
371 class TestClusterOsList(unittest.TestCase):
372   def test(self):
373     good = [
374       None,
375       [],
376       [(constants.DDM_ADD, "dos"),
377        (constants.DDM_REMOVE, "linux")],
378       ]
379
380     for i in good:
381       self.assertTrue(opcodes._TestClusterOsList(i))
382
383     wrong = ["", 0, "xy", ["Hello World"], object(),
384       [("foo", "bar")],
385       [("", "")],
386       [[constants.DDM_ADD]],
387       [(constants.DDM_ADD, "")],
388       [(constants.DDM_REMOVE, "")],
389       [(constants.DDM_ADD, None)],
390       [(constants.DDM_REMOVE, None)],
391       ]
392
393     for i in wrong:
394       self.assertFalse(opcodes._TestClusterOsList(i))
395
396
397 class TestOpInstanceSetParams(unittest.TestCase):
398   def _GenericTests(self, fn):
399     self.assertTrue(fn([]))
400     self.assertTrue(fn([(constants.DDM_ADD, {})]))
401     self.assertTrue(fn([(constants.DDM_REMOVE, {})]))
402     for i in [0, 1, 2, 3, 9, 10, 1024]:
403       self.assertTrue(fn([(i, {})]))
404
405     self.assertFalse(fn(None))
406     self.assertFalse(fn({}))
407     self.assertFalse(fn(""))
408     self.assertFalse(fn(0))
409     self.assertFalse(fn([(-100, {})]))
410     self.assertFalse(fn([(constants.DDM_ADD, 2, 3)]))
411     self.assertFalse(fn([[constants.DDM_ADD]]))
412
413   def testNicModifications(self):
414     fn = opcodes.OpInstanceSetParams.TestNicModifications
415     self._GenericTests(fn)
416
417     for param in constants.INIC_PARAMS:
418       self.assertTrue(fn([[constants.DDM_ADD, {param: None}]]))
419       self.assertTrue(fn([[constants.DDM_ADD, {param: param}]]))
420
421   def testDiskModifications(self):
422     fn = opcodes.OpInstanceSetParams.TestDiskModifications
423     self._GenericTests(fn)
424
425     for param in constants.IDISK_PARAMS:
426       self.assertTrue(fn([[constants.DDM_ADD, {param: 0}]]))
427       self.assertTrue(fn([[constants.DDM_ADD, {param: param}]]))
428
429
430 if __name__ == "__main__":
431   testutils.GanetiTestProgram()