[DVR] Don't populate unbound ports in router's ARP cache

When user is using keepalived on their instances, he often creates
additional port in Neutron to allocate some IP address which will
be then used as VIP in keepalived and will be configured in
allowed_address_pair of other ports plugged to instances with
This is e.g. Octavia's use case.

This together with DVR caused problems with connectivity to such VIP
as it was populated in router's arp cache with MAC address from
Neutron db.

As this port isn't bound, it is only Neutron db entry so there is no
need to set it in arp cache of the router.
This patch is doing exactly that to filter such "unbound" and
"binding_failed" ports from the list.

Change-Id: Ia885ce00dbb5f2968859e8d0850bc511016f0846
Closes-Bug: #1869887
Slawek Kaplonski 3 years ago
parent 6d0b8890b9
commit eb775458c6
  1. 16
  2. 29

@ -15,6 +15,7 @@ import collections
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
from neutron_lib.api import validators
from neutron_lib.callbacks import events
from neutron_lib.callbacks import exceptions
@ -66,6 +67,18 @@ def is_admin_state_down_necessary():
# TODO(slaweq): this should be moved to neutron_lib.plugins.utils module
def is_port_bound(port):
active_binding = plugin_utils.get_port_binding_by_status_and_host(
port.get("port_bindings", []), const.ACTIVE)
if not active_binding:
LOG.warning("Binding for port %s was not found.", port)
return False
return active_binding[portbindings_extended.VIF_TYPE] not in [
class DVRResourceOperationHandler(object):
"""Contains callbacks for DVR operations.
@ -1204,10 +1217,11 @@ class L3_NAT_with_dvr_db_mixin(_DVRAgentInterfaceMixin,
def get_ports_under_dvr_connected_subnet(self, context, subnet_id):
query = dvr_mac_db.get_ports_query_by_subnet_and_ip(context, subnet_id)
ports = [p for p in query.all() if is_port_bound(p)]
return [
port, process_extensions=False)
for port in query.all()
for port in ports

@ -1282,6 +1282,35 @@ class L3DvrTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase):
self.mixin.is_router_distributed(self.ctx, router_id))
@mock.patch.object(l3_dvr_db, "is_port_bound")
def test_get_ports_under_dvr_connected_subnet(self, is_port_bound_mock):
router_dict = {'name': 'test_router', 'admin_state_up': True,
'distributed': True}
router = self._create_router(router_dict)
with self.network() as network,\
self.subnet(network=network) as subnet:
fake_bound_ports_ids = []
def fake_is_port_bound(port):
return port['id'] in fake_bound_ports_ids
is_port_bound_mock.side_effect = fake_is_port_bound
for _ in range(4):
port_res = self.create_port(
{'fixed_ips': [{'subnet_id': subnet['subnet']['id']}]})
port_id = self.deserialize(self.fmt, port_res)['port']['id']
if len(fake_bound_ports_ids) < 2:
self.mixin.add_router_interface(self.ctx, router['id'],
{'subnet_id': subnet['subnet']['id']})
dvr_subnet_ports = self.mixin.get_ports_under_dvr_connected_subnet(
self.ctx, subnet['subnet']['id'])
dvr_subnet_ports_ids = [p['id'] for p in dvr_subnet_ports]
self.assertItemsEqual(fake_bound_ports_ids, dvr_subnet_ports_ids)
@mock.patch.object(plugin_utils, 'can_port_be_bound_to_virtual_bridge',
def test__get_assoc_data_valid_vnic_type(self, *args):