Capture NotFound Exceptions in subnet postcommit

Change I28323eddcf34fd12002cc706f4a9bcbb3f976ceb introduced
a subtle difference in the types of exceptions that can be
encountered during the operations after creating a subnet.

Before that change, a giant enclosing transaction would provide
REPEATABLE READ guarantees, so once a port was looked up by
IPAM, calling update_port on that port afterwards in the same
transaction would not result in a PortNotFound exception.
The same applied to looking up a router and then updating its
gateway port.

Now that there is no giant enclosing transaction, we have to
capture the possible PortNotFound and RouterNotFound exceptions
that can occur during these post commit subnet create operations.

Closes-Bug: #1644502
Change-Id: I29e2bf7bcda37ebbcee193f94c8b03df90f24ef1
This commit is contained in:
Kevin Benton 2016-11-24 04:17:15 -08:00
parent 41d2c23560
commit 9e006cbe64
3 changed files with 62 additions and 23 deletions

View File

@ -588,10 +588,19 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
gw_ports = self._get_router_gw_ports_by_network(context, gw_ports = self._get_router_gw_ports_by_network(context,
network['id']) network['id'])
router_ids = [p['device_id'] for p in gw_ports] router_ids = [p['device_id'] for p in gw_ports]
for id in router_ids:
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() ctx_admin = context.elevated()
ext_subnets_dict = {s['id']: s for s in network['subnets']} ext_subnets_dict = {s['id']: s for s in network['subnets']}
for id in router_ids: router = l3plugin.get_router(ctx_admin, router_id)
router = l3plugin.get_router(ctx_admin, id)
external_gateway_info = router['external_gateway_info'] external_gateway_info = router['external_gateway_info']
# Get all stateful (i.e. non-SLAAC/DHCPv6-stateless) fixed ips # Get all stateful (i.e. non-SLAAC/DHCPv6-stateless) fixed ips
fips = [f for f in external_gateway_info['external_fixed_ips'] fips = [f for f in external_gateway_info['external_fixed_ips']
@ -601,15 +610,15 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
# Don't add the fixed IP to the port if it already # Don't add the fixed IP to the port if it already
# has a stateful fixed IP of the same IP version # has a stateful fixed IP of the same IP version
if num_fips > 1: if num_fips > 1:
continue return
if num_fips == 1 and netaddr.IPAddress( if num_fips == 1 and netaddr.IPAddress(
fips[0]['ip_address']).version == subnet['ip_version']: fips[0]['ip_address']).version == subnet['ip_version']:
continue return
external_gateway_info['external_fixed_ips'].append( external_gateway_info['external_fixed_ips'].append(
{'subnet_id': subnet['id']}) {'subnet_id': subnet['id']})
info = {'router': {'external_gateway_info': info = {'router': {'external_gateway_info':
external_gateway_info}} external_gateway_info}}
l3plugin.update_router(context, id, info) l3plugin.update_router(context, router_id, info)
@db_api.retry_if_session_inactive() @db_api.retry_if_session_inactive()
def _create_subnet_postcommit(self, context, result, network, ipam_subnet): def _create_subnet_postcommit(self, context, result, network, ipam_subnet):
@ -624,7 +633,12 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
result, ipam_subnet) result, ipam_subnet)
for port_id in updated_ports: for port_id in updated_ports:
port_info = {'port': {'id': port_id}} port_info = {'port': {'id': port_id}}
try:
self.update_port(context, port_id, port_info) 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): def _get_subnetpool_id(self, context, subnet):
"""Return the subnetpool id for this request """Return the subnetpool id for this request

View File

@ -4226,7 +4226,7 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
def _test_create_subnet_ipv6_auto_addr_with_port_on_network( def _test_create_subnet_ipv6_auto_addr_with_port_on_network(
self, addr_mode, device_owner=DEVICE_OWNER_COMPUTE, 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 # Create a network with one IPv4 subnet and one port
with self.network() as network,\ with self.network() as network,\
self.subnet(network=network) as v4_subnet,\ self.subnet(network=network) as v4_subnet,\
@ -4254,6 +4254,9 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
# Add an IPv6 auto-address subnet to the network # Add an IPv6 auto-address subnet to the network
with mock.patch.object(directory.get_plugin(), with mock.patch.object(directory.get_plugin(),
'update_port') as mock_updated_port: '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', v6_subnet = self._make_subnet(self.fmt, network, 'fe80::1',
'fe80::/64', ip_version=6, 'fe80::/64', ip_version=6,
ipv6_ra_mode=addr_mode, ipv6_ra_mode=addr_mode,
@ -4306,6 +4309,10 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
self._test_create_subnet_ipv6_auto_addr_with_port_on_network( self._test_create_subnet_ipv6_auto_addr_with_port_on_network(
constants.IPV6_SLAAC, insert_db_reference_error=True) 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): def test_update_subnet_no_gateway(self):
with self.subnet() as subnet: with self.subnet() as subnet:
data = {'subnet': {'gateway_ip': '10.0.0.1'}} data = {'subnet': {'gateway_ip': '10.0.0.1'}}

View File

@ -871,6 +871,24 @@ class L3NatTestCaseBase(L3NatTestCaseMixin):
self.assertNotEqual(fip1['ip_address'], self.assertNotEqual(fip1['ip_address'],
fip2['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): def test_router_update_gateway_upon_subnet_create_ipv6(self):
with self.network() as n: with self.network() as n:
with self.subnet(network=n) as s1, self.router() as r: with self.subnet(network=n) as s1, self.router() as r: