]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Replace subnetpool config options with admin-only API
authorJohn Davidge <jodavidg@cisco.com>
Thu, 1 Oct 2015 19:37:34 +0000 (20:37 +0100)
committerJohn Davidge <jodavidg@cisco.com>
Fri, 6 Nov 2015 17:16:31 +0000 (17:16 +0000)
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 <carl@ecbaldwin.net>
Change-Id: I5daba2347cfb91fac0b155b2c1b459ee7d9e4505
Closes-Bug: 1501328

13 files changed:
etc/neutron.conf
etc/policy.json
neutron/api/v2/attributes.py
neutron/common/config.py
neutron/db/db_base_plugin_common.py
neutron/db/db_base_plugin_v2.py
neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD
neutron/db/migration/alembic_migrations/versions/mitaka/expand/13cfb89f881a_add_is_default_to_subnetpool.py [new file with mode: 0644]
neutron/db/models_v2.py
neutron/ipam/subnet_alloc.py
neutron/tests/etc/policy.json
neutron/tests/unit/db/test_db_base_plugin_v2.py
neutron/tests/unit/ipam/test_subnet_alloc.py

index 0575c03b6e47cc602953a26393620c2aa861d3dc..53b149267d05f254a0f23784e24606422b767228 100644 (file)
 # 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
index 4aab8d5190dc9a9602478bfb6b279ae27e8d46ac..c39ce3c40dda6a7f5ff26829a6860a19ddf72475 100644 (file)
 
     "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": "",
index 6b35774272a969bfe60e1737340445c8a55ce4b0..acde4afdd9c2cf2bd4240bcb87142711fa99900e 100644 (file)
@@ -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,
index 1f07ba00a0da966acff926db62d9288831db81f4..f1b660024c07c61e1c8d5a59ab2adc6bb61f9ce1 100644 (file)
@@ -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")),
index 70ee8c1e9a6e07d8c5999673c0e4288d2c7a4c10..c05101759eac62e3d3f4d505d1a1dff4f336caf0 100644 (file)
@@ -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']],
index bf032ada4c959ec36f5afcf7e2086863bc7a7fc9..2b7845cc1d1cf10d8554802b09b0b0bd23eeb2c7 100644 (file)
@@ -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):
index e732495bbe90e24aa84ca0065600c27c42eaced0..d96571365e9559598ec985196616eed476f70c4a 100644 (file)
@@ -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 (file)
index 0000000..6e7d9f7
--- /dev/null
@@ -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))
index 9ecba83ba9c5c1a46de62403f02dc599a513337b..c6468110e87842427e6ba5423365d4f5d989e231 100644 (file)
@@ -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)
index 9711e89f8ff6370a96e554c1e14b7173421adb2f..845fe1fab0ae6edc8354050367e20ea932afc42e 100644 (file)
@@ -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):
index 4aab8d5190dc9a9602478bfb6b279ae27e8d46ac..c39ce3c40dda6a7f5ff26829a6860a19ddf72475 100644 (file)
 
     "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": "",
index cc87fc1bf0f0fc5c092145d430fdf963866d8f02..b49a6ce2a2cab4b5cdc59ba7005957326e8b4725 100644 (file)
@@ -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],
index 79e168cef8806eaa0a2bd54e18a8d6970c3227ed..3351d22063c94566edbf074fb11a32989826baa0 100644 (file)
@@ -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)