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