rapi.testutils: Add exported functions to verify opcode input/result
authorMichael Hanselmann <hansmi@google.com>
Tue, 14 Feb 2012 16:11:41 +0000 (17:11 +0100)
committerMichael Hanselmann <hansmi@google.com>
Wed, 15 Feb 2012 10:20:26 +0000 (11:20 +0100)
These can be used by third-party code to verify mock code. Further work
on mocks is forthcoming, so this is only a start.

Signed-off-by: Michael Hanselmann <hansmi@google.com>
Reviewed-by: Iustin Pop <iustin@google.com>
Reviewed-by: RenĂ© Nussbaumer <rn@google.com>

Makefile.am
lib/rapi/testutils.py [new file with mode: 0644]
test/ganeti.rapi.testutils_unittest.py [new file with mode: 0755]

index d3cefb7..66f4e80 100644 (file)
@@ -252,7 +252,8 @@ rapi_PYTHON = \
        lib/rapi/client.py \
        lib/rapi/client_utils.py \
        lib/rapi/connector.py \
-       lib/rapi/rlib2.py
+       lib/rapi/rlib2.py \
+       lib/rapi/testutils.py
 
 http_PYTHON = \
        lib/http/__init__.py \
@@ -750,6 +751,7 @@ python_tests = \
        test/ganeti.rapi.client_unittest.py \
        test/ganeti.rapi.resources_unittest.py \
        test/ganeti.rapi.rlib2_unittest.py \
+       test/ganeti.rapi.testutils_unittest.py \
        test/ganeti.rpc_unittest.py \
        test/ganeti.runtime_unittest.py \
        test/ganeti.serializer_unittest.py \
diff --git a/lib/rapi/testutils.py b/lib/rapi/testutils.py
new file mode 100644 (file)
index 0000000..2d47353
--- /dev/null
@@ -0,0 +1,106 @@
+#
+#
+
+# Copyright (C) 2012 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
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+
+"""Remote API test utilities.
+
+"""
+
+import logging
+
+from ganeti import errors
+from ganeti import opcodes
+
+
+class VerificationError(Exception):
+  """Dedicated error class for test utilities.
+
+  This class is used to hide all of Ganeti's internal exception, so that
+  external users of these utilities don't have to integrate Ganeti's exception
+  hierarchy.
+
+  """
+
+
+def _GetOpById(op_id):
+  """Tries to get an opcode class based on its C{OP_ID}.
+
+  """
+  try:
+    return opcodes.OP_MAPPING[op_id]
+  except KeyError:
+    raise VerificationError("Unknown opcode ID '%s'" % op_id)
+
+
+def _HideInternalErrors(fn):
+  """Hides Ganeti-internal exceptions, see L{VerificationError}.
+
+  """
+  def wrapper(*args, **kwargs):
+    try:
+      return fn(*args, **kwargs)
+    except errors.GenericError, err:
+      raise VerificationError("Unhandled Ganeti error: %s" % err)
+
+  return wrapper
+
+
+@_HideInternalErrors
+def VerifyOpInput(op_id, data):
+  """Verifies opcode parameters according to their definition.
+
+  @type op_id: string
+  @param op_id: Opcode ID (C{OP_ID} attribute), e.g. C{OP_CLUSTER_VERIFY}
+  @type data: dict
+  @param data: Opcode parameter values
+  @raise VerificationError: Parameter verification failed
+
+  """
+  op_cls = _GetOpById(op_id)
+
+  try:
+    op = op_cls(**data) # pylint: disable=W0142
+  except TypeError, err:
+    raise VerificationError("Unable to create opcode instance: %s" % err)
+
+  try:
+    op.Validate(False)
+  except errors.OpPrereqError, err:
+    raise VerificationError("Parameter validation for opcode '%s' failed: %s" %
+                            (op_id, err))
+
+
+@_HideInternalErrors
+def VerifyOpResult(op_id, result):
+  """Verifies opcode results used in tests (e.g. in a mock).
+
+  @type op_id: string
+  @param op_id: Opcode ID (C{OP_ID} attribute), e.g. C{OP_CLUSTER_VERIFY}
+  @param result: Mocked opcode result
+  @raise VerificationError: Return value verification failed
+
+  """
+  resultcheck_fn = _GetOpById(op_id).OP_RESULT
+
+  if not resultcheck_fn:
+    logging.warning("Opcode '%s' has no result type definition", op_id)
+  elif not resultcheck_fn(result):
+    raise VerificationError("Given result does not match result description"
+                            " for opcode '%s': %s" % (op_id, resultcheck_fn))
diff --git a/test/ganeti.rapi.testutils_unittest.py b/test/ganeti.rapi.testutils_unittest.py
new file mode 100755 (executable)
index 0000000..55c0c9a
--- /dev/null
@@ -0,0 +1,100 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2012 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
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+
+"""Script for testing ganeti.rapi.testutils"""
+
+import unittest
+
+from ganeti import compat
+from ganeti import constants
+from ganeti import errors
+from ganeti import opcodes
+from ganeti import rapi
+
+import ganeti.rapi.testutils
+
+import testutils
+
+
+class TestHideInternalErrors(unittest.TestCase):
+  def test(self):
+    def inner():
+      raise errors.GenericError("error")
+
+    fn = rapi.testutils._HideInternalErrors(inner)
+
+    self.assertRaises(rapi.testutils.VerificationError, fn)
+
+
+class TestVerifyOpInput(unittest.TestCase):
+  def testUnknownOpId(self):
+    voi = rapi.testutils.VerifyOpInput
+
+    self.assertRaises(rapi.testutils.VerificationError, voi, "UNK_OP_ID", None)
+
+  def testUnknownParameter(self):
+    voi = rapi.testutils.VerifyOpInput
+
+    self.assertRaises(rapi.testutils.VerificationError, voi,
+      opcodes.OpClusterRename.OP_ID, {
+      "unk": "unk",
+      })
+
+  def testWrongParameterValue(self):
+    voi = rapi.testutils.VerifyOpInput
+    self.assertRaises(rapi.testutils.VerificationError, voi,
+      opcodes.OpClusterRename.OP_ID, {
+      "name": object(),
+      })
+
+  def testSuccess(self):
+    voi = rapi.testutils.VerifyOpInput
+    voi(opcodes.OpClusterRename.OP_ID, {
+      "name": "new-name.example.com",
+      })
+
+
+class TestVerifyOpResult(unittest.TestCase):
+  def testSuccess(self):
+    vor = rapi.testutils.VerifyOpResult
+
+    vor(opcodes.OpClusterVerify.OP_ID, {
+      constants.JOB_IDS_KEY: [
+        (False, "error message"),
+        ],
+      })
+
+  def testWrongResult(self):
+    vor = rapi.testutils.VerifyOpResult
+
+    self.assertRaises(rapi.testutils.VerificationError, vor,
+      opcodes.OpClusterVerify.OP_ID, [])
+
+  def testNoResultCheck(self):
+    vor = rapi.testutils.VerifyOpResult
+
+    assert opcodes.OpTestDummy.OP_RESULT is None
+
+    vor(opcodes.OpTestDummy.OP_ID, None)
+
+
+if __name__ == "__main__":
+  testutils.GanetiTestProgram()