From 8398ec0d771dd8e4944034ec2e658c747671de3e Mon Sep 17 00:00:00 2001 From: Hong Hui Xiao Date: Sat, 20 Aug 2016 09:06:10 -0400 Subject: [PATCH] Add mechanism driver error details to MechanismDriverError Now the ML2 core plugin maps driver errors to MechanismDriverError and hides the error details from the caller. This patch change MechanismDriverError from an instance of NeutronException to an instance of MultipleExceptions. Add add exceptions from mechanism driver as inner_exceptions of MultipleExceptions. As a result, the api layer will unwrap the MechanismDriverError and return the real error to client. Change-Id: I3a46932848d59f7f027640bfb598650f064b0a12 Closes-bug: #1273730 --- neutron/plugins/ml2/common/exceptions.py | 11 +++- .../plugins/ml2/drivers/l2pop/mech_driver.py | 10 ++-- neutron/plugins/ml2/managers.py | 9 ++-- .../ml2/drivers/l2pop/test_mech_driver.py | 4 +- neutron/tests/unit/plugins/ml2/test_plugin.py | 50 +++++++++++++------ 5 files changed, 56 insertions(+), 28 deletions(-) diff --git a/neutron/plugins/ml2/common/exceptions.py b/neutron/plugins/ml2/common/exceptions.py index 019b902883b..ff192ecbae3 100644 --- a/neutron/plugins/ml2/common/exceptions.py +++ b/neutron/plugins/ml2/common/exceptions.py @@ -18,11 +18,18 @@ from neutron_lib import exceptions from neutron._i18n import _ +from neutron.common import exceptions as n_exc -class MechanismDriverError(exceptions.NeutronException): +class MechanismDriverError(n_exc.MultipleExceptions): """Mechanism driver call failed.""" - message = _("%(method)s failed.") + + def __init__(self, method, errors=None): + # The message is not used by api, because api will unwrap + # MultipleExceptions and return inner exceptions. Keep it + # for backward-compatibility, in case other code use it. + self.message = _("%s failed.") % method + super(MechanismDriverError, self).__init__(errors) class ExtensionDriverError(exceptions.InvalidInput): diff --git a/neutron/plugins/ml2/drivers/l2pop/mech_driver.py b/neutron/plugins/ml2/drivers/l2pop/mech_driver.py index 77642a84723..7d9d5dd3a9b 100644 --- a/neutron/plugins/ml2/drivers/l2pop/mech_driver.py +++ b/neutron/plugins/ml2/drivers/l2pop/mech_driver.py @@ -14,13 +14,13 @@ # under the License. from neutron_lib import constants as const +from neutron_lib import exceptions from oslo_config import cfg from oslo_log import log as logging -from neutron._i18n import _LW +from neutron._i18n import _, _LW from neutron import context as n_context from neutron.db import api as db_api -from neutron.plugins.ml2.common import exceptions as ml2_exc from neutron.plugins.ml2 import driver_api as api from neutron.plugins.ml2.drivers.l2pop import config # noqa from neutron.plugins.ml2.drivers.l2pop import db as l2pop_db @@ -114,9 +114,9 @@ class L2populationMechanismDriver(api.MechanismDriver): if (orig['mac_address'] != port['mac_address'] and context.status == const.PORT_STATUS_ACTIVE): - LOG.warning(_LW("unable to modify mac_address of ACTIVE port " - "%s"), port['id']) - raise ml2_exc.MechanismDriverError(method='update_port_precommit') + msg = _("unable to modify mac_address of ACTIVE port " + "%s") % port['id'] + raise exceptions.InvalidInput(error_message=msg) def update_port_postcommit(self, context): port = context.current diff --git a/neutron/plugins/ml2/managers.py b/neutron/plugins/ml2/managers.py index d24fb319fef..8a0527a833a 100644 --- a/neutron/plugins/ml2/managers.py +++ b/neutron/plugins/ml2/managers.py @@ -404,7 +404,7 @@ class MechanismManager(stevedore.named.NamedExtensionManager): raise_db_retriable=False. See neutron.db.api.is_retriable for what db exception is retriable """ - error = False + errors = [] for driver in self.ordered_mech_drivers: try: getattr(driver.obj, method_name)(context) @@ -419,12 +419,13 @@ class MechanismManager(stevedore.named.NamedExtensionManager): _LE("Mechanism driver '%(name)s' failed in %(method)s"), {'name': driver.name, 'method': method_name} ) - error = True + errors.append(e) if not continue_on_failure: break - if error: + if errors: raise ml2_exc.MechanismDriverError( - method=method_name + method=method_name, + errors=errors ) def create_network_precommit(self, context): diff --git a/neutron/tests/unit/plugins/ml2/drivers/l2pop/test_mech_driver.py b/neutron/tests/unit/plugins/ml2/drivers/l2pop/test_mech_driver.py index 34db286e104..c462f9c637f 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/l2pop/test_mech_driver.py +++ b/neutron/tests/unit/plugins/ml2/drivers/l2pop/test_mech_driver.py @@ -15,6 +15,7 @@ import mock from neutron_lib import constants +from neutron_lib import exceptions from oslo_serialization import jsonutils import testtools @@ -24,7 +25,6 @@ from neutron import context from neutron.extensions import portbindings from neutron.extensions import providernet as pnet from neutron import manager -from neutron.plugins.ml2.common import exceptions as ml2_exc from neutron.plugins.ml2 import driver_context from neutron.plugins.ml2.drivers.l2pop import db as l2pop_db from neutron.plugins.ml2.drivers.l2pop import mech_driver as l2pop_mech_driver @@ -1022,5 +1022,5 @@ class TestL2PopulationMechDriver(base.BaseTestCase): original_port=original_port) mech_driver = l2pop_mech_driver.L2populationMechanismDriver() - with testtools.ExpectedException(ml2_exc.MechanismDriverError): + with testtools.ExpectedException(exceptions.InvalidInput): mech_driver.update_port_precommit(ctx) diff --git a/neutron/tests/unit/plugins/ml2/test_plugin.py b/neutron/tests/unit/plugins/ml2/test_plugin.py index c4c60554a92..e3ad65a0b97 100644 --- a/neutron/tests/unit/plugins/ml2/test_plugin.py +++ b/neutron/tests/unit/plugins/ml2/test_plugin.py @@ -1991,18 +1991,22 @@ class TestFaultyMechansimDriver(Ml2PluginV2FaultyDriverTestCase): def test_create_network_faulty(self): + err_msg = "Some errors" with mock.patch.object(mech_test.TestMechanismDriver, 'create_network_postcommit', - side_effect=ml2_exc.MechanismDriverError): + side_effect=(exc.InvalidInput( + error_message=err_msg))): 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(500, res.status_int) + self.assertEqual(400, res.status_int) error = self.deserialize(self.fmt, res) - self.assertEqual('MechanismDriverError', + self.assertEqual('InvalidInput', error['NeutronError']['type']) + # Check the client can see the root cause of error. + self.assertIn(err_msg, error['NeutronError']['message']) query_params = "tenant_id=%s" % tenant_id nets = self._list('networks', query_params=query_params) self.assertFalse(nets['networks']) @@ -2032,9 +2036,11 @@ class TestFaultyMechansimDriver(Ml2PluginV2FaultyDriverTestCase): def test_update_network_faulty(self): + err_msg = "Some errors" with mock.patch.object(mech_test.TestMechanismDriver, 'update_network_postcommit', - side_effect=ml2_exc.MechanismDriverError): + side_effect=(exc.InvalidInput( + error_message=err_msg))): with mock.patch.object(mech_logger.LoggerMechanismDriver, 'update_network_postcommit') as unp: @@ -2050,10 +2056,12 @@ class TestFaultyMechansimDriver(Ml2PluginV2FaultyDriverTestCase): data = {'network': {'name': new_name}} req = self.new_update_request('networks', data, net_id) res = req.get_response(self.api) - self.assertEqual(500, res.status_int) + self.assertEqual(400, res.status_int) error = self.deserialize(self.fmt, res) - self.assertEqual('MechanismDriverError', + self.assertEqual('InvalidInput', error['NeutronError']['type']) + # Check the client can see the root cause of error. + self.assertIn(err_msg, error['NeutronError']['message']) # Test if other mechanism driver was called self.assertTrue(unp.called) net = self._show('networks', net_id) @@ -2063,9 +2071,11 @@ class TestFaultyMechansimDriver(Ml2PluginV2FaultyDriverTestCase): def test_create_subnet_faulty(self): + err_msg = "Some errors" with mock.patch.object(mech_test.TestMechanismDriver, 'create_subnet_postcommit', - side_effect=ml2_exc.MechanismDriverError): + side_effect=(exc.InvalidInput( + error_message=err_msg))): with self.network() as network: net_id = network['network']['id'] @@ -2078,10 +2088,12 @@ class TestFaultyMechansimDriver(Ml2PluginV2FaultyDriverTestCase): 'gateway_ip': '10.0.20.1'}} req = self.new_create_request('subnets', data) res = req.get_response(self.api) - self.assertEqual(500, res.status_int) + self.assertEqual(400, res.status_int) error = self.deserialize(self.fmt, res) - self.assertEqual('MechanismDriverError', + self.assertEqual('InvalidInput', error['NeutronError']['type']) + # Check the client can see the root cause of error. + self.assertIn(err_msg, error['NeutronError']['message']) query_params = "network_id=%s" % net_id subnets = self._list('subnets', query_params=query_params) self.assertFalse(subnets['subnets']) @@ -2119,9 +2131,11 @@ class TestFaultyMechansimDriver(Ml2PluginV2FaultyDriverTestCase): def test_update_subnet_faulty(self): + err_msg = "Some errors" with mock.patch.object(mech_test.TestMechanismDriver, 'update_subnet_postcommit', - side_effect=ml2_exc.MechanismDriverError): + side_effect=(exc.InvalidInput( + error_message=err_msg))): with mock.patch.object(mech_logger.LoggerMechanismDriver, 'update_subnet_postcommit') as usp: @@ -2143,10 +2157,12 @@ class TestFaultyMechansimDriver(Ml2PluginV2FaultyDriverTestCase): data = {'subnet': {'name': new_name}} req = self.new_update_request('subnets', data, subnet_id) res = req.get_response(self.api) - self.assertEqual(500, res.status_int) + self.assertEqual(400, res.status_int) error = self.deserialize(self.fmt, res) - self.assertEqual('MechanismDriverError', + self.assertEqual('InvalidInput', error['NeutronError']['type']) + # Check the client can see the root cause of error. + self.assertIn(err_msg, error['NeutronError']['message']) # Test if other mechanism driver was called self.assertTrue(usp.called) subnet = self._show('subnets', subnet_id) @@ -2156,9 +2172,11 @@ class TestFaultyMechansimDriver(Ml2PluginV2FaultyDriverTestCase): def test_create_port_faulty(self): + err_msg = "Some errors" with mock.patch.object(mech_test.TestMechanismDriver, 'create_port_postcommit', - side_effect=ml2_exc.MechanismDriverError): + side_effect=(exc.InvalidInput( + error_message=err_msg))): with self.network() as network: net_id = network['network']['id'] @@ -2170,10 +2188,12 @@ class TestFaultyMechansimDriver(Ml2PluginV2FaultyDriverTestCase): 'fixed_ips': []}} req = self.new_create_request('ports', data) res = req.get_response(self.api) - self.assertEqual(500, res.status_int) + self.assertEqual(400, res.status_int) error = self.deserialize(self.fmt, res) - self.assertEqual('MechanismDriverError', + self.assertEqual('InvalidInput', error['NeutronError']['type']) + # Check the client can see the root cause of error. + self.assertIn(err_msg, error['NeutronError']['message']) query_params = "network_id=%s" % net_id ports = self._list('ports', query_params=query_params) self.assertFalse(ports['ports'])