rlib2: Declare all opcodes and equivalents
authorMichael Hanselmann <hansmi@google.com>
Wed, 14 Sep 2011 10:20:30 +0000 (12:20 +0200)
committerMichael Hanselmann <hansmi@google.com>
Wed, 14 Sep 2011 15:29:52 +0000 (17:29 +0200)
By declaring all used opcodes or opcodes equivalent to the operations
executed in a resource we will be able to ensure all opcodes are covered
by RAPI (with some exceptions).

Signed-off-by: Michael Hanselmann <hansmi@google.com>
Reviewed-by: Iustin Pop <iustin@google.com>

lib/rapi/baserlib.py
lib/rapi/rlib2.py
test/ganeti.rapi.baserlib_unittest.py

index 314de12..af799c4 100644 (file)
@@ -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
 
index 550af5a..049b97e 100644 (file)
@@ -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
 
index 6b54eac..0582cb7 100755 (executable)
 """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))