diff --git a/neutron/db/l3_dvr_db.py b/neutron/db/l3_dvr_db.py index 49cce500e24..0a1bfd783e0 100644 --- a/neutron/db/l3_dvr_db.py +++ b/neutron/db/l3_dvr_db.py @@ -204,11 +204,11 @@ class DVRResourceOperationHandler(object): return ports def _add_csnat_router_interface_port( - self, context, router, network_id, subnet_id, do_pop=True): + self, context, router, network_id, subnets, do_pop=True): """Add SNAT interface to the specified router and subnet.""" port_data = {'tenant_id': '', 'network_id': network_id, - 'fixed_ips': [{'subnet_id': subnet_id}], + 'fixed_ips': subnets, 'device_id': router.id, 'device_owner': const.DEVICE_OWNER_ROUTER_SNAT, 'admin_state_up': True, @@ -253,15 +253,31 @@ class DVRResourceOperationHandler(object): ) LOG.info('SNAT interface port list does not exist,' ' so create one: %s', port_list) + v6_subnets = [] + network = None for intf in int_ports: if intf.fixed_ips: # Passing the subnet for the port to make sure the IP's # are assigned on the right subnet if multiple subnet # exists - snat_port = self._add_csnat_router_interface_port( - context, router, intf['network_id'], - intf['fixed_ips'][0]['subnet_id'], do_pop=False) - port_list.append(snat_port) + for fixed_ip in intf['fixed_ips']: + ip_version = n_utils.get_ip_version( + fixed_ip.get('ip_address')) + if ip_version == const.IP_VERSION_4: + snat_port = self._add_csnat_router_interface_port( + context, router, intf['network_id'], + [{'subnet_id': fixed_ip['subnet_id']}], + do_pop=False) + port_list.append(snat_port) + else: + v6_subnets.append( + {"subnet_id": fixed_ip['subnet_id']}) + network = intf['network_id'] + if v6_subnets: + snat_port = self._add_csnat_router_interface_port( + context, router, network, + v6_subnets, do_pop=False) + port_list.append(snat_port) if port_list: self.l3plugin._populate_mtu_and_subnets_for_ports( context, port_list) @@ -397,7 +413,7 @@ class DVRResourceOperationHandler(object): admin_context = context.elevated() self._add_csnat_router_interface_port( admin_context, router_db, port['network_id'], - port['fixed_ips'][-1]['subnet_id']) + [{'subnet_id': port['fixed_ips'][-1]['subnet_id']}]) @registry.receives(resources.ROUTER_INTERFACE, [events.AFTER_CREATE]) @db_api.retry_if_session_inactive() diff --git a/neutron/tests/unit/db/test_l3_dvr_db.py b/neutron/tests/unit/db/test_l3_dvr_db.py index e09af96da32..4706a2f8ac7 100644 --- a/neutron/tests/unit/db/test_l3_dvr_db.py +++ b/neutron/tests/unit/db/test_l3_dvr_db.py @@ -596,6 +596,72 @@ class L3DvrTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase): def test_distributed_to_ha_csnat_ports_removal(self): self._test_csnat_ports_removal(ha=True) + def test_update_router_gw_info_csnat_ports_add(self): + router_dict = {'name': 'test_router', + 'admin_state_up': True, + 'distributed': True} + router = self._create_router(router_dict) + with self.network() as net_ext,\ + self.network() as net_int,\ + self.subnet( + network=net_int, + cidr='2001:db8:1::/64', + gateway_ip='2001:db8:1::1', + ip_version=const.IP_VERSION_6) as v6_subnet1,\ + self.subnet( + network=net_int, + cidr='2001:db8:2::/64', + gateway_ip='2001:db8:2::1', + ip_version=const.IP_VERSION_6) as v6_subnet2,\ + self.subnet( + network=net_int, + cidr='10.10.10.0/24') as v4_subnet: + + self.core_plugin.update_network( + self.ctx, net_ext['network']['id'], + {'network': {'router:external': True}}) + + # Add router interface, then set router gateway + self.mixin.add_router_interface(self.ctx, router['id'], + {'subnet_id': v6_subnet1['subnet']['id']}) + self.mixin.add_router_interface(self.ctx, router['id'], + {'subnet_id': v6_subnet2['subnet']['id']}) + self.mixin.add_router_interface(self.ctx, router['id'], + {'subnet_id': v4_subnet['subnet']['id']}) + + dvr_filters = {'device_owner': + [const.DEVICE_OWNER_DVR_INTERFACE]} + dvr_ports = self.core_plugin.get_ports( + self.ctx, filters=dvr_filters) + # One for IPv4, one for two IPv6 subnets + self.assertEqual(2, len(dvr_ports)) + + self.mixin.update_router( + self.ctx, router['id'], + {'router': {'external_gateway_info': + {'network_id': net_ext['network']['id']}}}) + + csnat_filters = {'device_owner': + [const.DEVICE_OWNER_ROUTER_SNAT]} + csnat_ports = self.core_plugin.get_ports( + self.ctx, filters=csnat_filters) + # One for IPv4, one for two IPv6 subnets + self.assertEqual(2, len(csnat_ports)) + + # Remove v4 subnet interface from router + self.mixin.remove_router_interface( + self.ctx, router['id'], + {'subnet_id': v4_subnet['subnet']['id']}) + + dvr_ports = self.core_plugin.get_ports( + self.ctx, filters=dvr_filters) + self.assertEqual(1, len(dvr_ports)) + + csnat_ports = self.core_plugin.get_ports( + self.ctx, filters=csnat_filters) + self.assertEqual(1, len(csnat_ports)) + self.assertEqual(2, len(csnat_ports[0]['fixed_ips'])) + def test_remove_router_interface_csnat_ports_removal(self): router_dict = {'name': 'test_router', 'admin_state_up': True, 'distributed': True}