From 6ee91e56c84ea6e930369f8649f1d2b50096cb80 Mon Sep 17 00:00:00 2001 From: John Davidge Date: Thu, 1 Oct 2015 20:37:34 +0100 Subject: [PATCH] Replace subnetpool config options with admin-only API This patch adds a new boolean 'is_default' property to subnetpools. This allows the admin to set the default v4/v6 subnetpools via the API rather than the existing neutron.conf options - which are deprecated by this patch. Only one subnetpool per IP family can be set to default. DocImpact ApiImpact Co-Authored-By: Carl Baldwin Change-Id: I5daba2347cfb91fac0b155b2c1b459ee7d9e4505 Closes-Bug: 1501328 --- etc/neutron.conf | 4 + etc/policy.json | 2 + neutron/api/v2/attributes.py | 7 ++ neutron/common/config.py | 10 ++- neutron/db/db_base_plugin_common.py | 1 + neutron/db/db_base_plugin_v2.py | 46 ++++++++-- .../alembic_migrations/versions/EXPAND_HEAD | 2 +- ...cfb89f881a_add_is_default_to_subnetpool.py | 36 ++++++++ neutron/db/models_v2.py | 1 + neutron/ipam/subnet_alloc.py | 3 +- neutron/tests/etc/policy.json | 2 + .../tests/unit/db/test_db_base_plugin_v2.py | 85 ++++++++++++++++++- neutron/tests/unit/ipam/test_subnet_alloc.py | 3 +- 13 files changed, 188 insertions(+), 14 deletions(-) create mode 100644 neutron/db/migration/alembic_migrations/versions/mitaka/expand/13cfb89f881a_add_is_default_to_subnetpool.py diff --git a/etc/neutron.conf b/etc/neutron.conf index 0575c03b6..53b149267 100644 --- a/etc/neutron.conf +++ b/etc/neutron.conf @@ -162,12 +162,16 @@ # CIDR must be passed to create a subnet and that subnet will not be allocated # from any pool; it will be considered part of the tenant's private address # space. +# This option is deprecated for removal in the N release. Please refrain from +# using it. # default_ipv4_subnet_pool = # Default Subnet Pool to be used for IPv6 subnet-allocation. # Specifies by UUID the pool to be used in case of subnet-create being # called without a subnet-pool ID. See the description for # default_ipv4_subnet_pool for more information. +# This option is deprecated for removal in the N release. Please refrain from +# using it. # default_ipv6_subnet_pool = # Set to True to enable IPv6 Prefix Delegation for subnet-allocation in a diff --git a/etc/policy.json b/etc/policy.json index 4aab8d519..c39ce3c40 100644 --- a/etc/policy.json +++ b/etc/policy.json @@ -22,8 +22,10 @@ "create_subnetpool": "", "create_subnetpool:shared": "rule:admin_only", + "create_subnetpool:is_default": "rule:admin_only", "get_subnetpool": "rule:admin_or_owner or rule:shared_subnetpools", "update_subnetpool": "rule:admin_or_owner", + "update_subnetpool:is_default": "rule:admin_only", "delete_subnetpool": "rule:admin_or_owner", "create_address_scope": "", diff --git a/neutron/api/v2/attributes.py b/neutron/api/v2/attributes.py index 6b3577427..acde4afdd 100644 --- a/neutron/api/v2/attributes.py +++ b/neutron/api/v2/attributes.py @@ -864,6 +864,13 @@ RESOURCE_ATTRIBUTE_MAP = { 'validate': {'type:non_negative': None}, 'convert_to': convert_to_int, 'is_visible': True}, + 'is_default': {'allow_post': True, + 'allow_put': True, + 'default': False, + 'convert_to': convert_to_boolean, + 'is_visible': True, + 'required_by_policy': True, + 'enforce_policy': True}, SHARED: {'allow_post': True, 'allow_put': False, 'default': False, diff --git a/neutron/common/config.py b/neutron/common/config.py index 1f07ba00a..f1b660024 100644 --- a/neutron/common/config.py +++ b/neutron/common/config.py @@ -72,12 +72,14 @@ core_opts = [ help=_("Maximum number of fixed ips per port. This option " "is deprecated and will be removed in the N " "release.")), - cfg.StrOpt('default_ipv4_subnet_pool', + cfg.StrOpt('default_ipv4_subnet_pool', deprecated_for_removal=True, help=_("Default IPv4 subnet-pool to be used for automatic " - "subnet CIDR allocation")), - cfg.StrOpt('default_ipv6_subnet_pool', + "subnet CIDR allocation. This option is deprecated for " + "removal in the N release.")), + cfg.StrOpt('default_ipv6_subnet_pool', deprecated_for_removal=True, help=_("Default IPv6 subnet-pool to be used for automatic " - "subnet CIDR allocation")), + "subnet CIDR allocation. This option is deprecated for " + "removal in the N release.")), cfg.BoolOpt('ipv6_pd_enabled', default=False, help=_("Enables IPv6 Prefix Delegation for automatic subnet " "CIDR allocation")), diff --git a/neutron/db/db_base_plugin_common.py b/neutron/db/db_base_plugin_common.py index 70ee8c1e9..c05101759 100644 --- a/neutron/db/db_base_plugin_common.py +++ b/neutron/db/db_base_plugin_common.py @@ -146,6 +146,7 @@ class DbBasePluginCommon(common_db_mixin.CommonDbMixin): 'default_prefixlen': default_prefixlen, 'min_prefixlen': min_prefixlen, 'max_prefixlen': max_prefixlen, + 'is_default': subnetpool['is_default'], 'shared': subnetpool['shared'], 'prefixes': [prefix['cidr'] for prefix in subnetpool['prefixes']], diff --git a/neutron/db/db_base_plugin_v2.py b/neutron/db/db_base_plugin_v2.py index bf032ada4..2b7845cc1 100644 --- a/neutron/db/db_base_plugin_v2.py +++ b/neutron/db/db_base_plugin_v2.py @@ -601,7 +601,7 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon, ipam_subnet) return self._make_subnet_dict(subnet, context=context) - def _get_subnetpool_id(self, subnet): + def _get_subnetpool_id(self, context, subnet): """Returns the subnetpool id for this request If the pool id was explicitly set in the request then that will be @@ -630,10 +630,18 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon, 'cidr and subnetpool_id') raise n_exc.BadRequest(resource='subnets', msg=msg) + if ip_version == 6 and cfg.CONF.ipv6_pd_enabled: + return constants.IPV6_PD_POOL_ID + + subnetpool = self.get_default_subnetpool(context, ip_version) + if subnetpool: + return subnetpool['id'] + + # Until the default_subnet_pool config options are removed in the N + # release, check for them after get_default_subnetpool returns None. + # TODO(john-davidge): Remove after Mitaka release. if ip_version == 4: return cfg.CONF.default_ipv4_subnet_pool - if cfg.CONF.ipv6_pd_enabled: - return constants.IPV6_PD_POOL_ID return cfg.CONF.default_ipv6_subnet_pool def create_subnet(self, context, subnet): @@ -654,7 +662,7 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon, subnet['subnet']['cidr'] = '%s/%s' % (net.network, net.prefixlen) s['tenant_id'] = self._get_tenant_id_for_create(context, s) - subnetpool_id = self._get_subnetpool_id(s) + subnetpool_id = self._get_subnetpool_id(context, s) if subnetpool_id: self.ipam.validate_pools_with_subnetpool(s) if subnetpool_id == constants.IPV6_PD_POOL_ID: @@ -929,6 +937,17 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon, 'address_scope_id': address_scope_id} raise n_exc.IllegalSubnetPoolUpdate(reason=msg) + def _check_default_subnetpool_exists(self, context, ip_version): + """Check if a default already exists for the given IP version. + + There can only be one default subnetpool for each IP family. Raise an + InvalidInput error if a default has already been set. + """ + if self.get_default_subnetpool(context, ip_version): + msg = _("A default subnetpool for this IP family has already " + "been set. Only one default may exist per IP family") + raise n_exc.InvalidInput(error_message=msg) + def create_subnetpool(self, context, subnetpool): """Create a subnetpool""" @@ -936,6 +955,9 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon, sp_reader = subnet_alloc.SubnetPoolReader(sp) if sp_reader.address_scope_id is attributes.ATTR_NOT_SPECIFIED: sp_reader.address_scope_id = None + if sp_reader.is_default: + self._check_default_subnetpool_exists(context, + sp_reader.ip_version) self._validate_address_scope_id(context, sp_reader.address_scope_id, id, sp_reader.prefixes) tenant_id = self._get_tenant_id_for_create(context, sp) @@ -948,6 +970,7 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon, sp_reader.default_prefixlen, 'min_prefixlen': sp_reader.min_prefixlen, 'max_prefixlen': sp_reader.max_prefixlen, + 'is_default': sp_reader.is_default, 'shared': sp_reader.shared, 'default_quota': sp_reader.default_quota, 'address_scope_id': sp_reader.address_scope_id} @@ -986,8 +1009,8 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon, updated['prefixes'] = orig_prefixes for key in ['id', 'name', 'ip_version', 'min_prefixlen', - 'max_prefixlen', 'default_prefixlen', 'shared', - 'default_quota', 'address_scope_id']: + 'max_prefixlen', 'default_prefixlen', 'is_default', + 'shared', 'default_quota', 'address_scope_id']: self._write_key(key, updated, model, new_pool) return updated @@ -1008,6 +1031,9 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon, updated = self._updated_subnetpool_dict(orig_sp, new_sp) updated['tenant_id'] = orig_sp.tenant_id reader = subnet_alloc.SubnetPoolReader(updated) + if reader.is_default and not orig_sp.is_default: + self._check_default_subnetpool_exists(context, + reader.ip_version) if orig_sp.address_scope_id: self._check_subnetpool_update_allowed(context, id, orig_sp.address_scope_id) @@ -1044,6 +1070,14 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon, page_reverse=page_reverse) return collection + def get_default_subnetpool(self, context, ip_version): + """Retrieve the default subnetpool for the given IP version.""" + filters = {'is_default': [True], + 'ip_version': [ip_version]} + subnetpool = self.get_subnetpools(context, filters=filters) + if subnetpool: + return subnetpool[0] + def delete_subnetpool(self, context, id): """Delete a subnetpool.""" with context.session.begin(subtransactions=True): diff --git a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD b/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD index e732495bb..d96571365 100644 --- a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD +++ b/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD @@ -1 +1 @@ -59cb5b6cf4d +13cfb89f881a diff --git a/neutron/db/migration/alembic_migrations/versions/mitaka/expand/13cfb89f881a_add_is_default_to_subnetpool.py b/neutron/db/migration/alembic_migrations/versions/mitaka/expand/13cfb89f881a_add_is_default_to_subnetpool.py new file mode 100644 index 000000000..6e7d9f749 --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/mitaka/expand/13cfb89f881a_add_is_default_to_subnetpool.py @@ -0,0 +1,36 @@ +# Copyright 2015 Cisco Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""add is_default to subnetpool + +Revision ID: 13cfb89f881a +Revises: 59cb5b6cf4d +Create Date: 2015-09-30 15:58:31.170153 + +""" + +# revision identifiers, used by Alembic. +revision = '13cfb89f881a' +down_revision = '59cb5b6cf4d' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.add_column('subnetpools', + sa.Column('is_default', + sa.Boolean(), + nullable=False)) diff --git a/neutron/db/models_v2.py b/neutron/db/models_v2.py index 9ecba83ba..c6468110e 100644 --- a/neutron/db/models_v2.py +++ b/neutron/db/models_v2.py @@ -236,6 +236,7 @@ class SubnetPool(model_base.BASEV2, HasId, HasTenant): min_prefixlen = sa.Column(sa.Integer, nullable=False) max_prefixlen = sa.Column(sa.Integer, nullable=False) shared = sa.Column(sa.Boolean, nullable=False) + is_default = sa.Column(sa.Boolean, nullable=False) default_quota = sa.Column(sa.Integer, nullable=True) hash = sa.Column(sa.String(36), nullable=False, server_default='') address_scope_id = sa.Column(sa.String(36), nullable=True) diff --git a/neutron/ipam/subnet_alloc.py b/neutron/ipam/subnet_alloc.py index 9711e89f8..845fe1fab 100644 --- a/neutron/ipam/subnet_alloc.py +++ b/neutron/ipam/subnet_alloc.py @@ -226,7 +226,7 @@ class SubnetPoolReader(object): self._read_id(subnetpool) self._read_prefix_bounds(subnetpool) self._read_attrs(subnetpool, - ['tenant_id', 'name', 'shared']) + ['tenant_id', 'name', 'is_default', 'shared']) self._read_address_scope(subnetpool) self.subnetpool = {'id': self.id, 'name': self.name, @@ -240,6 +240,7 @@ class SubnetPoolReader(object): 'default_prefixlen': self.default_prefixlen, 'default_quota': self.default_quota, 'address_scope_id': self.address_scope_id, + 'is_default': self.is_default, 'shared': self.shared} def _read_attrs(self, subnetpool, keys): diff --git a/neutron/tests/etc/policy.json b/neutron/tests/etc/policy.json index 4aab8d519..c39ce3c40 100644 --- a/neutron/tests/etc/policy.json +++ b/neutron/tests/etc/policy.json @@ -22,8 +22,10 @@ "create_subnetpool": "", "create_subnetpool:shared": "rule:admin_only", + "create_subnetpool:is_default": "rule:admin_only", "get_subnetpool": "rule:admin_or_owner or rule:shared_subnetpools", "update_subnetpool": "rule:admin_or_owner", + "update_subnetpool:is_default": "rule:admin_only", "delete_subnetpool": "rule:admin_or_owner", "create_address_scope": "", 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 cc87fc1bf..b49a6ce2a 100644 --- a/neutron/tests/unit/db/test_db_base_plugin_v2.py +++ b/neutron/tests/unit/db/test_db_base_plugin_v2.py @@ -2823,6 +2823,30 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase): self.assertEqual(res.status_int, webob.exc.HTTPClientError.code) def test_create_subnet_only_ip_version_v4(self): + with self.network() as network: + tenant_id = network['network']['tenant_id'] + subnetpool_prefix = '10.0.0.0/8' + with self.subnetpool(prefixes=[subnetpool_prefix], + admin=True, + name="My subnet pool", + tenant_id=tenant_id, + min_prefixlen='25', + is_default=True) as subnetpool: + subnetpool_id = subnetpool['subnetpool']['id'] + data = {'subnet': {'network_id': network['network']['id'], + 'ip_version': '4', + 'prefixlen': '27', + 'tenant_id': tenant_id}} + subnet_req = self.new_create_request('subnets', data) + res = subnet_req.get_response(self.api) + subnet = self.deserialize(self.fmt, res)['subnet'] + ip_net = netaddr.IPNetwork(subnet['cidr']) + self.assertIn(ip_net, netaddr.IPNetwork(subnetpool_prefix)) + self.assertEqual(27, ip_net.prefixlen) + self.assertEqual(subnetpool_id, subnet['subnetpool_id']) + + def test_create_subnet_only_ip_version_v4_old(self): + # TODO(john-davidge): Remove after Mitaka release. with self.network() as network: tenant_id = network['network']['tenant_id'] subnetpool_prefix = '10.0.0.0/8' @@ -2847,6 +2871,30 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase): self.assertEqual(subnetpool_id, subnet['subnetpool_id']) def test_create_subnet_only_ip_version_v6(self): + with self.network() as network: + tenant_id = network['network']['tenant_id'] + subnetpool_prefix = '2000::/56' + with self.subnetpool(prefixes=[subnetpool_prefix], + admin=True, + name="My ipv6 subnet pool", + tenant_id=tenant_id, + min_prefixlen='64', + is_default=True) as subnetpool: + subnetpool_id = subnetpool['subnetpool']['id'] + cfg.CONF.set_override('ipv6_pd_enabled', False) + data = {'subnet': {'network_id': network['network']['id'], + 'ip_version': '6', + 'tenant_id': tenant_id}} + subnet_req = self.new_create_request('subnets', data) + res = subnet_req.get_response(self.api) + subnet = self.deserialize(self.fmt, res)['subnet'] + self.assertEqual(subnetpool_id, subnet['subnetpool_id']) + ip_net = netaddr.IPNetwork(subnet['cidr']) + self.assertIn(ip_net, netaddr.IPNetwork(subnetpool_prefix)) + self.assertEqual(64, ip_net.prefixlen) + + def test_create_subnet_only_ip_version_v6_old(self): + # TODO(john-davidge): Remove after Mitaka release. with self.network() as network: tenant_id = network['network']['tenant_id'] subnetpool_prefix = '2000::/56' @@ -2856,9 +2904,9 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase): tenant_id=tenant_id, min_prefixlen='64') as subnetpool: subnetpool_id = subnetpool['subnetpool']['id'] - cfg.CONF.set_override('ipv6_pd_enabled', False) cfg.CONF.set_override('default_ipv6_subnet_pool', subnetpool_id) + cfg.CONF.set_override('ipv6_pd_enabled', False) data = {'subnet': {'network_id': network['network']['id'], 'ip_version': '6', 'tenant_id': tenant_id}} @@ -4858,6 +4906,9 @@ class TestSubnetPoolsV2(NeutronDbPluginV2TestCase): def _validate_max_prefix(self, prefix, subnetpool): self.assertEqual(subnetpool['subnetpool']['max_prefixlen'], prefix) + def _validate_is_default(self, subnetpool): + self.assertTrue(subnetpool['subnetpool']['is_default']) + def test_create_subnetpool_empty_prefix_list(self): self.assertRaises(webob.exc.HTTPClientError, self._test_create_subnetpool, @@ -4866,6 +4917,38 @@ class TestSubnetPoolsV2(NeutronDbPluginV2TestCase): tenant_id=self._tenant_id, min_prefixlen='21') + def test_create_default_subnetpools(self): + for cidr, min_prefixlen in (['fe80::/48', '64'], + ['10.10.10.0/24', '24']): + pool = self._test_create_subnetpool([cidr], + admin=True, + tenant_id=self._tenant_id, + name=self._POOL_NAME, + min_prefixlen=min_prefixlen, + is_default=True) + self._validate_is_default(pool) + + def test_cannot_create_multiple_default_subnetpools(self): + for cidr1, cidr2, min_prefixlen in (['fe80::/48', '2001::/48', '64'], + ['10.10.10.0/24', '10.10.20.0/24', + '24']): + + pool = self._test_create_subnetpool([cidr1], + admin=True, + tenant_id=self._tenant_id, + name=self._POOL_NAME, + min_prefixlen=min_prefixlen, + is_default=True) + self._validate_is_default(pool) + self.assertRaises(webob.exc.HTTPClientError, + self._test_create_subnetpool, + [cidr2], + admin=True, + tenant_id=self._tenant_id, + name=self._POOL_NAME, + min_prefixlen=min_prefixlen, + is_default=True) + def test_create_subnetpool_ipv4_24_with_defaults(self): subnet = netaddr.IPNetwork('10.10.10.0/24') subnetpool = self._test_create_subnetpool([subnet.cidr], diff --git a/neutron/tests/unit/ipam/test_subnet_alloc.py b/neutron/tests/unit/ipam/test_subnet_alloc.py index 79e168cef..3351d2206 100644 --- a/neutron/tests/unit/ipam/test_subnet_alloc.py +++ b/neutron/tests/unit/ipam/test_subnet_alloc.py @@ -43,7 +43,7 @@ class TestSubnetAllocation(testlib_api.SqlTestCase): max_prefixlen=attributes.ATTR_NOT_SPECIFIED, default_prefixlen=attributes.ATTR_NOT_SPECIFIED, default_quota=attributes.ATTR_NOT_SPECIFIED, - shared=False): + shared=False, is_default=False): subnetpool = {'subnetpool': {'name': name, 'tenant_id': self._tenant_id, 'prefixes': prefix_list, @@ -51,6 +51,7 @@ class TestSubnetAllocation(testlib_api.SqlTestCase): 'max_prefixlen': max_prefixlen, 'default_prefixlen': default_prefixlen, 'shared': shared, + 'is_default': is_default, 'default_quota': default_quota}} return plugin.create_subnetpool(ctx, subnetpool) -- 2.45.2