diff --git a/lib/rapi/baserlib.py b/lib/rapi/baserlib.py index 314de121a1edd510ad4da022d29fd524417a31a9..af799c4684b0edfbae2bc6f3c41cd020240d0119 100644 --- a/lib/rapi/baserlib.py +++ b/lib/rapi/baserlib.py @@ -440,22 +440,23 @@ class _MetaOpcodeResource(type): obj = type.__call__(mcs, *args, **kwargs) for (method, op_attr, rename_attr, fn_attr) in _OPCODE_ATTRS: - try: - opcode = getattr(obj, op_attr) - except AttributeError: - # If the "*_OPCODE" attribute isn't set, "*_RENAME" or "Get*OpInput" - # shouldn't either + if hasattr(obj, method): + # If the method handler is already defined, "*_RENAME" or "Get*OpInput" + # shouldn't be (they're only used by the automatically generated + # handler) assert not hasattr(obj, rename_attr) assert not hasattr(obj, fn_attr) - continue - - assert not hasattr(obj, method) - - # Generate handler method on handler instance - setattr(obj, method, - compat.partial(obj._GenericHandler, opcode, - getattr(obj, rename_attr, None), - getattr(obj, fn_attr, obj._GetDefaultData))) + else: + # Try to generate handler method on handler instance + try: + opcode = getattr(obj, op_attr) + except AttributeError: + pass + else: + setattr(obj, method, + compat.partial(obj._GenericHandler, opcode, + getattr(obj, rename_attr, None), + getattr(obj, fn_attr, obj._GetDefaultData))) return obj diff --git a/lib/rapi/rlib2.py b/lib/rapi/rlib2.py index 550af5a93e340de4d18d10204fc30d9645b016da..049b97eb87c14de41d67d65bb4c757dbeab09c95 100644 --- a/lib/rapi/rlib2.py +++ b/lib/rapi/rlib2.py @@ -182,10 +182,12 @@ class R_version(baserlib.ResourceBase): return constants.RAPI_VERSION -class R_2_info(baserlib.ResourceBase): +class R_2_info(baserlib.OpcodeResource): """/2/info resource. """ + GET_OPCODE = opcodes.OpClusterQuery + def GET(self): """Returns cluster information. @@ -206,10 +208,12 @@ class R_2_features(baserlib.ResourceBase): return list(ALL_FEATURES) -class R_2_os(baserlib.ResourceBase): +class R_2_os(baserlib.OpcodeResource): """/2/os resource. """ + GET_OPCODE = opcodes.OpOsDiagnose + def GET(self): """Return a list of all OSes. @@ -351,10 +355,12 @@ class R_2_jobs_id_wait(baserlib.ResourceBase): } -class R_2_nodes(baserlib.ResourceBase): +class R_2_nodes(baserlib.OpcodeResource): """/2/nodes resource. """ + GET_OPCODE = opcodes.OpNodeQuery + def GET(self): """Returns a list of all nodes. @@ -371,10 +377,12 @@ class R_2_nodes(baserlib.ResourceBase): uri_fields=("id", "uri")) -class R_2_nodes_name(baserlib.ResourceBase): +class R_2_nodes_name(baserlib.OpcodeResource): """/2/nodes/[node_name] resource. """ + GET_OPCODE = opcodes.OpNodeQuery + def GET(self): """Send information about a node. @@ -582,6 +590,7 @@ class R_2_groups(baserlib.OpcodeResource): """/2/groups resource. """ + GET_OPCODE = opcodes.OpGroupQuery POST_OPCODE = opcodes.OpGroupAdd POST_RENAME = { "name": "group_name", @@ -697,6 +706,7 @@ class R_2_instances(baserlib.OpcodeResource): """/2/instances resource. """ + GET_OPCODE = opcodes.OpInstanceQuery POST_OPCODE = opcodes.OpInstanceCreate POST_RENAME = { "os": "os_type", @@ -750,6 +760,7 @@ class R_2_instances_name(baserlib.OpcodeResource): """/2/instances/[instance_name] resource. """ + GET_OPCODE = opcodes.OpInstanceQuery DELETE_OPCODE = opcodes.OpInstanceRemove def GET(self): @@ -885,12 +896,14 @@ def _ParseInstanceReinstallRequest(name, data): return ops -class R_2_instances_name_reinstall(baserlib.ResourceBase): +class R_2_instances_name_reinstall(baserlib.OpcodeResource): """/2/instances/[instance_name]/reinstall resource. Implements an instance reinstall. """ + POST_OPCODE = opcodes.OpInstanceReinstall + def POST(self): """Reinstall an instance. @@ -1097,6 +1110,7 @@ class R_2_instances_name_console(baserlib.ResourceBase): """ GET_ACCESS = [rapi.RAPI_ACCESS_WRITE] + GET_OPCODE = opcodes.OpInstanceConsole def GET(self): """Request information for connecting to instance's console. @@ -1141,6 +1155,8 @@ class R_2_query(baserlib.ResourceBase): """ # Results might contain sensitive information GET_ACCESS = [rapi.RAPI_ACCESS_WRITE] + GET_OPCODE = opcodes.OpQuery + PUT_OPCODE = opcodes.OpQuery def _Query(self, fields, filter_): return self.GetClient().Query(self.items[0], fields, filter_).ToDict() @@ -1175,6 +1191,8 @@ class R_2_query_fields(baserlib.ResourceBase): """/2/query/[resource]/fields resource. """ + GET_OPCODE = opcodes.OpQueryFields + def GET(self): """Retrieves list of available fields for a resource. @@ -1199,6 +1217,7 @@ class _R_Tags(baserlib.OpcodeResource): """ TAG_LEVEL = None + GET_OPCODE = opcodes.OpTagsGet PUT_OPCODE = opcodes.OpTagsSet DELETE_OPCODE = opcodes.OpTagsDel diff --git a/test/ganeti.rapi.baserlib_unittest.py b/test/ganeti.rapi.baserlib_unittest.py index 6b54eac8236524424b034ab0766b8e4821612590..0582cb799d6cf9b6b50244dfd2d2b59acf26e300 100755 --- a/test/ganeti.rapi.baserlib_unittest.py +++ b/test/ganeti.rapi.baserlib_unittest.py @@ -22,11 +22,13 @@ """Script for testing ganeti.rapi.baserlib""" import unittest +import itertools from ganeti import errors from ganeti import opcodes from ganeti import ht from ganeti import http +from ganeti import compat from ganeti.rapi import baserlib import testutils @@ -98,19 +100,45 @@ class TestFillOpcode(unittest.TestCase): class TestOpcodeResource(unittest.TestCase): - def testDoubleDefinition(self): - class _TClass(baserlib.OpcodeResource): - GET_OPCODE = opcodes.OpTestDelay - def GET(self): pass - - self.assertRaises(AssertionError, _TClass, None, None, None) - - def testNoOpCode(self): - class _TClass(baserlib.OpcodeResource): - POST_OPCODE = None - def POST(self): pass + @staticmethod + def _MakeClass(method, attrs): + return type("Test%s" % method, (baserlib.OpcodeResource, ), attrs) + + @staticmethod + def _GetMethodAttributes(method): + attrs = ["%s_OPCODE" % method, "%s_RENAME" % method, + "Get%sOpInput" % method.capitalize()] + assert attrs == dict((opattrs[0], list(opattrs[1:])) + for opattrs in baserlib._OPCODE_ATTRS)[method] + return attrs - self.assertRaises(AssertionError, _TClass, None, None, None) + def test(self): + for method in baserlib._SUPPORTED_METHODS: + # Empty handler + obj = self._MakeClass(method, {})(None, None, None) + for attr in itertools.chain(*baserlib._OPCODE_ATTRS): + self.assertFalse(hasattr(obj, attr)) + + # Direct handler function + obj = self._MakeClass(method, { + method: lambda _: None, + })(None, None, None) + self.assertFalse(compat.all(hasattr(obj, attr) + for i in baserlib._SUPPORTED_METHODS + for attr in self._GetMethodAttributes(i))) + + # Let metaclass define handler function + for opcls in [None, object()]: + obj = self._MakeClass(method, { + "%s_OPCODE" % method: opcls, + })(None, None, None) + self.assertTrue(callable(getattr(obj, method))) + self.assertEqual(getattr(obj, "%s_OPCODE" % method), opcls) + self.assertFalse(hasattr(obj, "%s_RENAME" % method)) + self.assertFalse(compat.any(hasattr(obj, attr) + for i in baserlib._SUPPORTED_METHODS + if i != method + for attr in self._GetMethodAttributes(i))) def testIllegalRename(self): class _TClass(baserlib.OpcodeResource): @@ -124,8 +152,8 @@ class TestOpcodeResource(unittest.TestCase): pass obj = _Empty(None, None, None) - for attr in ["GetPostOpInput", "GetPutOpInput", "GetGetOpInput", - "GetDeleteOpInput"]: + + for attr in itertools.chain(*baserlib._OPCODE_ATTRS): self.assertFalse(hasattr(obj, attr))