diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py old mode 100644 new mode 100755 index ba60115a71..6dbedd12a1 --- a/neutron/plugins/ml2/plugin.py +++ b/neutron/plugins/ml2/plugin.py @@ -51,6 +51,7 @@ from neutron.extensions import portbindings from neutron.extensions import providernet as provider from neutron import manager from neutron.openstack.common import excutils +from neutron.openstack.common.gettextutils import _LI from neutron.openstack.common import importutils from neutron.openstack.common import jsonutils from neutron.openstack.common import lockutils @@ -948,6 +949,11 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, cur_binding = db.get_dvr_port_binding_by_host(session, port_id, host) + if not cur_binding: + LOG.info(_LI("Binding info for port %s was not found, " + "it might have been deleted already."), + port_id) + return # Commit our binding results only if port has not been # successfully bound concurrently by another thread or # process and no binding inputs have been changed. @@ -1055,6 +1061,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, filter(models_v2.Port.id.startswith(port_id)). one()) except sa_exc.NoResultFound: + LOG.debug("No ports have port_id starting with %s", + port_id) return except exc.MultipleResultsFound: LOG.error(_("Multiple ports have port_id starting with %s"), @@ -1072,8 +1080,18 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, port_context = driver_context.DvrPortContext( self, plugin_context, port, network, binding) else: + # since eager loads are disabled in port_db query + # related attribute port_binding could disappear in + # concurrent port deletion. + # It's not an error condition. + binding = port_db.port_binding + if not binding: + LOG.info(_LI("Binding info for port %s was not found, " + "it might have been deleted already."), + port_id) + return port_context = driver_context.PortContext( - self, plugin_context, port, network, port_db.port_binding) + self, plugin_context, port, network, binding) return self._bind_port_if_needed(port_context) diff --git a/neutron/tests/unit/ml2/test_port_binding.py b/neutron/tests/unit/ml2/test_port_binding.py old mode 100644 new mode 100755 index 3f3c4b2053..b2da2ab977 --- a/neutron/tests/unit/ml2/test_port_binding.py +++ b/neutron/tests/unit/ml2/test_port_binding.py @@ -19,6 +19,7 @@ from neutron import context from neutron.extensions import portbindings from neutron import manager from neutron.plugins.ml2 import config as config +from neutron.plugins.ml2 import models as ml2_models from neutron.tests.unit import test_db_plugin as test_plugin @@ -98,6 +99,29 @@ class PortBindingTestCase(test_plugin.NeutronDbPluginV2TestCase): portbindings.VIF_TYPE_OVS, True, True, 'ACTIVE') + def test_update_port_binding_no_binding(self): + ctx = context.get_admin_context() + with self.port(name='name') as port: + # emulating concurrent binding deletion + (ctx.session.query(ml2_models.PortBinding). + filter_by(port_id=port['port']['id']).delete()) + self.assertIsNone( + self.plugin.get_bound_port_context(ctx, port['port']['id'])) + + def test_commit_dvr_port_binding(self): + ctx = context.get_admin_context() + + class MechContext(object): + pass + + mctx = MechContext() + mctx._binding = None + # making a shortcut: calling private method directly to + # avoid bothering with "concurrent" port binding deletion + res = self.plugin._commit_dvr_port_binding(ctx, 'anyUUID', + 'HostA', mctx) + self.assertIsNone(res) + def _test_update_port_binding(self, host, new_host=None): with mock.patch.object(self.plugin, '_notify_port_updated') as notify_mock: