diff --git a/neutron/plugins/ml2/common/exceptions.py b/neutron/plugins/ml2/common/exceptions.py index ed94b1e1f14..166711d8ee9 100644 --- a/neutron/plugins/ml2/common/exceptions.py +++ b/neutron/plugins/ml2/common/exceptions.py @@ -21,3 +21,8 @@ from neutron.common import exceptions class MechanismDriverError(exceptions.NeutronException): """Mechanism driver call failed.""" message = _("%(method)s failed.") + + +class ExtensionDriverError(exceptions.InvalidInput): + """Extension driver call failed.""" + message = _("Extension %(driver)s failed.") diff --git a/neutron/plugins/ml2/managers.py b/neutron/plugins/ml2/managers.py index ef5b868cfc6..4f678b2265a 100644 --- a/neutron/plugins/ml2/managers.py +++ b/neutron/plugins/ml2/managers.py @@ -15,6 +15,7 @@ from oslo_config import cfg from oslo_log import log +from oslo_utils import excutils import six import stevedore @@ -764,10 +765,10 @@ class ExtensionManager(stevedore.named.NamedExtensionManager): try: getattr(driver.obj, method_name)(plugin_context, data, result) except Exception: - LOG.exception( - _LE("Extension driver '%(name)s' failed in %(method)s"), - {'name': driver.name, 'method': method_name} - ) + with excutils.save_and_reraise_exception(): + LOG.info(_LI("Extension driver '%(name)s' failed in " + "%(method)s"), + {'name': driver.name, 'method': method_name}) def process_create_network(self, plugin_context, data, result): """Notify all extension drivers during network creation.""" @@ -799,23 +800,30 @@ class ExtensionManager(stevedore.named.NamedExtensionManager): self._call_on_ext_drivers("process_update_port", plugin_context, data, result) + def _call_on_dict_driver(self, method_name, session, base_model, result): + for driver in self.ordered_ext_drivers: + try: + getattr(driver.obj, method_name)(session, base_model, result) + except Exception: + LOG.error(_LE("Extension driver '%(name)s' failed in " + "%(method)s"), + {'name': driver.name, 'method': method_name}) + raise ml2_exc.ExtensionDriverError(driver=driver.name) + + LOG.debug("%(method)s succeeded for driver %(driver)s", + {'method': method_name, 'driver': driver.name}) + def extend_network_dict(self, session, base_model, result): """Notify all extension drivers to extend network dictionary.""" - for driver in self.ordered_ext_drivers: - driver.obj.extend_network_dict(session, base_model, result) - LOG.debug("Extended network dict for driver '%(drv)s'", - {'drv': driver.name}) + self._call_on_dict_driver("extend_network_dict", session, base_model, + result) def extend_subnet_dict(self, session, base_model, result): """Notify all extension drivers to extend subnet dictionary.""" - for driver in self.ordered_ext_drivers: - driver.obj.extend_subnet_dict(session, base_model, result) - LOG.debug("Extended subnet dict for driver '%(drv)s'", - {'drv': driver.name}) + self._call_on_dict_driver("extend_subnet_dict", session, base_model, + result) def extend_port_dict(self, session, base_model, result): """Notify all extension drivers to extend port dictionary.""" - for driver in self.ordered_ext_drivers: - driver.obj.extend_port_dict(session, base_model, result) - LOG.debug("Extended port dict for driver '%(drv)s'", - {'drv': driver.name}) + self._call_on_dict_driver("extend_port_dict", session, base_model, + result) diff --git a/neutron/tests/unit/plugins/ml2/test_extension_driver_api.py b/neutron/tests/unit/plugins/ml2/test_extension_driver_api.py index bff70fecb58..78d63533858 100644 --- a/neutron/tests/unit/plugins/ml2/test_extension_driver_api.py +++ b/neutron/tests/unit/plugins/ml2/test_extension_driver_api.py @@ -11,6 +11,7 @@ # under the License. import mock +import uuid from neutron import context from neutron import manager @@ -31,6 +32,58 @@ class ExtensionDriverTestCase(test_plugin.Ml2PluginV2TestCase): self._plugin = manager.NeutronManager.get_plugin() self._ctxt = context.get_admin_context() + def _verify_network_create(self, code, exc_reason): + tenant_id = str(uuid.uuid4()) + data = {'network': {'name': 'net1', + 'tenant_id': tenant_id}} + req = self.new_create_request('networks', data) + res = req.get_response(self.api) + self.assertEqual(code, res.status_int) + + network = self.deserialize(self.fmt, res) + if exc_reason: + self.assertEqual(exc_reason, + network['NeutronError']['type']) + + return (network, tenant_id) + + def _verify_network_update(self, network, code, exc_reason): + net_id = network['network']['id'] + new_name = 'a_brand_new_name' + data = {'network': {'name': new_name}} + req = self.new_update_request('networks', data, net_id) + res = req.get_response(self.api) + self.assertEqual(code, res.status_int) + error = self.deserialize(self.fmt, res) + self.assertEqual(exc_reason, + error['NeutronError']['type']) + + def test_faulty_process_create(self): + with mock.patch.object(ext_test.TestExtensionDriver, + 'process_create_network', + side_effect=TypeError): + net, tenant_id = self._verify_network_create(500, + 'HTTPInternalServerError') + # Verify the operation is rolled back + query_params = "tenant_id=%s" % tenant_id + nets = self._list('networks', query_params=query_params) + self.assertFalse(nets['networks']) + + def test_faulty_process_update(self): + with mock.patch.object(ext_test.TestExtensionDriver, + 'process_update_network', + side_effect=TypeError): + network, tid = self._verify_network_create(201, None) + self._verify_network_update(network, 500, + 'HTTPInternalServerError') + + def test_faulty_extend_dict(self): + with mock.patch.object(ext_test.TestExtensionDriver, + 'extend_network_dict', + side_effect=TypeError): + network, tid = self._verify_network_create(201, None) + self._verify_network_update(network, 400, 'ExtensionDriverError') + def test_network_attr(self): with self.network() as network: # Test create network