From: Dane LeBlanc Date: Mon, 16 Mar 2015 14:23:26 +0000 (-0400) Subject: Include IPv6 SLAAC addresses implicitly for port create X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=259f969c030491690f1e6c856b505acbb0746657;p=openstack-build%2Fneutron-build.git Include IPv6 SLAAC addresses implicitly for port create (Patch set #1 for the multiple-ipv6-prefixes blueprint) This patch set resolves an issue whereby auto-address subnets are not being included implicitly for port create operations that include a fixed_ips list. Change-Id: I61beb2d663dfa6070b529b567d79664b1cb69810 Partially-implements: blueprint multiple-ipv6-prefixes --- diff --git a/neutron/db/db_base_plugin_v2.py b/neutron/db/db_base_plugin_v2.py index 73db078b8..0abb7d025 100644 --- a/neutron/db/db_base_plugin_v2.py +++ b/neutron/db/db_base_plugin_v2.py @@ -391,6 +391,7 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2, raise n_exc.InvalidInput(error_message=msg) subnet_id = subnet['id'] + is_auto_addr_subnet = ipv6_utils.is_auto_address_subnet(subnet) if 'ip_address' in fixed: # Ensure that the IP's are unique if not NeutronDbPluginV2._check_unique_ip(context, network_id, @@ -405,7 +406,7 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2, fixed['ip_address'])): raise n_exc.InvalidIpForSubnet( ip_address=fixed['ip_address']) - if (ipv6_utils.is_auto_address_subnet(subnet) and + if (is_auto_addr_subnet and device_owner not in constants.ROUTER_INTERFACE_OWNERS): msg = (_("IPv6 address %(address)s can not be directly " @@ -417,7 +418,15 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2, fixed_ip_set.append({'subnet_id': subnet_id, 'ip_address': fixed['ip_address']}) else: - fixed_ip_set.append({'subnet_id': subnet_id}) + # A scan for auto-address subnets on the network is done + # separately so that all such subnets (not just those + # listed explicitly here by subnet ID) are associated + # with the port. + if (device_owner in constants.ROUTER_INTERFACE_OWNERS or + device_owner == constants.DEVICE_OWNER_ROUTER_SNAT or + not is_auto_addr_subnet): + fixed_ip_set.append({'subnet_id': subnet_id}) + if len(fixed_ip_set) > cfg.CONF.max_fixed_ips_per_port: msg = _('Exceeded maximim amount of fixed ips per port') raise n_exc.InvalidInput(error_message=msg) @@ -497,6 +506,9 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2, """ p = port['port'] ips = [] + v6_stateless = [] + net_id_filter = {'network_id': [p['network_id']]} + subnets = self.get_subnets(context, filters=net_id_filter) fixed_configured = p['fixed_ips'] is not attributes.ATTR_NOT_SPECIFIED if fixed_configured: @@ -507,13 +519,17 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2, ips = self._allocate_fixed_ips(context, configured_ips, p['mac_address']) + + # For ports that are not router ports, implicitly include all + # auto-address subnets for address association. + if (not p['device_owner'] in constants.ROUTER_INTERFACE_OWNERS and + p['device_owner'] != constants.DEVICE_OWNER_ROUTER_SNAT): + v6_stateless += [subnet for subnet in subnets + if ipv6_utils.is_auto_address_subnet(subnet)] else: - filter = {'network_id': [p['network_id']]} - subnets = self.get_subnets(context, filters=filter) - # Split into v4 and v6 subnets + # Split into v4, v6 stateless and v6 stateful subnets v4 = [] v6_stateful = [] - v6_stateless = [] for subnet in subnets: if subnet['ip_version'] == 4: v4.append(subnet) @@ -523,24 +539,26 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2, else: v6_stateful.append(subnet) - for subnet in v6_stateless: - 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()) - ips.append({'ip_address': ip_address.format(), - 'subnet_id': subnet['id']}) version_subnets = [v4, v6_stateful] for subnets in version_subnets: if subnets: result = NeutronDbPluginV2._generate_ip(context, subnets) ips.append({'ip_address': result['ip_address'], 'subnet_id': result['subnet_id']}) + + 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()) + ips.append({'ip_address': ip_address.format(), + 'subnet_id': subnet['id']}) + return ips def _validate_subnet_cidr(self, context, network, new_subnet_cidr): diff --git a/neutron/tests/unit/test_db_plugin.py b/neutron/tests/unit/test_db_plugin.py index d0b4f4bca..a352bcc68 100644 --- a/neutron/tests/unit/test_db_plugin.py +++ b/neutron/tests/unit/test_db_plugin.py @@ -1622,23 +1622,111 @@ fixed_ips=ip_address%%3D%s&fixed_ips=ip_address%%3D%s&fixed_ips=subnet_id%%3D%s self.assertIn({'ip_address': eui_addr, 'subnet_id': subnet2['subnet']['id']}, ips) + def _make_v6_subnet(self, network, ra_addr_mode): + return (self._make_subnet(self.fmt, network, gateway='fe80::1', + cidr='fe80::/64', ip_version=6, + ipv6_ra_mode=ra_addr_mode, + ipv6_address_mode=ra_addr_mode)) + + @staticmethod + def _calc_ipv6_addr_by_EUI64(port, subnet): + port_mac = port['port']['mac_address'] + subnet_cidr = subnet['subnet']['cidr'] + return str(ipv6_utils.get_ipv6_addr_by_EUI64(subnet_cidr, port_mac)) + def test_ip_allocation_for_ipv6_subnet_slaac_address_mode(self): res = self._create_network(fmt=self.fmt, name='net', admin_state_up=True) network = self.deserialize(self.fmt, res) - v6_subnet = self._make_subnet(self.fmt, network, - gateway='fe80::1', - cidr='fe80::/64', - ip_version=6, - ipv6_ra_mode=None, - ipv6_address_mode=constants.IPV6_SLAAC) + subnet = self._make_v6_subnet(network, constants.IPV6_SLAAC) port = self._make_port(self.fmt, network['network']['id']) - self.assertEqual(len(port['port']['fixed_ips']), 1) - port_mac = port['port']['mac_address'] - subnet_cidr = v6_subnet['subnet']['cidr'] - eui_addr = str(ipv6_utils.get_ipv6_addr_by_EUI64(subnet_cidr, - port_mac)) - self.assertEqual(port['port']['fixed_ips'][0]['ip_address'], eui_addr) + self.assertEqual(1, len(port['port']['fixed_ips'])) + self.assertEqual(self._calc_ipv6_addr_by_EUI64(port, subnet), + port['port']['fixed_ips'][0]['ip_address']) + + def _test_create_port_with_ipv6_subnet_in_fixed_ips(self, addr_mode): + """Test port create with an IPv6 subnet incl in fixed IPs.""" + with self.network(name='net') as network: + subnet = self._make_v6_subnet(network, addr_mode) + subnet_id = subnet['subnet']['id'] + fixed_ips = [{'subnet_id': subnet_id}] + with self.port(subnet=subnet, fixed_ips=fixed_ips) as port: + if addr_mode == constants.IPV6_SLAAC: + exp_ip_addr = self._calc_ipv6_addr_by_EUI64(port, subnet) + else: + exp_ip_addr = 'fe80::2' + port_fixed_ips = port['port']['fixed_ips'] + self.assertEqual(1, len(port_fixed_ips)) + self.assertEqual(exp_ip_addr, + port_fixed_ips[0]['ip_address']) + + def test_create_port_with_ipv6_slaac_subnet_in_fixed_ips(self): + self._test_create_port_with_ipv6_subnet_in_fixed_ips( + addr_mode=constants.IPV6_SLAAC) + + def test_create_port_with_ipv6_dhcp_stateful_subnet_in_fixed_ips(self): + self._test_create_port_with_ipv6_subnet_in_fixed_ips( + addr_mode=constants.DHCPV6_STATEFUL) + + def test_create_port_with_multiple_ipv4_and_ipv6_subnets(self): + """Test port create with multiple IPv4, IPv6 DHCP/SLAAC subnets.""" + res = self._create_network(fmt=self.fmt, name='net', + admin_state_up=True) + network = self.deserialize(self.fmt, res) + sub_dicts = [ + {'gateway': '10.0.0.1', 'cidr': '10.0.0.0/24', + 'ip_version': 4, 'ra_addr_mode': None}, + {'gateway': '10.0.1.1', 'cidr': '10.0.1.0/24', + 'ip_version': 4, 'ra_addr_mode': None}, + {'gateway': 'fe80::1', 'cidr': 'fe80::/64', + 'ip_version': 6, 'ra_addr_mode': constants.IPV6_SLAAC}, + {'gateway': 'fe81::1', 'cidr': 'fe81::/64', + 'ip_version': 6, 'ra_addr_mode': constants.IPV6_SLAAC}, + {'gateway': 'fe82::1', 'cidr': 'fe82::/64', + 'ip_version': 6, 'ra_addr_mode': constants.DHCPV6_STATEFUL}, + {'gateway': 'fe83::1', 'cidr': 'fe83::/64', + 'ip_version': 6, 'ra_addr_mode': constants.DHCPV6_STATEFUL}] + subnets = {} + for sub_dict in sub_dicts: + subnet = self._make_subnet( + self.fmt, network, + gateway=sub_dict['gateway'], + cidr=sub_dict['cidr'], + ip_version=sub_dict['ip_version'], + ipv6_ra_mode=sub_dict['ra_addr_mode'], + ipv6_address_mode=sub_dict['ra_addr_mode']) + subnets[subnet['subnet']['id']] = sub_dict + res = self._create_port(self.fmt, net_id=network['network']['id']) + port = self.deserialize(self.fmt, res) + # Since the create port request was made without a list of fixed IPs, + # the port should be associated with addresses for one of the + # IPv4 subnets, one of the DHCPv6 subnets, and both of the IPv6 + # SLAAC subnets. + self.assertEqual(4, len(port['port']['fixed_ips'])) + addr_mode_count = {None: 0, constants.DHCPV6_STATEFUL: 0, + constants.IPV6_SLAAC: 0} + for fixed_ip in port['port']['fixed_ips']: + subnet_id = fixed_ip['subnet_id'] + if subnet_id in subnets: + addr_mode_count[subnets[subnet_id]['ra_addr_mode']] += 1 + self.assertEqual(1, addr_mode_count[None]) + self.assertEqual(1, addr_mode_count[constants.DHCPV6_STATEFUL]) + self.assertEqual(2, addr_mode_count[constants.IPV6_SLAAC]) + + def test_delete_port_with_ipv6_slaac_address(self): + """Test that a port with an IPv6 SLAAC address can be deleted.""" + res = self._create_network(fmt=self.fmt, name='net', + admin_state_up=True) + network = self.deserialize(self.fmt, res) + # Create a port that has an associated IPv6 SLAAC address + self._make_v6_subnet(network, constants.IPV6_SLAAC) + res = self._create_port(self.fmt, net_id=network['network']['id']) + port = self.deserialize(self.fmt, res) + self.assertEqual(1, len(port['port']['fixed_ips'])) + # Confirm that the port can be deleted + self._delete('ports', port['port']['id']) + self._show('ports', port['port']['id'], + expected_code=webob.exc.HTTPNotFound.code) def test_ip_allocation_for_ipv6_2_subnet_slaac_mode(self): res = self._create_network(fmt=self.fmt, name='net',