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