From ce5e95723925b0d7750bb3daa5be54765985cc30 Mon Sep 17 00:00:00 2001 From: Dane LeBlanc Date: Mon, 17 Nov 2014 10:40:04 -0500 Subject: [PATCH] Subnet delete for IPv6 SLAAC should not require prior port disassoc With the current Neutron implementation, a subnet cannot be deleted until all associated IP addresses have been remove from ports (via port update) or the associated ports/VMs have been deleted. In the case of SLAAC-enabled subnets, however, it's not feasible to require removal of SLAAC-generated addresses individually from each associated port before deleting a subnet because of the multicast nature of RA messages. For SLAAC-enabled subnets, the processing of subnet delete requests needs to be changed so that these subnets will be allowed to be deleted, and all ports get disassociated from their corresponding SLAAC IP address, when there are ports existing on the SLAAC subnet. Change-Id: I281f5a1553248e09174dc49d0a42aef4b5c44bee Closes-Bug: 1393435 --- neutron/db/db_base_plugin_v2.py | 8 ++++++-- neutron/plugins/ml2/plugin.py | 4 +++- neutron/tests/unit/test_db_plugin.py | 30 ++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/neutron/db/db_base_plugin_v2.py b/neutron/db/db_base_plugin_v2.py index 70827c58a..903332de0 100644 --- a/neutron/db/db_base_plugin_v2.py +++ b/neutron/db/db_base_plugin_v2.py @@ -1276,9 +1276,13 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2, filter_by(network_id=subnet['network_id']). with_lockmode('update')) - # remove network owned ports + # Remove network owned ports, and delete IP allocations + # for IPv6 addresses which were automatically generated + # via SLAAC + is_ipv6_slaac_subnet = ipv6_utils.is_slaac_subnet(subnet) for a in allocated: - if a.ports.device_owner in AUTO_DELETE_PORT_OWNERS: + if (is_ipv6_slaac_subnet or + a.ports.device_owner in AUTO_DELETE_PORT_OWNERS): NeutronDbPluginV2._delete_ip_allocation( context, subnet.network_id, id, a.ip_address) else: diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py index 0f596a66c..09c6e9523 100644 --- a/neutron/plugins/ml2/plugin.py +++ b/neutron/plugins/ml2/plugin.py @@ -31,6 +31,7 @@ from neutron.api.rpc.handlers import securitygroups_rpc from neutron.api.v2 import attributes from neutron.common import constants as const from neutron.common import exceptions as exc +from neutron.common import ipv6_utils from neutron.common import rpc as n_rpc from neutron.common import topics from neutron.common import utils @@ -729,7 +730,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, LOG.debug(_("Ports to auto-deallocate: %s"), allocated) only_auto_del = all(not a.port_id or a.ports.device_owner in db_base_plugin_v2. - AUTO_DELETE_PORT_OWNERS + AUTO_DELETE_PORT_OWNERS or + ipv6_utils.is_slaac_subnet(subnet) for a in allocated) if not only_auto_del: LOG.debug(_("Tenant-owned ports exist")) diff --git a/neutron/tests/unit/test_db_plugin.py b/neutron/tests/unit/test_db_plugin.py index 8a4f3ec5a..689e3ce56 100644 --- a/neutron/tests/unit/test_db_plugin.py +++ b/neutron/tests/unit/test_db_plugin.py @@ -2641,6 +2641,36 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase): self.assertEqual(res.status_int, webob.exc.HTTPNoContent.code) + def test_delete_subnet_ipv6_slaac_port_exists(self): + """Test IPv6 SLAAC subnet delete when a port is still using subnet.""" + res = self._create_network(fmt=self.fmt, name='net', + admin_state_up=True) + network = self.deserialize(self.fmt, res) + # Create an IPv6 SLAAC subnet and a port using that subnet + subnet = self._make_subnet(self.fmt, network, gateway='fe80::1', + cidr='fe80::/64', ip_version=6, + ipv6_ra_mode=constants.IPV6_SLAAC, + ipv6_address_mode=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'])) + + # The port should have an address from the subnet + req = self.new_show_request('ports', port['port']['id'], self.fmt) + res = req.get_response(self.api) + sport = self.deserialize(self.fmt, req.get_response(self.api)) + self.assertEqual(1, len(sport['port']['fixed_ips'])) + + # Delete the subnet + req = self.new_delete_request('subnets', subnet['subnet']['id']) + res = req.get_response(self.api) + self.assertEqual(webob.exc.HTTPNoContent.code, res.status_int) + # The port should no longer have an address from the deleted subnet + req = self.new_show_request('ports', port['port']['id'], self.fmt) + res = req.get_response(self.api) + sport = self.deserialize(self.fmt, req.get_response(self.api)) + self.assertEqual(0, len(sport['port']['fixed_ips'])) + def test_delete_network(self): gateway_ip = '10.0.0.1' cidr = '10.0.0.0/24' -- 2.45.2