From: Dane LeBlanc Date: Thu, 9 Apr 2015 14:32:33 +0000 (-0400) Subject: IPv6 SLAAC subnet create should update ports on net X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=1dc98e414f200a78a6b1dc78f222c588646e6935;p=openstack-build%2Fneutron-build.git 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: I88d04a13ce5b8ed4c88eac734e589e8a90e986a0 Closes-Bug: 1427474 Closes-Bug: 1441382 Closes-Bug: 1440183 (cherry picked from commit bd1044ba0e9d7d0f4752c891ac340b115f0019c4) --- diff --git a/neutron/db/db_base_plugin_v2.py b/neutron/db/db_base_plugin_v2.py index f7bcf8db5..dcf7adc6f 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. @@ -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) diff --git a/neutron/tests/unit/db/test_db_base_plugin_v2.py b/neutron/tests/unit/db/test_db_base_plugin_v2.py index 0dc18001d..e8ef97e8f 100644 --- a/neutron/tests/unit/db/test_db_base_plugin_v2.py +++ b/neutron/tests/unit/db/test_db_base_plugin_v2.py @@ -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'}]