diff --git a/neutron/db/db_base_plugin_v2.py b/neutron/db/db_base_plugin_v2.py index 264b661eb73..6f7e82d0712 100644 --- a/neutron/db/db_base_plugin_v2.py +++ b/neutron/db/db_base_plugin_v2.py @@ -588,28 +588,37 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon, gw_ports = self._get_router_gw_ports_by_network(context, network['id']) router_ids = [p['device_id'] for p in gw_ports] - ctx_admin = context.elevated() - ext_subnets_dict = {s['id']: s for s in network['subnets']} for id in router_ids: - router = l3plugin.get_router(ctx_admin, id) - external_gateway_info = router['external_gateway_info'] - # Get all stateful (i.e. non-SLAAC/DHCPv6-stateless) fixed ips - fips = [f for f in external_gateway_info['external_fixed_ips'] - if not ipv6_utils.is_auto_address_subnet( - ext_subnets_dict[f['subnet_id']])] - num_fips = len(fips) - # Don't add the fixed IP to the port if it already - # has a stateful fixed IP of the same IP version - if num_fips > 1: - continue - if num_fips == 1 and netaddr.IPAddress( - fips[0]['ip_address']).version == subnet['ip_version']: - continue - external_gateway_info['external_fixed_ips'].append( - {'subnet_id': subnet['id']}) - info = {'router': {'external_gateway_info': - external_gateway_info}} - l3plugin.update_router(context, id, info) + try: + self._update_router_gw_port(context, id, network, subnet) + except l3.RouterNotFound: + LOG.debug("Router %(id)s was concurrently deleted while " + "updating GW port for subnet %(s)s", + {'id': id, 's': subnet}) + + def _update_router_gw_port(self, context, router_id, network, subnet): + l3plugin = directory.get_plugin(constants.L3) + ctx_admin = context.elevated() + ext_subnets_dict = {s['id']: s for s in network['subnets']} + router = l3plugin.get_router(ctx_admin, router_id) + external_gateway_info = router['external_gateway_info'] + # Get all stateful (i.e. non-SLAAC/DHCPv6-stateless) fixed ips + fips = [f for f in external_gateway_info['external_fixed_ips'] + if not ipv6_utils.is_auto_address_subnet( + ext_subnets_dict[f['subnet_id']])] + num_fips = len(fips) + # Don't add the fixed IP to the port if it already + # has a stateful fixed IP of the same IP version + if num_fips > 1: + return + if num_fips == 1 and netaddr.IPAddress( + fips[0]['ip_address']).version == subnet['ip_version']: + return + external_gateway_info['external_fixed_ips'].append( + {'subnet_id': subnet['id']}) + info = {'router': {'external_gateway_info': + external_gateway_info}} + l3plugin.update_router(context, router_id, info) @db_api.retry_if_session_inactive() def _create_subnet_postcommit(self, context, result, network, ipam_subnet): @@ -624,7 +633,12 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon, result, ipam_subnet) for port_id in updated_ports: port_info = {'port': {'id': port_id}} - self.update_port(context, port_id, port_info) + try: + self.update_port(context, port_id, port_info) + except exc.PortNotFound: + LOG.debug("Port %(p)s concurrently deleted while adding " + "address for new subnet %(s)s.", {'p': port_id, + 's': result}) def _get_subnetpool_id(self, context, subnet): """Return the subnetpool id for this request diff --git a/neutron/tests/unit/db/test_db_base_plugin_v2.py b/neutron/tests/unit/db/test_db_base_plugin_v2.py index 5e164a3cf1b..000e18be984 100644 --- a/neutron/tests/unit/db/test_db_base_plugin_v2.py +++ b/neutron/tests/unit/db/test_db_base_plugin_v2.py @@ -4226,7 +4226,7 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase): def _test_create_subnet_ipv6_auto_addr_with_port_on_network( self, addr_mode, device_owner=DEVICE_OWNER_COMPUTE, - insert_db_reference_error=False): + insert_db_reference_error=False, insert_port_not_found=False): # Create a network with one IPv4 subnet and one port with self.network() as network,\ self.subnet(network=network) as v4_subnet,\ @@ -4254,6 +4254,9 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase): # Add an IPv6 auto-address subnet to the network with mock.patch.object(directory.get_plugin(), 'update_port') as mock_updated_port: + if insert_port_not_found: + mock_updated_port.side_effect = lib_exc.PortNotFound( + port_id=port['port']['id']) v6_subnet = self._make_subnet(self.fmt, network, 'fe80::1', 'fe80::/64', ip_version=6, ipv6_ra_mode=addr_mode, @@ -4306,6 +4309,10 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase): self._test_create_subnet_ipv6_auto_addr_with_port_on_network( constants.IPV6_SLAAC, insert_db_reference_error=True) + def test_create_subnet_ipv6_slaac_with_port_not_found(self): + self._test_create_subnet_ipv6_auto_addr_with_port_on_network( + constants.IPV6_SLAAC, insert_port_not_found=True) + def test_update_subnet_no_gateway(self): with self.subnet() as subnet: data = {'subnet': {'gateway_ip': '10.0.0.1'}} diff --git a/neutron/tests/unit/extensions/test_l3.py b/neutron/tests/unit/extensions/test_l3.py index eb6480fd1b0..7463acfab4f 100644 --- a/neutron/tests/unit/extensions/test_l3.py +++ b/neutron/tests/unit/extensions/test_l3.py @@ -871,6 +871,24 @@ class L3NatTestCaseBase(L3NatTestCaseMixin): self.assertNotEqual(fip1['ip_address'], fip2['ip_address']) + def test_router_concurrent_delete_upon_subnet_create(self): + with self.network() as n: + with self.subnet(network=n) as s1, self.router() as r: + self._set_net_external(n['network']['id']) + self._add_external_gateway_to_router( + r['router']['id'], + n['network']['id'], + ext_ips=[{'subnet_id': s1['subnet']['id']}]) + plugin = directory.get_plugin(lib_constants.L3) + mock.patch.object( + plugin, 'update_router', + side_effect=l3.RouterNotFound(router_id='1')).start() + # ensure the router disappearing doesn't interfere with subnet + # creation + self._create_subnet(self.fmt, net_id=n['network']['id'], + ip_version=6, cidr='2001:db8::/32', + expected_res_status=(exc.HTTPCreated.code)) + def test_router_update_gateway_upon_subnet_create_ipv6(self): with self.network() as n: with self.subnet(network=n) as s1, self.router() as r: