Implement default subnet pool configuration settings

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
This commit is contained in:
Carl Baldwin 2015-03-26 18:10:10 +00:00
parent fb8ea72240
commit 3428594556
6 changed files with 130 additions and 10 deletions

View 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 =============

View 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,

View 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 "

View 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)

View 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)

View 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'],