]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
IPv6 SLAAC subnet create should update ports on net
authorDane LeBlanc <leblancd@cisco.com>
Thu, 9 Apr 2015 14:32:33 +0000 (10:32 -0400)
committerDane LeBlanc <leblancd@cisco.com>
Wed, 15 Apr 2015 17:53:41 +0000 (13:53 -0400)
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: I88d04a13ce5b8ed4c88eac734e589e8a90e986a0
Closes-Bug: 1427474
Closes-Bug: 1441382
Closes-Bug: 1440183

neutron/db/db_base_plugin_v2.py
neutron/tests/unit/db/test_db_base_plugin_v2.py

index f7bcf8db538388c8e25afd70d8c71776621a7193..dcf7adc6f6f302f31e9ffa6bf2e9ed213f7ff2f7 100644 (file)
@@ -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.
 
@@ -585,13 +596,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']})
 
@@ -1343,8 +1349,46 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
                         'subnet pool')
                 raise n_exc.BadRequest(resource='subnets', msg=msg)
             # Create subnet from the implicit(AKA null) pool
-            return self._create_subnet_from_implicit_pool(context, subnet)
-        return self._create_subnet_from_pool(context, subnet, subnetpool_id)
+            created_subnet = self._create_subnet_from_implicit_pool(context,
+                                                                    subnet)
+        else:
+            created_subnet = self._create_subnet_from_pool(context, subnet,
+                                                           subnetpool_id)
+
+        # If this subnet supports auto-addressing, then update any
+        # internal ports on the network with addresses for this subnet.
+        if ipv6_utils.is_auto_address_subnet(created_subnet):
+            self._add_auto_addrs_on_network_ports(context, created_subnet)
+
+        return created_subnet
+
+    def _add_auto_addrs_on_network_ports(self, context, subnet):
+        """For an auto-address subnet, add addrs for ports on the net."""
+        with context.session.begin(subtransactions=True):
+            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'])
+                try:
+                    # Do the insertion of each IP allocation entry within
+                    # the context of a nested transaction, so that the entry
+                    # is rolled back independently of other entries whenever
+                    # the corresponding port has been deleted.
+                    with context.session.begin_nested():
+                        context.session.add(allocated)
+                except db_exc.DBReferenceError:
+                    LOG.debug("Port %s was deleted while updating it with an "
+                              "IPv6 auto-address. Ignoring.", port['id'])
 
     def _update_subnet_dns_nameservers(self, context, id, s):
         old_dns_list = self._get_dns_by_subnet(context, id)
index 0dc18001dd438ddb6ffba59a3727de78b8908f07..e8ef97e8fdb353e6dc107a49f152ed9e2462c57e 100644 (file)
@@ -20,7 +20,9 @@ import itertools
 import mock
 import netaddr
 from oslo_config import cfg
+from oslo_db import exception as db_exc
 from oslo_utils import importutils
+from sqlalchemy import orm
 from testtools import matchers
 import webob.exc
 
@@ -3811,6 +3813,71 @@ 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,
+            insert_db_reference_error=False):
+        # Create a network with one IPv4 subnet and one port
+        with self.network() as network,\
+            self.subnet(network=network) as v4_subnet,\
+            self.port(subnet=v4_subnet, device_owner=device_owner) as port:
+            if insert_db_reference_error:
+                def db_ref_err_for_ipalloc(instance):
+                    if instance.__class__.__name__ == 'IPAllocation':
+                        raise db_exc.DBReferenceError(
+                            'dummy_table', 'dummy_constraint',
+                            'dummy_key', 'dummy_key_table')
+                mock.patch.object(orm.Session, 'add',
+                                  side_effect=db_ref_err_for_ipalloc).start()
+            # Add an IPv6 auto-address subnet to the network
+            v6_subnet = self._make_subnet(self.fmt, network, 'fe80::1',
+                                          'fe80::/64', ip_version=6,
+                                          ipv6_ra_mode=addr_mode,
+                                          ipv6_address_mode=addr_mode)
+            if (insert_db_reference_error
+                or 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.IPV6_SLAAC,
+            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.IPV6_SLAAC,
+            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.IPV6_SLAAC,
+            device_owner=constants.DEVICE_OWNER_ROUTER_SNAT)
+
+    def test_create_subnet_ipv6_slaac_with_db_reference_error(self):
+        self._test_create_subnet_ipv6_auto_addr_with_port_on_network(
+            constants.IPV6_SLAAC, insert_db_reference_error=True)
+
     def test_update_subnet_no_gateway(self):
         with self.subnet() as subnet:
             data = {'subnet': {'gateway_ip': '10.0.0.1'}}
@@ -5330,6 +5397,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'},
@@ -5338,6 +5406,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'}]