Merge branch 'stable-2.6'
[ganeti-local] / test / ganeti.opcodes_unittest.py
index ff52381..7e6cb6e 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/python
 #
 
-# Copyright (C) 2010 Google Inc.
+# Copyright (C) 2010, 2011 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -30,10 +30,21 @@ from ganeti import opcodes
 from ganeti import ht
 from ganeti import constants
 from ganeti import errors
+from ganeti import compat
 
 import testutils
 
 
+#: Unless an opcode is included in the following list it must have a result
+#: check of some sort
+MISSING_RESULT_CHECK = frozenset([
+  opcodes.OpTestAllocator,
+  opcodes.OpTestDelay,
+  opcodes.OpTestDummy,
+  opcodes.OpTestJqueue,
+  ])
+
+
 class TestOpcodes(unittest.TestCase):
   def test(self):
     self.assertRaises(ValueError, opcodes.OpCode.LoadOpCode, None)
@@ -45,6 +56,16 @@ class TestOpcodes(unittest.TestCase):
       self.assert_(cls.OP_ID.startswith("OP_"))
       self.assert_(len(cls.OP_ID) > 3)
       self.assertEqual(cls.OP_ID, cls.OP_ID.upper())
+      self.assertEqual(cls.OP_ID, opcodes._NameToId(cls.__name__))
+      self.assertFalse(compat.any(cls.OP_ID.startswith(prefix)
+                                  for prefix in opcodes._SUMMARY_PREFIX.keys()))
+      if cls in MISSING_RESULT_CHECK:
+        self.assertTrue(cls.OP_RESULT is None,
+                        msg=("%s is listed to not have a result check" %
+                             cls.OP_ID))
+      else:
+        self.assertTrue(callable(cls.OP_RESULT),
+                        msg=("%s should have a result check" % cls.OP_ID))
 
       self.assertRaises(TypeError, cls, unsupported_parameter="some value")
 
@@ -88,32 +109,42 @@ class TestOpcodes(unittest.TestCase):
       self.assertEqual("OP_%s" % summary, op.OP_ID)
 
   def testSummary(self):
-    class _TestOp(opcodes.OpCode):
-      OP_ID = "OP_TEST"
+    class OpTest(opcodes.OpCode):
       OP_DSC_FIELD = "data"
       OP_PARAMS = [
-        ("data", ht.NoDefault, ht.TString),
+        ("data", ht.NoDefault, ht.TString, None),
         ]
 
