From: Sean M. Collins Date: Mon, 7 Apr 2014 18:35:58 +0000 (-0400) Subject: Support Subnets that are configured by external RAs X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=75be9df987ae43684b5452d2f0139db2e6acafdd;p=openstack-build%2Fneutron-build.git Support Subnets that are configured by external RAs The IPv6 attributes for subnets provides support for IPv6 subnets that are managed by non-OpenStack gateway devices, by creating a subnet that has the ipv6_ra_mode attribute not set, and the ipv6_address_mode attribute set to IPV6_SLAAC. In order to support stateless IPv6, Neutron should calculate an IPv6 address based on the IPv6 prefix and MAC address via EUI-64 specification, and assign this address to the port. Implements blueprint ipv6-provider-nets-slaac DocImpact Change-Id: Ia76d76a67e0dc9f996a72e38d987a05aecc1b55f Co-Authored-By: Xu Han Peng Co-Authored-By: Dazhao Yu --- diff --git a/neutron/db/db_base_plugin_v2.py b/neutron/db/db_base_plugin_v2.py index 7b3ae166a..7f61fe552 100644 --- a/neutron/db/db_base_plugin_v2.py +++ b/neutron/db/db_base_plugin_v2.py @@ -26,6 +26,7 @@ from sqlalchemy import sql from neutron.api.v2 import attributes from neutron.common import constants from neutron.common import exceptions as n_exc +from neutron.common import ipv6_utils from neutron import context as ctx from neutron.db import api as db from neutron.db import models_v2 @@ -351,6 +352,12 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2, ip_address=ip_address, subnet_id=subnet_id).delete() + @staticmethod + def _check_if_subnet_uses_eui64(subnet): + """Check if ipv6 address will be calculated via EUI64.""" + return (subnet['ipv6_address_mode'] == constants.IPV6_SLAAC + or subnet['ipv6_address_mode'] == constants.DHCPV6_STATELESS) + @staticmethod def _generate_ip(context, subnets): try: @@ -670,6 +677,20 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2, v4.append(subnet) else: v6.append(subnet) + for subnet in v6: + if self._check_if_subnet_uses_eui64(subnet): + #(dzyu) If true, calculate an IPv6 address + # by mac address and prefix, then remove this + # subnet from the array of subnets that will be passed + # to the _generate_ip() function call, since we just + # generated an IP. + mac = p['mac_address'] + prefix = subnet['cidr'] + ip_address = ipv6_utils.get_ipv6_addr_by_EUI64( + prefix, mac) + ips.append({'ip_address': ip_address.format(), + 'subnet_id': subnet['id']}) + v6.remove(subnet) version_subnets = [v4, v6] for subnets in version_subnets: if subnets: @@ -1345,7 +1366,6 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2, p = port['port'] port_id = p.get('id') or uuidutils.generate_uuid() network_id = p['network_id'] - mac_address = p['mac_address'] # NOTE(jkoelker) Get the tenant_id outside of the session to avoid # unneeded db action if the operation raises tenant_id = self._get_tenant_id_for_create(context, p) @@ -1358,16 +1378,19 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2, # Ensure that a MAC address is defined and it is unique on the # network - if mac_address is attributes.ATTR_NOT_SPECIFIED: - mac_address = NeutronDbPluginV2._generate_mac(context, - network_id) + if p['mac_address'] is attributes.ATTR_NOT_SPECIFIED: + #Note(scollins) Add the generated mac_address to the port, + #since _allocate_ips_for_port will need the mac when + #calculating an EUI-64 address for a v6 subnet + p['mac_address'] = NeutronDbPluginV2._generate_mac(context, + network_id) else: # Ensure that the mac on the network is unique if not NeutronDbPluginV2._check_unique_mac(context, network_id, - mac_address): + p['mac_address']): raise n_exc.MacAddressInUse(net_id=network_id, - mac=mac_address) + mac=p['mac_address']) # Returns the IP's for the port ips = self._allocate_ips_for_port(context, network, port) @@ -1381,7 +1404,7 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2, name=p['name'], id=port_id, network_id=network_id, - mac_address=mac_address, + mac_address=p['mac_address'], admin_state_up=p['admin_state_up'], status=status, device_id=p['device_id'], diff --git a/neutron/tests/unit/oneconvergence/test_nvsd_plugin.py b/neutron/tests/unit/oneconvergence/test_nvsd_plugin.py index 6cc8a1496..564b8c56a 100644 --- a/neutron/tests/unit/oneconvergence/test_nvsd_plugin.py +++ b/neutron/tests/unit/oneconvergence/test_nvsd_plugin.py @@ -115,6 +115,9 @@ class TestOneConvergencePluginPortsV2(test_plugin.TestPortsV2, self.assertEqual(port['binding:vif_type'], portbindings.VIF_TYPE_OVS) + def test_ip_allocation_for_ipv6_subnet_slaac_adddress_mode(self): + self.skipTest("NVSD Plugin does not support IPV6.") + class TestOneConvergenceBasicGet(test_plugin.TestBasicGet, OneConvergencePluginV2TestCase): diff --git a/neutron/tests/unit/test_db_plugin.py b/neutron/tests/unit/test_db_plugin.py index d1ff9a90c..cfb3daa11 100644 --- a/neutron/tests/unit/test_db_plugin.py +++ b/neutron/tests/unit/test_db_plugin.py @@ -30,6 +30,7 @@ from neutron.api.v2 import attributes from neutron.api.v2 import router from neutron.common import constants from neutron.common import exceptions as n_exc +from neutron.common import ipv6_utils from neutron.common import test_lib from neutron.common import utils from neutron import context @@ -1397,6 +1398,24 @@ fixed_ips=ip_address%%3D%s&fixed_ips=ip_address%%3D%s&fixed_ips=subnet_id%%3D%s self._delete('ports', port3['port']['id']) self._delete('ports', port4['port']['id']) + def test_ip_allocation_for_ipv6_subnet_slaac_adddress_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::/80', + ip_version=6, + ipv6_ra_mode=None, + ipv6_address_mode=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) + def test_range_allocation(self): with self.subnet(gateway_ip='10.0.0.3', cidr='10.0.0.0/29') as subnet: