From 81f4469b620ec221f53d3ffb4d00b90896dc5ce1 Mon Sep 17 00:00:00 2001 From: Dane LeBlanc Date: Mon, 2 Mar 2015 22:03:10 -0500 Subject: [PATCH] IPv6 SLAAC subnet create should update ports on net If ports are first created on a network, and then an IPv6 SLAAC or DHCPv6-stateless subnet is created on that network, then the ports created prior to the subnet create are not getting automatically updated (associated) with addresses for the SLAAC/DHCPv6-stateless subnet, as required. Change-Id: I5901db6655c045c0e78c7cb7fc51ce8c9a9e1933 Closes-Bug: 1427474 --- neutron/db/db_base_plugin_v2.py | 51 +++++++++++++++++++----- neutron/tests/unit/test_db_plugin.py | 58 ++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 10 deletions(-) diff --git a/neutron/db/db_base_plugin_v2.py b/neutron/db/db_base_plugin_v2.py index 84cd7c4b6..99c7c420a 100644 --- a/neutron/db/db_base_plugin_v2.py +++ b/neutron/db/db_base_plugin_v2.py @@ -472,9 +472,9 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2, # from subnet else: if is_auto_addr: - prefix = subnet['cidr'] - ip_address = ipv6_utils.get_ipv6_addr_by_EUI64( - prefix, mac_address) + ip_address = self._calculate_ipv6_eui64_addr(context, + subnet, + mac_address) ips.append({'ip_address': ip_address.format(), 'subnet_id': subnet['id']}) else: @@ -531,6 +531,17 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2, ips = self._allocate_fixed_ips(context, to_add, mac_address) return ips, prev_ips + def _calculate_ipv6_eui64_addr(self, context, subnet, mac_addr): + prefix = subnet['cidr'] + network_id = subnet['network_id'] + ip_address = ipv6_utils.get_ipv6_addr_by_EUI64( + prefix, mac_addr).format() + if not self._check_unique_ip(context, network_id, + subnet['id'], ip_address): + raise n_exc.IpAddressInUse(net_id=network_id, + ip_address=ip_address) + return ip_address + def _allocate_ips_for_port(self, context, port): """Allocate IP addresses for the port. @@ -583,13 +594,8 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2, for subnet in v6_stateless: # IP addresses for IPv6 SLAAC and DHCPv6-stateless subnets # are implicitly included. - prefix = subnet['cidr'] - ip_address = ipv6_utils.get_ipv6_addr_by_EUI64(prefix, - p['mac_address']) - if not self._check_unique_ip(context, p['network_id'], - subnet['id'], ip_address.format()): - raise n_exc.IpAddressInUse(net_id=p['network_id'], - ip_address=ip_address.format()) + ip_address = self._calculate_ipv6_eui64_addr(context, subnet, + p['mac_address']) ips.append({'ip_address': ip_address.format(), 'subnet_id': subnet['id']}) @@ -1242,6 +1248,9 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2, s['dns_nameservers'], s['host_routes'], s['allocation_pools']) + # If this subnet supports auto-addressing, then update any + # internal ports on the network with addresses for this subnet. + self._add_auto_addrs_on_network_ports(context, subnet) if network.external: self._update_router_gw_ports(context, subnet['id'], @@ -1268,6 +1277,9 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2, s['dns_nameservers'], s['host_routes'], s['allocation_pools']) + # If this subnet supports auto-addressing, then update any + # internal ports on the network with addresses for this subnet. + self._add_auto_addrs_on_network_ports(context, subnet) if network.external: self._update_router_gw_ports(context, subnet['id'], @@ -1334,6 +1346,25 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2, return self._create_subnet_from_implicit_pool(context, subnet) return self._create_subnet_from_pool(context, subnet, subnetpool_id) + def _add_auto_addrs_on_network_ports(self, context, subnet): + """If subnet uses auto-addressing, add addrs for ports on the net.""" + if ipv6_utils.is_auto_address_subnet(subnet): + network_id = subnet['network_id'] + port_qry = context.session.query(models_v2.Port) + for port in port_qry.filter( + and_(models_v2.Port.network_id == network_id, + models_v2.Port.device_owner != + constants.DEVICE_OWNER_ROUTER_SNAT, + ~models_v2.Port.device_owner.in_( + constants.ROUTER_INTERFACE_OWNERS))): + ip_address = self._calculate_ipv6_eui64_addr( + context, subnet, port['mac_address']) + allocated = models_v2.IPAllocation(network_id=network_id, + port_id=port['id'], + ip_address=ip_address, + subnet_id=subnet['id']) + context.session.add(allocated) + def _update_subnet_dns_nameservers(self, context, id, s): old_dns_list = self._get_dns_by_subnet(context, id) new_dns_addr_set = set(s["dns_nameservers"]) diff --git a/neutron/tests/unit/test_db_plugin.py b/neutron/tests/unit/test_db_plugin.py index f7c5fe8af..7fd69646d 100644 --- a/neutron/tests/unit/test_db_plugin.py +++ b/neutron/tests/unit/test_db_plugin.py @@ -3802,6 +3802,62 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase): self.assertEqual(ctx_manager.exception.code, webob.exc.HTTPClientError.code) + def _test_create_subnet_ipv6_auto_addr_with_port_on_network( + self, addr_mode, device_owner=DEVICE_OWNER_COMPUTE): + # Create a network with one IPv4 subnet and one port + with self.network() as network: + with self.subnet(network=network) as v4_subnet: + with self.port(subnet=v4_subnet, + device_owner=device_owner) as port: + # Add an IPv6 auto-address subnet to the network + with self.subnet(network=network, cidr='fe80::/64', + ip_version=6, ipv6_ra_mode=addr_mode, + ipv6_address_mode=addr_mode + ) as v6_subnet: + if (device_owner == constants.DEVICE_OWNER_ROUTER_SNAT + or device_owner in + constants.ROUTER_INTERFACE_OWNERS): + # DVR SNAT and router interfaces should not have + # been updated with addresses from the new + # auto-address subnet + self.assertEqual(1, + len(port['port']['fixed_ips'])) + else: + # Confirm that the port has been updated with an + # address from the new auto-address subnet + req = self.new_show_request( + 'ports', port['port']['id'], self.fmt) + sport = self.deserialize( + self.fmt, req.get_response(self.api)) + fixed_ips = sport['port']['fixed_ips'] + self.assertEqual(2, len(fixed_ips)) + self.assertIn(v6_subnet['subnet']['id'], + [fixed_ip['subnet_id'] for fixed_ip + in fixed_ips]) + + def test_create_subnet_ipv6_slaac_with_port_on_network(self): + self._test_create_subnet_ipv6_auto_addr_with_port_on_network( + constants.IPV6_SLAAC) + + def test_create_subnet_dhcpv6_stateless_with_port_on_network(self): + self._test_create_subnet_ipv6_auto_addr_with_port_on_network( + constants.DHCPV6_STATELESS) + + def test_create_subnet_ipv6_slaac_with_dhcp_port_on_network(self): + self._test_create_subnet_ipv6_auto_addr_with_port_on_network( + constants.DHCPV6_STATELESS, + device_owner=constants.DEVICE_OWNER_DHCP) + + def test_create_subnet_ipv6_slaac_with_router_intf_on_network(self): + self._test_create_subnet_ipv6_auto_addr_with_port_on_network( + constants.DHCPV6_STATELESS, + device_owner=constants.DEVICE_OWNER_ROUTER_INTF) + + def test_create_subnet_ipv6_slaac_with_snat_intf_on_network(self): + self._test_create_subnet_ipv6_auto_addr_with_port_on_network( + constants.DHCPV6_STATELESS, + device_owner=constants.DEVICE_OWNER_ROUTER_SNAT) + def test_update_subnet_no_gateway(self): with self.subnet() as subnet: data = {'subnet': {'gateway_ip': '10.0.0.1'}} @@ -5321,6 +5377,7 @@ class TestNeutronDbPluginV2(base.BaseTestCase): 'enable_dhcp': True, 'gateway_ip': u'2001:100::1', 'id': u'd1a28edd-bd83-480a-bd40-93d036c89f13', + 'network_id': 'fbb9b578-95eb-4b79-a116-78e5c4927176', 'ip_version': 6, 'ipv6_address_mode': None, 'ipv6_ra_mode': u'slaac'}, @@ -5329,6 +5386,7 @@ class TestNeutronDbPluginV2(base.BaseTestCase): 'enable_dhcp': True, 'gateway_ip': u'2001:200::1', 'id': u'dc813d3d-ed66-4184-8570-7325c8195e28', + 'network_id': 'fbb9b578-95eb-4b79-a116-78e5c4927176', 'ip_version': 6, 'ipv6_address_mode': None, 'ipv6_ra_mode': u'slaac'}] -- 2.45.2