models_v2.IPAllocation).filter_by(
subnet_id=subnet_id).join(models_v2.Port).first()
+ def _subnet_check_ip_allocations_internal_router_ports(self, context,
+ subnet_id):
+ # Do not delete the subnet if IP allocations for internal
+ # router ports still exist
+ allocs = context.session.query(models_v2.IPAllocation).filter_by(
+ subnet_id=subnet_id).join(models_v2.Port).filter(
+ models_v2.Port.device_owner.in_(
+ constants.ROUTER_INTERFACE_OWNERS)
+ ).first()
+ if allocs:
+ LOG.debug("Subnet %s still has internal router ports, "
+ "cannot delete", subnet_id)
+ raise n_exc.SubnetInUse(subnet_id=id)
+
def delete_subnet(self, context, id):
with context.session.begin(subtransactions=True):
subnet = self._get_subnet(context, id)
# for IPv6 addresses which were automatically generated
# via SLAAC
is_auto_addr_subnet = ipv6_utils.is_auto_address_subnet(subnet)
- if not is_auto_addr_subnet:
+ if is_auto_addr_subnet:
+ self._subnet_check_ip_allocations_internal_router_ports(
+ context, id)
+ else:
qry_network_ports = (
qry_network_ports.filter(models_v2.Port.device_owner.
in_(AUTO_DELETE_PORT_OWNERS)))
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."""
+ def _create_slaac_subnet_and_port(self, port_owner=None):
+ # Create an IPv6 SLAAC subnet and a port using that 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'])
+ if port_owner:
+ res = self._create_port(self.fmt, net_id=network['network']['id'],
+ device_owner=port_owner)
+ else:
+ 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']))
sport = self.deserialize(self.fmt, req.get_response(self.api))
self.assertEqual(1, len(sport['port']['fixed_ips']))
+ return subnet, port
+
+ def test_delete_subnet_ipv6_slaac_port_exists(self):
+ """Test IPv6 SLAAC subnet delete when a port is still using subnet."""
+ subnet, port = self._create_slaac_subnet_and_port()
# Delete the subnet
req = self.new_delete_request('subnets', subnet['subnet']['id'])
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_subnet_ipv6_slaac_router_port_exists(self):
+ """Test IPv6 SLAAC subnet delete with a router port using the subnet"""
+ subnet, port = self._create_slaac_subnet_and_port(
+ constants.DEVICE_OWNER_ROUTER_INTF)
+ # Delete the subnet and assert that we get a HTTP 409 error
+ req = self.new_delete_request('subnets', subnet['subnet']['id'])
+ res = req.get_response(self.api)
+ self.assertEqual(webob.exc.HTTPConflict.code, res.status_int)
+ # The subnet should still exist and the port should still have an
+ # address from the subnet
+ req = self.new_show_request('subnets', subnet['subnet']['id'],
+ self.fmt)
+ res = req.get_response(self.api)
+ ssubnet = self.deserialize(self.fmt, req.get_response(self.api))
+ self.assertIsNotNone(ssubnet)
+ 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']))
+ port_subnet_ids = [fip['subnet_id'] for fip in
+ sport['port']['fixed_ips']]
+ self.assertIn(subnet['subnet']['id'], port_subnet_ids)
+
def test_delete_network(self):
gateway_ip = '10.0.0.1'
cidr = '10.0.0.0/24'
break
self.assertTrue(found)
+ def _test_router_delete_subnet_inuse_returns_409(self, router, subnet):
+ r, s = router, subnet
+ self._router_interface_action('add',
+ r['router']['id'],
+ s['subnet']['id'],
+ None)
+ # subnet cannot be deleted as it's attached to a router
+ self._delete('subnets', s['subnet']['id'],
+ expected_code=exc.HTTPConflict.code)
+ # remove interface so test can exit without errors
+ self._router_interface_action('remove',
+ r['router']['id'],
+ s['subnet']['id'],
+ None)
+
+ def _ipv6_subnet(self, mode):
+ return self.subnet(cidr='fd00::1/64', gateway_ip='fd00::1',
+ ip_version=6,
+ ipv6_ra_mode=mode,
+ ipv6_address_mode=mode)
+
def test_router_delete_subnet_inuse_returns_409(self):
with self.router() as r:
with self.subnet() as s:
- self._router_interface_action('add',
- r['router']['id'],
- s['subnet']['id'],
- None)
- # subnet cannot be delete as it's attached to a router
- self._delete('subnets', s['subnet']['id'],
- expected_code=exc.HTTPConflict.code)
- # remove interface so test can exit without errors
- self._router_interface_action('remove',
- r['router']['id'],
- s['subnet']['id'],
- None)
+ self._test_router_delete_subnet_inuse_returns_409(r, s)
+
+ def test_router_delete_ipv6_slaac_subnet_inuse_returns_409(self):
+ with self.router() as r:
+ with self._ipv6_subnet(l3_constants.IPV6_SLAAC) as s:
+ self._test_router_delete_subnet_inuse_returns_409(r, s)
+
+ def test_router_delete_dhcpv6_stateless_subnet_inuse_returns_409(self):
+ with self.router() as r:
+ with self._ipv6_subnet(l3_constants.DHCPV6_STATELESS) as s:
+ self._test_router_delete_subnet_inuse_returns_409(r, s)
def test_delete_ext_net_with_disassociated_floating_ips(self):
with self.network() as net: