diff --git a/neutron/db/l3_dvr_db.py b/neutron/db/l3_dvr_db.py index 7845f9712f..e051255462 100644 --- a/neutron/db/l3_dvr_db.py +++ b/neutron/db/l3_dvr_db.py @@ -576,6 +576,7 @@ class L3_NAT_with_dvr_db_mixin(l3_db.L3_NAT_db_mixin, ports = ( rp.port.id for rp in router.attached_ports.filter_by(port_type=DEVICE_OWNER_DVR_SNAT) + if rp.port ) c_snat_ports = self._core_plugin.get_ports( diff --git a/neutron/tests/unit/ml2/test_ml2_plugin.py b/neutron/tests/unit/ml2/test_ml2_plugin.py index d1729194c7..4bf3115981 100644 --- a/neutron/tests/unit/ml2/test_ml2_plugin.py +++ b/neutron/tests/unit/ml2/test_ml2_plugin.py @@ -24,6 +24,7 @@ from neutron.common import exceptions as exc from neutron.common import utils from neutron import context from neutron.db import db_base_plugin_v2 as base_plugin +from neutron.db import l3_db from neutron.extensions import external_net as external_net from neutron.extensions import l3agentscheduler from neutron.extensions import multiprovidernet as mpnet @@ -296,6 +297,38 @@ class TestMl2DvrPortsV2(TestMl2PortsV2): self._test_delete_dvr_serviced_port( device_owner=constants.DEVICE_OWNER_LOADBALANCER) + def test_concurrent_csnat_port_delete(self): + plugin = manager.NeutronManager.get_service_plugins()[ + service_constants.L3_ROUTER_NAT] + r = plugin.create_router( + self.context, + {'router': {'name': 'router', 'admin_state_up': True}}) + with self.subnet() as s: + p = plugin.add_router_interface(self.context, r['id'], + {'subnet_id': s['subnet']['id']}) + + # lie to turn the port into an SNAT interface + with self.context.session.begin(): + rp = self.context.session.query(l3_db.RouterPort).filter_by( + port_id=p['port_id']).first() + rp.port_type = constants.DEVICE_OWNER_ROUTER_SNAT + + # take the port away before csnat gets a chance to delete it + # to simulate a concurrent delete + orig_get_ports = plugin._core_plugin.get_ports + + def get_ports_with_delete_first(*args, **kwargs): + plugin._core_plugin.delete_port(self.context, + p['port_id'], + l3_port_check=False) + return orig_get_ports(*args, **kwargs) + plugin._core_plugin.get_ports = get_ports_with_delete_first + + # This should be able to handle a concurrent delete without raising + # an exception + router = plugin._get_router(self.context, r['id']) + plugin.delete_csnat_router_interface_ports(self.context, router) + class TestMl2PortBinding(Ml2PluginV2TestCase, test_bindings.PortBindingsTestCase):