diff --git a/Makefile.am b/Makefile.am index d3cefb749c414619ea74d782226b3906c0e44d28..66f4e80e77582f3c85db7f5a5805643dc79bafae 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 index 0000000000000000000000000000000000000000..2d473538df773915233fb79df26b7ec95b744597 --- /dev/null +++ b/lib/rapi/testutils.py @@ -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 index 0000000000000000000000000000000000000000..55c0c9abc9a95ebed12ab1e4b2b7a0421ed19adc --- /dev/null +++ b/test/ganeti.rapi.testutils_unittest.py @@ -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()