-    self.assertEqual(_TestOp(data="").Summary(), "TEST()")
-    self.assertEqual(_TestOp(data="Hello World").Summary(),
+    self.assertEqual(OpTest(data="").Summary(), "TEST()")
+    self.assertEqual(OpTest(data="Hello World").Summary(),
                      "TEST(Hello World)")
-    self.assertEqual(_TestOp(data="node1.example.com").Summary(),
+    self.assertEqual(OpTest(data="node1.example.com").Summary(),
                      "TEST(node1.example.com)")
 
+  def testTinySummary(self):
+    self.assertFalse(utils.FindDuplicates(opcodes._SUMMARY_PREFIX.values()))
+    self.assertTrue(compat.all(prefix.endswith("_") and supplement.endswith("_")
+                               for (prefix, supplement) in
+                                 opcodes._SUMMARY_PREFIX.items()))
+
+    self.assertEqual(opcodes.OpClusterPostInit().TinySummary(), "C_POST_INIT")
+    self.assertEqual(opcodes.OpNodeRemove().TinySummary(), "N_REMOVE")
+    self.assertEqual(opcodes.OpInstanceMigrate().TinySummary(), "I_MIGRATE")
+    self.assertEqual(opcodes.OpGroupQuery().TinySummary(), "G_QUERY")
+    self.assertEqual(opcodes.OpTestJqueue().TinySummary(), "TEST_JQUEUE")
+
   def testListSummary(self):
-    class _TestOp(opcodes.OpCode):
-      OP_ID = "OP_TEST"
+    class OpTest(opcodes.OpCode):
       OP_DSC_FIELD = "data"
       OP_PARAMS = [
-        ("data", ht.NoDefault, ht.TList),
+        ("data", ht.NoDefault, ht.TList, None),
         ]
 
-    self.assertEqual(_TestOp(data=["a", "b", "c"]).Summary(),
+    self.assertEqual(OpTest(data=["a", "b", "c"]).Summary(),
                      "TEST(a,b,c)")
-    self.assertEqual(_TestOp(data=["a", None, "c"]).Summary(),
+    self.assertEqual(OpTest(data=["a", None, "c"]).Summary(),
                      "TEST(a,None,c)")
-    self.assertEqual(_TestOp(data=[1, 2, 3, 4]).Summary(), "TEST(1,2,3,4)")
+    self.assertEqual(OpTest(data=[1, 2, 3, 4]).Summary(), "TEST(1,2,3,4)")
 
   def testOpId(self):
     self.assertFalse(utils.FindDuplicates(cls.OP_ID
@@ -123,10 +154,10 @@ class TestOpcodes(unittest.TestCase):
   def testParams(self):
     supported_by_all = set(["debug_level", "dry_run", "priority"])
 
-    self.assert_(opcodes.BaseOpCode not in opcodes.OP_MAPPING.values())
-    self.assert_(opcodes.OpCode in opcodes.OP_MAPPING.values())
+    self.assertTrue(opcodes.BaseOpCode not in opcodes.OP_MAPPING.values())
+    self.assertTrue(opcodes.OpCode not in opcodes.OP_MAPPING.values())
 
-    for cls in opcodes.OP_MAPPING.values():
+    for cls in opcodes.OP_MAPPING.values() + [opcodes.OpCode]:
       all_slots = cls._all_slots()
 
       self.assertEqual(len(set(all_slots) & supported_by_all), 3,
@@ -137,12 +168,13 @@ class TestOpcodes(unittest.TestCase):
       self.assert_(hasattr(cls, "OP_PARAMS"),
                    msg="%s doesn't have OP_PARAMS" % cls.OP_ID)
 
-      param_names = [name for (name, _, _) in cls.GetAllParams()]
+      param_names = [name for (name, _, _, _) in cls.GetAllParams()]
 
       self.assertEqual(all_slots, param_names)
 
       # Without inheritance
-      self.assertEqual(cls.__slots__, [name for (name, _, _) in cls.OP_PARAMS])
+      self.assertEqual(cls.__slots__,
+                       [name for (name, _, _, _) in cls.OP_PARAMS])
 
       # This won't work if parameters are converted to a dictionary
       duplicates = utils.FindDuplicates(param_names)
@@ -151,28 +183,33 @@ class TestOpcodes(unittest.TestCase):
                             (duplicates, cls.OP_ID)))
 
       # Check parameter definitions
-      for attr_name, aval, test in cls.GetAllParams():
+      for attr_name, aval, test, doc in cls.GetAllParams():
         self.assert_(attr_name)
         self.assert_(test is None or test is ht.NoType or callable(test),
                      msg=("Invalid type check for %s.%s" %
                           (cls.OP_ID, attr_name)))
+        self.assertTrue(doc is None or isinstance(doc, basestring))
 
         if callable(aval):
           self.assertFalse(callable(aval()),
                            msg="Default value returned by function is callable")
 
+      # If any parameter has documentation, all others need to have it as well
+      has_doc = [doc is not None for (_, _, _, doc) in cls.OP_PARAMS]
+      self.assertTrue(not compat.any(has_doc) or compat.all(has_doc),
+                      msg="%s does not document all parameters" % cls)
+
   def testValidateNoModification(self):
-    class _TestOp(opcodes.OpCode):
-      OP_ID = "OP_TEST"
+    class OpTest(opcodes.OpCode):
       OP_PARAMS = [
-        ("nodef", ht.NoDefault, ht.TMaybeString),
-        ("wdef", "default", ht.TMaybeString),
-        ("number", 0, ht.TInt),
-        ("notype", None, ht.NoType),
+        ("nodef", ht.NoDefault, ht.TMaybeString, None),
+        ("wdef", "default", ht.TMaybeString, None),
+        ("number", 0, ht.TInt, None),
+        ("notype", None, ht.NoType, None),
         ]
 
     # Missing required parameter "nodef"
-    op = _TestOp()
+    op = OpTest()
     before = op.__getstate__()
     self.assertRaises(errors.OpPrereqError, op.Validate, False)
     self.assertFalse(hasattr(op, "nodef"))
@@ -182,7 +219,7 @@ class TestOpcodes(unittest.TestCase):
     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
 
     # Required parameter "nodef" is provided
-    op = _TestOp(nodef="foo")
+    op = OpTest(nodef="foo")
     before = op.__getstate__()
     op.Validate(False)
     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
@@ -192,7 +229,7 @@ class TestOpcodes(unittest.TestCase):
     self.assertFalse(hasattr(op, "notype"))
 
     # Missing required parameter "nodef"
-    op = _TestOp(wdef="hello", number=999)
+    op = OpTest(wdef="hello", number=999)
     before = op.__getstate__()
     self.assertRaises(errors.OpPrereqError, op.Validate, False)
     self.assertFalse(hasattr(op, "nodef"))
@@ -200,7 +237,7 @@ class TestOpcodes(unittest.TestCase):
     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
 
     # Wrong type for "nodef"
-    op = _TestOp(nodef=987)
+    op = OpTest(nodef=987)
     before = op.__getstate__()
     self.assertRaises(errors.OpPrereqError, op.Validate, False)
     self.assertEqual(op.nodef, 987)
@@ -208,14 +245,14 @@ class TestOpcodes(unittest.TestCase):
     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
 
     # Testing different types for "notype"
-    op = _TestOp(nodef="foo", notype=[1, 2, 3])
+    op = OpTest(nodef="foo", notype=[1, 2, 3])
     before = op.__getstate__()
     op.Validate(False)
     self.assertEqual(op.nodef, "foo")
     self.assertEqual(op.notype, [1, 2, 3])
     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
 
-    op = _TestOp(nodef="foo", notype="Hello World")
+    op = OpTest(nodef="foo", notype="Hello World")
     before = op.__getstate__()
     op.Validate(False)
     self.assertEqual(op.nodef, "foo")
@@ -223,17 +260,16 @@ class TestOpcodes(unittest.TestCase):
     self.assertEqual(op.__getstate__(), before, msg="Opcode was modified")
 
   def testValidateSetDefaults(self):
-    class _TestOp(opcodes.OpCode):
-      OP_ID = "OP_TEST"
+    class OpTest(opcodes.OpCode):
       OP_PARAMS = [
         # Static default value
-        ("value1", "default", ht.TMaybeString),
+        ("value1", "default", ht.TMaybeString, None),
 
         # Default value callback
-        ("value2", lambda: "result", ht.TMaybeString),
+        ("value2", lambda: "result", ht.TMaybeString, None),
         ]
 
-    op = _TestOp()
+    op = OpTest()
     before = op.__getstate__()
     op.Validate(True)
     self.assertNotEqual(op.__getstate__(), before,
@@ -244,7 +280,7 @@ class TestOpcodes(unittest.TestCase):
     self.assert_(op.debug_level is None)
     self.assertEqual(op.priority, constants.OP_PRIO_DEFAULT)
 
-    op = _TestOp(value1="hello", value2="world", debug_level=123)
+    op = OpTest(value1="hello", value2="world", debug_level=123)
     before = op.__getstate__()
     op.Validate(True)
     self.assertNotEqual(op.__getstate__(), before,
@@ -254,5 +290,128 @@ class TestOpcodes(unittest.TestCase):
     self.assertEqual(op.debug_level, 123)
 
 
+class TestOpcodeDepends(unittest.TestCase):
+  def test(self):
+    check_relative = opcodes._BuildJobDepCheck(True)
+    check_norelative = opcodes.TNoRelativeJobDependencies
+
+    for fn in [check_relative, check_norelative]:
+      self.assertTrue(fn(None))
+      self.assertTrue(fn([]))
+      self.assertTrue(fn([(1, [])]))
+      self.assertTrue(fn([(719833, [])]))
+      self.assertTrue(fn([("24879", [])]))
+      self.assertTrue(fn([(2028, [constants.JOB_STATUS_ERROR])]))
+      self.assertTrue(fn([
+        (2028, [constants.JOB_STATUS_ERROR]),
+        (18750, []),
+        (5063, [constants.JOB_STATUS_SUCCESS, constants.JOB_STATUS_ERROR]),
+        ]))
+
+      self.assertFalse(fn(1))
+      self.assertFalse(fn([(9, )]))
+      self.assertFalse(fn([(15194, constants.JOB_STATUS_ERROR)]))
+
+    for i in [
+      [(-1, [])],
+      [(-27740, [constants.JOB_STATUS_CANCELED, constants.JOB_STATUS_ERROR]),
+       (-1, [constants.JOB_STATUS_ERROR]),
+       (9921, [])],
+      ]:
+      self.assertTrue(check_relative(i))
+      self.assertFalse(check_norelative(i))
+
+
+class TestResultChecks(unittest.TestCase):
+  def testJobIdList(self):
+    for i in [[], [(False, "error")], [(False, "")],
+              [(True, 123), (True, "999")]]:
+      self.assertTrue(opcodes.TJobIdList(i))
+
+    for i in ["", [("x", 1)], [[], []], [[False, "", None], [True, 123]]]:
+      self.assertFalse(opcodes.TJobIdList(i))
+
+  def testJobIdListOnly(self):
+    self.assertTrue(opcodes.TJobIdListOnly({
+      constants.JOB_IDS_KEY: [],
+      }))
+    self.assertTrue(opcodes.TJobIdListOnly({
+      constants.JOB_IDS_KEY: [(True, "9282")],
+      }))
+
+    self.assertFalse(opcodes.TJobIdListOnly({
+      "x": None,
+      }))
+    self.assertFalse(opcodes.TJobIdListOnly({
+      constants.JOB_IDS_KEY: [],
+      "x": None,
+      }))
+    self.assertFalse(opcodes.TJobIdListOnly({
+      constants.JOB_IDS_KEY: [("foo", "bar")],
+      }))
+    self.assertFalse(opcodes.TJobIdListOnly({
+      constants.JOB_IDS_KEY: [("one", "two", "three")],
+      }))
+
+
+class TestClusterOsList(unittest.TestCase):
+  def test(self):
+    good = [
+      None,
+      [],
+      [(constants.DDM_ADD, "dos"),
+       (constants.DDM_REMOVE, "linux")],
+      ]
+
+    for i in good:
+      self.assertTrue(opcodes._TestClusterOsList(i))
+
+    wrong = ["", 0, "xy", ["Hello World"], object(),
+      [("foo", "bar")],
+      [("", "")],
+      [[constants.DDM_ADD]],
+      [(constants.DDM_ADD, "")],
+      [(constants.DDM_REMOVE, "")],
+      [(constants.DDM_ADD, None)],
+      [(constants.DDM_REMOVE, None)],
+      ]
+
+    for i in wrong:
+      self.assertFalse(opcodes._TestClusterOsList(i))
+
+
+class TestOpInstanceSetParams(unittest.TestCase):
+  def _GenericTests(self, fn):
+    self.assertTrue(fn([]))
+    self.assertTrue(fn([(constants.DDM_ADD, {})]))
+    self.assertTrue(fn([(constants.DDM_REMOVE, {})]))
+    for i in [0, 1, 2, 3, 9, 10, 1024]:
+      self.assertTrue(fn([(i, {})]))
+
+    self.assertFalse(fn(None))
+    self.assertFalse(fn({}))
+    self.assertFalse(fn(""))
+    self.assertFalse(fn(0))
+    self.assertFalse(fn([(-100, {})]))
+    self.assertFalse(fn([(constants.DDM_ADD, 2, 3)]))
+    self.assertFalse(fn([[constants.DDM_ADD]]))
+
+  def testNicModifications(self):
+    fn = opcodes.OpInstanceSetParams.TestNicModifications
+    self._GenericTests(fn)
+
+    for param in constants.INIC_PARAMS:
+      self.assertTrue(fn([[constants.DDM_ADD, {param: None}]]))
+      self.assertTrue(fn([[constants.DDM_ADD, {param: param}]]))
+
+  def testDiskModifications(self):
+    fn = opcodes.OpInstanceSetParams.TestDiskModifications
+    self._GenericTests(fn)
+
+    for param in constants.IDISK_PARAMS:
+      self.assertTrue(fn([[constants.DDM_ADD, {param: 0}]]))
+      self.assertTrue(fn([[constants.DDM_ADD, {param: param}]]))
+
+
 if __name__ == "__main__":
   testutils.GanetiTestProgram()