diff --git a/neutron/common/exceptions.py b/neutron/common/exceptions.py index 227cf082150..09f27512863 100644 --- a/neutron/common/exceptions.py +++ b/neutron/common/exceptions.py @@ -457,3 +457,8 @@ class SubnetPoolQuotaExceeded(OverQuota): class DeviceNotFoundError(NeutronException): message = _("Device '%(device_name)s' does not exist") + + +class NetworkSubnetPoolAffinityError(BadRequest): + message = _("Subnets hosted on the same network must be allocated from " + "the same subnet pool") diff --git a/neutron/db/db_base_plugin_v2.py b/neutron/db/db_base_plugin_v2.py index 74dabca7ffa..e755754c6b1 100644 --- a/neutron/db/db_base_plugin_v2.py +++ b/neutron/db/db_base_plugin_v2.py @@ -641,6 +641,16 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2, 'cidr': subnet.cidr}) raise n_exc.InvalidInput(error_message=err_msg) + def _validate_network_subnetpools(self, network, + new_subnetpool_id, ip_version): + """Validate all subnets on the given network have been allocated from + the same subnet pool as new_subnetpool_id + """ + for subnet in network.subnets: + if (subnet.ip_version == ip_version and + new_subnetpool_id != subnet.subnetpool_id): + raise n_exc.NetworkSubnetPoolAffinityError() + def _validate_allocation_pools(self, ip_pools, subnet_cidr): """Validate IP allocation pools. @@ -1191,6 +1201,9 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2, allocation_pools) self._validate_subnet_cidr(context, network, subnet_args['cidr']) + self._validate_network_subnetpools(network, + subnet_args['subnetpool_id'], + subnet_args['ip_version']) subnet = models_v2.Subnet(**subnet_args) context.session.add(subnet) diff --git a/neutron/tests/api/test_subnetpools.py b/neutron/tests/api/test_subnetpools.py index 73aad680617..454dfb86b78 100644 --- a/neutron/tests/api/test_subnetpools.py +++ b/neutron/tests/api/test_subnetpools.py @@ -256,3 +256,20 @@ class SubnetPoolsTestV6(SubnetPoolsTest): prefixes = [u'2001:db8:3::/48'] cls._subnetpool_data = {'subnetpool': {'min_prefixlen': min_prefixlen, 'prefixes': prefixes}} + + @test.attr(type='smoke') + @test.idempotent_id('f62d73dc-cf6f-4879-b94b-dab53982bf3b') + def test_create_dual_stack_subnets_from_subnetpools(self): + pool_id_v6, subnet_v6 = self._create_subnet_from_pool() + self.addCleanup(self.client.delete_subnet, subnet_v6['id']) + pool_values_v4 = {'prefixes': ['192.168.0.0/16'], + 'min_prefixlen': 21, + 'max_prefixlen': 32} + pool_name_v4, pool_id_v4 = self._create_subnetpool(self.client, + pool_values=pool_values_v4) + subnet_v4 = self.client.create_subnet( + network_id=subnet_v6['network_id'], + ip_version=4, + subnetpool_id=pool_id_v4)['subnet'] + self.addCleanup(self.client.delete_subnet, subnet_v4['id']) + self.assertEqual(subnet_v4['network_id'], subnet_v6['network_id']) diff --git a/neutron/tests/api/test_subnetpools_negative.py b/neutron/tests/api/test_subnetpools_negative.py index 52424499a0e..c82c6f87273 100644 --- a/neutron/tests/api/test_subnetpools_negative.py +++ b/neutron/tests/api/test_subnetpools_negative.py @@ -118,3 +118,24 @@ class SubnetPoolsNegativeTestJSON(base.BaseNetworkTest): self.assertRaises(lib_exc.BadRequest, self.client.update_subnetpool, pool_id, subnetpool_data) + + @test.attr(type=['negative', 'smoke']) + @test.idempotent_id('fc011824-153e-4469-97ad-9808eb88cae1') + def test_create_subnet_different_pools_same_network(self): + network = self.create_network(network_name='smoke-network') + subnetpool_data = {'prefixes': ['192.168.0.0/16'], + 'name': 'test-pool'} + pool_id = self._create_subnetpool(self.admin_client, subnetpool_data) + subnet = self.admin_client.create_subnet( + network_id=network['id'], + cidr='10.10.10.0/24', + ip_version=4, + gateway_ip=None) + subnet_id = subnet['subnet']['id'] + self.addCleanup(self.admin_client.delete_subnet, subnet_id) + self.addCleanup(self.admin_client.delete_subnetpool, pool_id) + self.assertRaises(lib_exc.BadRequest, + self.admin_client.create_subnet, + network_id=network['id'], + ip_version=4, + subnetpool_id=pool_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 a088e352270..680b1d59c9a 100644 --- a/neutron/tests/unit/db/test_db_base_plugin_v2.py +++ b/neutron/tests/unit/db/test_db_base_plugin_v2.py @@ -5535,6 +5535,15 @@ class NeutronDbPluginV2AsMixinTestCase(NeutronDbPluginV2TestCase, subnet['subnet']['id']) self.assertIsNone(res) + def test__validate_network_subnetpools(self): + network = models_v2.Network() + network.subnets = [models_v2.Subnet(subnetpool_id='test_id', + ip_version=4)] + new_subnetpool_id = None + self.assertRaises(n_exc.NetworkSubnetPoolAffinityError, + self.plugin._validate_network_subnetpools, + network, new_subnetpool_id, 4) + class TestNetworks(testlib_api.SqlTestCase): def setUp(self):