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:
parent
41d2c23560
commit
9e006cbe64
|
@ -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
|
||||
|
|
|
@ -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'}}
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue