Auto-remove floating agent gw ports on net/subnet delete

fip agent gw ports may be left in DB after router removal due to
race condition between l3 agent and server: when server processes
"router delete" - l3 agent is still processing "router add" and creates
fip agent gw port after server already removed the router.

The patch also adds handling of external network delete event
to cleanup fip namespaces left on agents due to same race condition.

Change-Id: Ib2f3aca08946e584156d092c37e1ea5ed5ca81a6
Closes-Bug: #1902998
This commit is contained in:
Oleg Bondarev 2020-11-05 10:41:15 +04:00
parent 28c79c9747
commit b97a8eb488
3 changed files with 22 additions and 3 deletions

View File

@ -76,4 +76,5 @@ IDPOOL_SELECT_SIZE = 100
# with these owners, it will allow subnet deletion to proceed with the
# IP allocations being cleaned up by cascade.
AUTO_DELETE_PORT_OWNERS = [constants.DEVICE_OWNER_DHCP,
constants.DEVICE_OWNER_DISTRIBUTED]
constants.DEVICE_OWNER_DISTRIBUTED,
constants.DEVICE_OWNER_AGENT_GW]

View File

@ -13,6 +13,7 @@
# under the License.
import collections
from neutron_lib.api.definitions import external_net as extnet_apidef
from neutron_lib.api.definitions import l3 as l3_apidef
from neutron_lib.api.definitions import portbindings
from neutron_lib.api.definitions import portbindings_extended
@ -409,6 +410,15 @@ class DVRResourceOperationHandler(object):
if host_id:
return
@registry.receives(resources.NETWORK, [events.AFTER_DELETE])
def delete_fip_namespaces_for_ext_net(self, rtype, event, trigger,
context, network, **kwargs):
if network.get(extnet_apidef.EXTERNAL):
# Send the information to all the L3 Agent hosts
# to clean up the fip namespace as it is no longer required.
self.l3plugin.l3_rpc_notifier.delete_fipnamespace_for_ext_net(
context, network['id'])
def _get_ports_for_allowed_address_pair_ip(self, context, network_id,
fixed_ip):
"""Return all active ports associated with the allowed_addr_pair ip."""

View File

@ -1611,17 +1611,25 @@ fixed_ips=ip_address%%3D%s&fixed_ips=ip_address%%3D%s&fixed_ips=subnet_id%%3D%s
res = req.get_response(self.api)
self.assertEqual(webob.exc.HTTPConflict.code, res.status_int)
def test_delete_network_port_exists_owned_by_network(self):
def _test_delete_network_port_exists_owned_by_network(self, device_owner):
res = self._create_network(fmt=self.fmt, name='net',
admin_state_up=True)
network = self.deserialize(self.fmt, res)
network_id = network['network']['id']
self._create_port(self.fmt, network_id,
device_owner=constants.DEVICE_OWNER_DHCP)
device_owner=device_owner)
req = self.new_delete_request('networks', network_id)
res = req.get_response(self.api)
self.assertEqual(webob.exc.HTTPNoContent.code, res.status_int)
def test_test_delete_network_port_exists_dhcp(self):
self._test_delete_network_port_exists_owned_by_network(
constants.DEVICE_OWNER_DHCP)
def test_test_delete_network_port_exists_fip_gw(self):
self._test_delete_network_port_exists_owned_by_network(
constants.DEVICE_OWNER_AGENT_GW)
def test_delete_network_port_exists_owned_by_network_race(self):
res = self._create_network(fmt=self.fmt, name='net',
admin_state_up=True)