diff --git a/neutron/api/extensions.py b/neutron/api/extensions.py index 666b2cede2..2652493238 100644 --- a/neutron/api/extensions.py +++ b/neutron/api/extensions.py @@ -51,6 +51,10 @@ class PluginInterface(object): marked with the abstractmethod decorator is provided by the plugin class. """ + + if not cls.__abstractmethods__: + return NotImplemented + for method in cls.__abstractmethods__: if any(method in base.__dict__ for base in klass.__mro__): continue diff --git a/neutron/tests/unit/test_extensions.py b/neutron/tests/unit/test_extensions.py index fc373709bb..6ab310f75a 100644 --- a/neutron/tests/unit/test_extensions.py +++ b/neutron/tests/unit/test_extensions.py @@ -15,6 +15,8 @@ # License for the specific language governing permissions and limitations # under the License. +import abc + import mock import routes import webob @@ -57,6 +59,47 @@ class FakePluginWithExtension(db_base_plugin_v2.NeutronDbPluginV2): self._log("method_to_support_foxnsox_extension", context) +class PluginInterfaceTest(base.BaseTestCase): + def test_issubclass_hook(self): + class A(object): + def f(self): + pass + + class B(extensions.PluginInterface): + @abc.abstractmethod + def f(self): + pass + + self.assertTrue(issubclass(A, B)) + + def test_issubclass_hook_class_without_abstract_methods(self): + class A(object): + def f(self): + pass + + class B(extensions.PluginInterface): + def f(self): + pass + + self.assertFalse(issubclass(A, B)) + + def test_issubclass_hook_not_all_methods_implemented(self): + class A(object): + def f(self): + pass + + class B(extensions.PluginInterface): + @abc.abstractmethod + def f(self): + pass + + @abc.abstractmethod + def g(self): + pass + + self.assertFalse(issubclass(A, B)) + + class ResourceExtensionTest(base.BaseTestCase): class ResourceExtensionController(wsgi.Controller):