diff --git a/neutron/agent/l3/dvr_local_router.py b/neutron/agent/l3/dvr_local_router.py index df61eec554c..2930c9f1882 100644 --- a/neutron/agent/l3/dvr_local_router.py +++ b/neutron/agent/l3/dvr_local_router.py @@ -318,6 +318,11 @@ class DvrLocalRouter(dvr_router_base.DvrRouterBase): p['mac_address'], subnet_id, 'add') + for allowed_address_pair in p.get('allowed_address_pairs', []): + self._update_arp_entry(allowed_address_pair['ip_address'], + allowed_address_pair['mac_address'], + subnet_id, + 'add') self._process_arp_cache_for_internal_port(subnet_id) @staticmethod diff --git a/neutron/db/allowedaddresspairs_db.py b/neutron/db/allowedaddresspairs_db.py index bf22b75d5da..437041f57e5 100644 --- a/neutron/db/allowedaddresspairs_db.py +++ b/neutron/db/allowedaddresspairs_db.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. # +import collections from neutron_lib.api.definitions import allowedaddresspairs as addr_apidef from neutron_lib.api.definitions import port as port_def @@ -65,6 +66,17 @@ class AllowedAddressPairsMixin(object): return [self._make_allowed_address_pairs_dict(pair.db_obj) for pair in pairs] + def get_allowed_address_pairs_for_ports(self, context, port_ids): + pairs = ( + obj_addr_pair.AllowedAddressPair. + get_allowed_address_pairs_for_ports( + context, port_ids=port_ids)) + result = collections.defaultdict(list) + for pair in pairs: + result[pair.port_id].append( + self._make_allowed_address_pairs_dict(pair.db_obj)) + return result + @staticmethod @resource_extend.extends([port_def.COLLECTION_NAME]) def _extend_port_dict_allowed_address_pairs(port_res, port_db): diff --git a/neutron/db/l3_dvr_db.py b/neutron/db/l3_dvr_db.py index 2307478242c..ea70d6270e3 100644 --- a/neutron/db/l3_dvr_db.py +++ b/neutron/db/l3_dvr_db.py @@ -1256,11 +1256,23 @@ 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 [ + # TODO(slaweq): if there would be way to pass to neutron-lib only + # list of extensions which actually should be processed, than setting + # process_extensions=True below could avoid that second loop and + # getting allowed_address_pairs for each of the ports + ports_list = [ self.l3plugin._core_plugin._make_port_dict( port, process_extensions=False) for port in ports ] + ports_ids = [p['id'] for p in ports_list] + allowed_address_pairs = ( + self._core_plugin.get_allowed_address_pairs_for_ports( + context, ports_ids)) + for port in ports_list: + port['allowed_address_pairs'] = allowed_address_pairs.get( + port['id'], []) + return ports_list def is_distributed_router(router): diff --git a/neutron/objects/port/extensions/allowedaddresspairs.py b/neutron/objects/port/extensions/allowedaddresspairs.py index a15cacaecdc..68e892cf7ee 100644 --- a/neutron/objects/port/extensions/allowedaddresspairs.py +++ b/neutron/objects/port/extensions/allowedaddresspairs.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +from neutron_lib.db import api as db_api from neutron.common import utils from neutron.db.models import allowed_address_pair as models @@ -61,3 +62,12 @@ class AllowedAddressPair(base.NeutronDbObject): fields['mac_address'] = utils.AuthenticEUI( fields['mac_address']) return fields + + @classmethod + def get_allowed_address_pairs_for_ports(cls, context, port_ids): + with db_api.CONTEXT_READER.using(context): + query = context.session.query(models.AllowedAddressPair).filter( + models.AllowedAddressPair.port_id.in_(port_ids)) + pairs = [cls._load_object(context, db_obj) + for db_obj in query.all()] + return pairs diff --git a/neutron/tests/functional/agent/l3/test_dvr_router.py b/neutron/tests/functional/agent/l3/test_dvr_router.py index 0236ee3f077..578449bc3a5 100644 --- a/neutron/tests/functional/agent/l3/test_dvr_router.py +++ b/neutron/tests/functional/agent/l3/test_dvr_router.py @@ -1000,21 +1000,25 @@ class TestDvrRouter(framework.L3AgentTestFramework): # cache is properly populated. self.agent.conf.agent_mode = 'dvr_snat' router_info = self.generate_dvr_router_info(enable_snat=True) - expected_neighbor = '35.4.1.10' + expected_neighbors = ['35.4.1.10', '10.0.0.10'] port_data = { - 'fixed_ips': [{'ip_address': expected_neighbor}], + 'fixed_ips': [{'ip_address': expected_neighbors[0]}], 'mac_address': 'fa:3e:aa:bb:cc:dd', - 'device_owner': DEVICE_OWNER_COMPUTE + 'device_owner': DEVICE_OWNER_COMPUTE, + 'allowed_address_pairs': [ + {'ip_address': expected_neighbors[1], + 'mac_address': 'fa:3e:aa:bb:cc:dd'}] } self.agent.plugin_rpc.get_ports_by_subnet.return_value = [port_data] router1 = self.manage_router(self.agent, router_info) internal_device = router1.get_internal_device_name( router_info['_interfaces'][0]['id']) - neighbor = ip_lib.dump_neigh_entries(4, internal_device, - router1.ns_name, - dst=expected_neighbor) - self.assertNotEqual([], neighbor) - self.assertEqual(expected_neighbor, neighbor[0]['dst']) + for expected_neighbor in expected_neighbors: + neighbor = ip_lib.dump_neigh_entries(4, internal_device, + router1.ns_name, + dst=expected_neighbor) + self.assertNotEqual([], neighbor) + self.assertEqual(expected_neighbor, neighbor[0]['dst']) def _assert_rfp_fpr_mtu(self, router, expected_mtu=1500): dev_mtu = self.get_device_mtu( diff --git a/neutron/tests/unit/agent/l3/test_dvr_local_router.py b/neutron/tests/unit/agent/l3/test_dvr_local_router.py index 2f71ef8a79d..738feb3de87 100644 --- a/neutron/tests/unit/agent/l3/test_dvr_local_router.py +++ b/neutron/tests/unit/agent/l3/test_dvr_local_router.py @@ -531,7 +531,10 @@ class TestDvrRouterOperations(base.BaseTestCase): 'device_owner': lib_constants.DEVICE_OWNER_DHCP, 'fixed_ips': [{'ip_address': '1.2.3.4', 'prefixlen': 24, - 'subnet_id': subnet_id}]}, + 'subnet_id': subnet_id}], + 'allowed_address_pairs': [ + {'ip_address': '10.20.30.40', + 'mac_address': '00:11:22:33:44:55'}]}, {'mac_address': '11:22:33:44:55:66', 'device_owner': lib_constants.DEVICE_OWNER_LOADBALANCER, 'fixed_ips': [{'ip_address': '1.2.3.5', @@ -553,8 +556,9 @@ class TestDvrRouterOperations(base.BaseTestCase): '_process_arp_cache_for_internal_port') as parp: ri._set_subnet_arp_info(subnet_id) self.assertEqual(1, parp.call_count) - self.mock_ip_dev.neigh.add.assert_called_once_with( - '1.2.3.4', '00:11:22:33:44:55') + self.mock_ip_dev.neigh.add.assert_has_calls([ + mock.call('1.2.3.4', '00:11:22:33:44:55'), + mock.call('10.20.30.40', '00:11:22:33:44:55')]) # Test negative case router['distributed'] = False diff --git a/neutron/tests/unit/db/test_l3_dvr_db.py b/neutron/tests/unit/db/test_l3_dvr_db.py index 63cfd8bd259..7edaee9ceff 100644 --- a/neutron/tests/unit/db/test_l3_dvr_db.py +++ b/neutron/tests/unit/db/test_l3_dvr_db.py @@ -1110,6 +1110,8 @@ class L3DvrTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase): router = self._create_router(router_dict) with self.network() as network,\ self.subnet(network=network) as subnet: + self.mixin._core_plugin.get_allowed_address_pairs_for_ports = ( + mock.Mock()) fake_bound_ports_ids = [] def fake_is_port_bound(port): @@ -1131,6 +1133,8 @@ class L3DvrTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase): 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) + (self.mixin._core_plugin.get_allowed_address_pairs_for_ports. + assert_called_once_with(self.ctx, dvr_subnet_ports_ids)) @mock.patch.object(plugin_utils, 'can_port_be_bound_to_virtual_bridge', return_value=True)