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,
network['id'])
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()
ext_subnets_dict = {s['id']: s for s in network['subnets']}
for id in router_ids:
router = l3plugin.get_router(ctx_admin, id)
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']
@ -601,15 +610,15 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
# 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
return
if num_fips == 1 and netaddr.IPAddress(
fips[0]['ip_address']).version == subnet['ip_version']:
continue
return
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)
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}}
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

View File

@ -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'}}

View File

@ -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: