]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Implement default subnet pool configuration settings
authorCarl Baldwin <carl.baldwin@hp.com>
Thu, 26 Mar 2015 18:10:10 +0000 (18:10 +0000)
committerCarl Baldwin <carl.baldwin@hp.com>
Wed, 1 Apr 2015 16:05:57 +0000 (16:05 +0000)
The default_ipv6_subnet_pool option was added [1] as an integration
point between prefix delegation work and subnet allocation work.  This
patch completes the integration with subnet allocation.  This
addresses the use case where a deployer wants all ipv6 addresses to
come -- by default -- from a globally routable pool of ipv6 addresses.

In a deployment with this option set, an API user can still access the
old behavior by passing None explicitly as subnetpool_id when creating
a subnet.

This patch also adds the default_ipv4_subnet_pool for completeness.

[1] https://review.openstack.org/#/c/166973

Change-Id: I301189b5cd31d7c5fa4a40fa3e04f8e6ac77592b
Partially-Implements: blueprint subnet-allocation

etc/neutron.conf
neutron/api/v2/attributes.py
neutron/common/config.py
neutron/db/db_base_plugin_v2.py
neutron/tests/unit/opencontrail/test_contrail_plugin.py
neutron/tests/unit/test_db_plugin.py

index e25d8e253754e0f91f0afe4e694cbaad69ec635d..a2c281500e0c37903c006d4360cf30386aff8993 100644 (file)
@@ -146,10 +146,20 @@ lock_path = $state_path/lock
 # Maximum number of routes per router
 # max_routes = 30
 
+# Default Subnet Pool to be used for IPv4 subnet-allocation.
+# Specifies by UUID the pool to be used in case of subnet-create being called
+# without a subnet-pool ID.  The default of None means that no pool will be
+# used unless passed explicitly to subnet create.  If no pool is used, then a
+# 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.
+# 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 CIDR or subnet-pool ID. Set to "prefix_delegation"
+# called without a subnet-pool ID.  Set to "prefix_delegation"
 # to enable IPv6 Prefix Delegation in a PD-capable environment.
+# See the description for default_ipv4_subnet_pool for more information.
 # default_ipv6_subnet_pool =
 
 # =========== items for MTU selection and advertisement =============
index 1ccbf779d63d600b6f901cd396e32d6d71e161fd..a7cefecf59308428eb3e15d8a5a716f3cea796bd 100644 (file)
@@ -782,7 +782,7 @@ RESOURCE_ATTRIBUTE_MAP = {
                           'allow_put': False,
                           'default': ATTR_NOT_SPECIFIED,
                           'required_by_policy': False,
-                          'validate': {'type:uuid': None},
+                          'validate': {'type:uuid_or_none': None},
                           'is_visible': True},
         'prefixlen': {'allow_post': True,
                       'allow_put': False,
index 3a17e042d50afa66a91964a8b1280c197f62e66d..2837b4ca69565705436578e099c0c9e3a2b41d65 100644 (file)
@@ -71,9 +71,12 @@ core_opts = [
                help=_("Maximum number of host routes per subnet")),
     cfg.IntOpt('max_fixed_ips_per_port', default=5,
                help=_("Maximum number of fixed ips per port")),
-    cfg.IntOpt('default_ipv6_subnet_pool', default=None,
-               help=_("Default subnet-pool to be used for automatic subnet "
-                      "CIDR allocation")),
+    cfg.StrOpt('default_ipv4_subnet_pool', default=None,
+               help=_("Default IPv4 subnet-pool to be used for automatic "
+                      "subnet CIDR allocation")),
+    cfg.StrOpt('default_ipv6_subnet_pool', default=None,
+               help=_("Default IPv6 subnet-pool to be used for automatic "
+                      "subnet CIDR allocation")),
     cfg.IntOpt('dhcp_lease_duration', default=86400,
                deprecated_name='dhcp_lease_time',
                help=_("DHCP lease duration (in seconds). Use -1 to tell "
index ea3461b2a1307627ff6d48bdef336a948aa6233c..255544e14dfb9861edc1d55c7554491801f1533b 100644 (file)
@@ -1212,7 +1212,7 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
     @oslo_db_api.wrap_db_retry(max_retries=db_api.MAX_RETRIES,
                                retry_on_request=True,
                                retry_on_deadlock=True)
-    def _create_subnet_from_pool(self, context, subnet):
+    def _create_subnet_from_pool(self, context, subnet, subnetpool_id):
         s = subnet['subnet']
         tenant_id = self._get_tenant_id_for_create(context, s)
         has_allocpool = attributes.is_attr_set(s['allocation_pools'])
@@ -1223,7 +1223,7 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
             raise n_exc.BadRequest(resource='subnets', msg=reason)
 
         with context.session.begin(subtransactions=True):
-            subnetpool = self._get_subnetpool(context, s['subnetpool_id'])
+            subnetpool = self._get_subnetpool(context, subnetpool_id)
             network = self._get_network(context, s["network_id"])
             allocator = subnet_alloc.SubnetAllocator(subnetpool)
             req = self._make_subnet_request(tenant_id, s, subnetpool)
@@ -1273,6 +1273,39 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
                                          subnet['network_id'])
         return self._make_subnet_dict(subnet)
 
+    def _get_subnetpool_id(self, subnet):
+        """Returns the subnetpool id for this request
+
+        If the pool id was explicitly set in the request then that will be
+        returned, even if it is None.
+
+        Otherwise, the default pool for the IP version requested will be
+        returned.  This will either be a pool id or None (the default for each
+        configuration parameter).  This implies that the ip version must be
+        either set implicitly with a specific cidr or explicitly using
+        ip_version attribute.
+
+        :param subnet: The subnet dict from the request
+        """
+        subnetpool_id = subnet.get('subnetpool_id',
+                                   attributes.ATTR_NOT_SPECIFIED)
+        if subnetpool_id != attributes.ATTR_NOT_SPECIFIED:
+            return subnetpool_id
+
+        cidr = subnet.get('cidr')
+        if attributes.is_attr_set(cidr):
+            ip_version = netaddr.IPNetwork(cidr).version
+        else:
+            ip_version = subnet.get('ip_version')
+            if not attributes.is_attr_set(ip_version):
+                msg = _('ip_version must be specified in the absence of '
+                        'cidr and subnetpool_id')
+                raise n_exc.BadRequest(resource='subnets', msg=msg)
+
+        if ip_version == 4:
+            return cfg.CONF.default_ipv4_subnet_pool
+        return cfg.CONF.default_ipv6_subnet_pool
+
     def create_subnet(self, context, subnet):
 
         s = subnet['subnet']
@@ -1290,11 +1323,15 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
             net = netaddr.IPNetwork(s['cidr'])
             subnet['subnet']['cidr'] = '%s/%s' % (net.network, net.prefixlen)
 
-        subnetpool_id = s.get('subnetpool_id', attributes.ATTR_NOT_SPECIFIED)
-        if not attributes.is_attr_set(subnetpool_id):
+        subnetpool_id = self._get_subnetpool_id(s)
+        if not subnetpool_id:
+            if not has_cidr:
+                msg = _('A cidr must be specified in the absence of a '
+                        'subnet pool')
+                raise n_exc.BadRequest(resource='subnets', msg=msg)
             # Create subnet from the implicit(AKA null) pool
             return self._create_subnet_from_implicit_pool(context, subnet)
-        return self._create_subnet_from_pool(context, subnet)
+        return self._create_subnet_from_pool(context, subnet, subnetpool_id)
 
     def _update_subnet_dns_nameservers(self, context, id, s):
         old_dns_list = self._get_dns_by_subnet(context, id)
index f05407012b8fcb69f496c16f0187dec6dc1db450..926b040de6667d413be6ef7ad6b1076a59e4efde 100644 (file)
@@ -215,6 +215,8 @@ class ContrailPluginTestCase(test_plugin.NeutronDbPluginV2TestCase):
     def setUp(self, plugin=None, ext_mgr=None):
         if 'v6' in self._testMethodName:
             self.skipTest("OpenContrail Plugin does not support IPV6.")
+        if 'test_create_subnet_only_ip_version' in self._testMethodName:
+            self.skipTest("OpenContrail Plugin does not support subnet pools.")
         cfg.CONF.keystone_authtoken = KeyStoneInfo()
         mock.patch('requests.post').start().side_effect = FAKE_SERVER.request
         super(ContrailPluginTestCase, self).setUp(self._plugin_name)
index 077f4e90fb3dc25f741fe2453a28b62db83127af..7f5c8a8484c1ea6bd11d453ad7e8d22f943f5031 100644 (file)
@@ -2811,6 +2811,74 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
             res = subnet_req.get_response(self.api)
             self.assertEqual(res.status_int, webob.exc.HTTPClientError.code)
 
+    def test_create_subnet_no_ip_version(self):
+        with self.network() as network:
+            cfg.CONF.set_override('default_ipv4_subnet_pool', None)
+            cfg.CONF.set_override('default_ipv6_subnet_pool', None)
+            data = {'subnet': {'network_id': network['network']['id'],
+                    'tenant_id': network['network']['tenant_id']}}
+            subnet_req = self.new_create_request('subnets', data)
+            res = subnet_req.get_response(self.api)
+            self.assertEqual(res.status_int, webob.exc.HTTPClientError.code)
+
+    def test_create_subnet_only_ip_version_v6_no_pool(self):
+        with self.network() as network:
+            tenant_id = network['network']['tenant_id']
+            cfg.CONF.set_override('default_ipv6_subnet_pool', None)
+            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)
+            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=False,
+                                 name="My subnet pool",
+                                 tenant_id=tenant_id,
+                                 min_prefixlen='25') as subnetpool:
+                subnetpool_id = subnetpool['subnetpool']['id']
+                cfg.CONF.set_override('default_ipv4_subnet_pool',
+                                      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.assertTrue(ip_net in netaddr.IPNetwork(subnetpool_prefix))
+                self.assertEqual(27, ip_net.prefixlen)
+                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=False,
+                                 name="My ipv6 subnet pool",
+                                 tenant_id=tenant_id,
+                                 min_prefixlen='64') as subnetpool:
+                subnetpool_id = subnetpool['subnetpool']['id']
+                cfg.CONF.set_override('default_ipv6_subnet_pool',
+                                      subnetpool_id)
+                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.assertTrue(ip_net in netaddr.IPNetwork(subnetpool_prefix))
+                self.assertEqual(64, ip_net.prefixlen)
+
     def test_create_subnet_bad_V4_cidr_prefix_len(self):
         with self.network() as network:
             data = {'subnet': {'network_id': network['network']['id'],