Dual-stack implicit subnetpools

Commit 3565d7496f added the
implicit subnetpool extension. That commit limited the semantics
to only cover a single address family. This patch extends that
commit to scope the semantics by address family. This means that
there can be an implicit subnetpool per project per address family,
and there can be a shared implicit subnetpool  per address family.

Change-Id: I30b3bd5ac92bd4c51927225af0b21ea5fc570d5b
This commit is contained in:
Thomas Bachman 2017-04-20 20:55:52 +00:00
parent 5fa26eb3a6
commit 9c6f103ca1
3 changed files with 71 additions and 26 deletions

View File

@ -41,17 +41,20 @@ class ImplicitSubnetpool(model_base.BASEV2):
class ImplicitSubnetpoolMixin(object):
"""Mixin class for implicit subnetpool."""
def get_implicit_subnetpool_id(self, context, tenant=None):
pool = self.get_implicit_subnetpool(context, tenant=tenant)
def get_implicit_subnetpool_id(self, context, tenant=None, ip_version="4"):
pool = self.get_implicit_subnetpool(context, tenant=tenant,
ip_version=ip_version)
return pool['id'] if pool else None
def get_implicit_subnetpool(self, context, tenant=None):
pools = self._get_implicit_subnetpools(context, tenant=tenant)
def get_implicit_subnetpool(self, context, tenant=None, ip_version="4"):
pools = self._get_implicit_subnetpools(context, tenant=tenant,
ip_version=ip_version)
return pools[0] if pools else None
def _get_implicit_subnetpools(self, context, tenant=None):
def _get_implicit_subnetpools(self, context, tenant=None, ip_version="4"):
admin_context = context.elevated()
filters = {"is_implicit": [True]}
filters = {"is_implicit": [True],
"ip_version": ip_version}
if tenant:
filters["tenant_id"] = [tenant]
else:
@ -110,17 +113,18 @@ class ImplicitSubnetpoolMixin(object):
# Verify feasibility. Only one implicit SP must exist per
# tenant (or global)
msg = _('There can be at most one implicit '
'subnetpool per tenant.')
'subnetpool per address family per tenant.')
self._validate_implicit_subnetpool(
context, subnetpool['id'], tenant=subnetpool['tenant_id'],
msg=msg)
msg=msg, ip_version=subnetpool['ip_version'])
if subnetpool['shared']:
# Check globally too
msg = _('There can be at most one global implicit '
'subnetpool.')
'subnetpool per address family.')
self._validate_implicit_subnetpool(
context, subnetpool['id'],
tenant=subnetpool['tenant_id'], msg=msg)
tenant=None,
msg=msg, ip_version=subnetpool['ip_version'])
db_obj = self._get_implicit_subnetpool(
context, subnetpool['id'])
if db_obj:
@ -132,9 +136,9 @@ class ImplicitSubnetpoolMixin(object):
return is_implicit
def _validate_implicit_subnetpool(self, context, subnetpool_id,
tenant=None, msg=None):
tenant=None, msg=None, ip_version="4"):
current_implicit_sp = self._get_implicit_subnetpools(
context, tenant=tenant)
context, tenant=tenant, ip_version=ip_version)
if len(current_implicit_sp) > 1:
raise n_exc.BadRequest(resource='subnetpools', msg=msg)
if (len(current_implicit_sp) == 1 and

View File

@ -476,8 +476,11 @@ class Ml2PlusPlugin(ml2_plugin.Ml2Plugin,
def _get_subnetpool_id(self, context, subnet):
# Check for regular subnetpool ID first, then Tenant's implicit,
# then global implicit.
ip_version = subnet['ip_version']
return (
super(Ml2PlusPlugin, self)._get_subnetpool_id(context, subnet) or
self.get_implicit_subnetpool_id(context,
tenant=subnet['tenant_id']) or
self.get_implicit_subnetpool_id(context, tenant=None))
tenant=subnet['tenant_id'],
ip_version=ip_version) or
self.get_implicit_subnetpool_id(context, tenant=None,
ip_version=ip_version))

View File

@ -1780,16 +1780,18 @@ class TestAimMapping(ApicAimTestCase):
def test_network_in_address_scope_pre_existing_common_vrf(self):
self.test_network_in_address_scope_pre_existing_vrf(common_vrf=True)
def test_default_subnetpool(self):
def _test_default_subnetpool(self, prefix, sn1, gw1, sn2, gw2, sn3, gw3):
# Create a non-default non-shared SP
subnetpool = self._make_subnetpool(
self.fmt, ['10.0.0.0/8'], name='spool1',
self.fmt, [prefix], name='spool1',
tenant_id='t1')['subnetpool']
net = self._make_network(self.fmt, 'pvt-net1', True,
tenant_id='t1')['network']
sub = self._make_subnet(
self.fmt, {'network': net}, '10.0.1.1', '10.0.1.0/24',
tenant_id='t1')['subnet']
self.fmt, {'network': net,
}, gw1, sn1,
tenant_id='t1',
ip_version=subnetpool['ip_version'])['subnet']
self.assertIsNone(sub['subnetpool_id'])
# Make SP default
data = {'subnetpool': {'is_implicit': True}}
@ -1800,18 +1802,20 @@ class TestAimMapping(ApicAimTestCase):
tenant_id='t1')['network']
# Create another subnet
sub = self._make_subnet(
self.fmt, {'network': net}, '10.0.2.1',
'10.0.2.0/24', tenant_id='t1')['subnet']
self.fmt, {'network': net}, gw2,
sn2, tenant_id='t1',
ip_version=subnetpool['ip_version'])['subnet']
# This time, SP ID is set
self.assertEqual(subnetpool['id'], sub['subnetpool_id'])
# Create a shared SP in a different tenant
subnetpool_shared = self._make_subnetpool(
self.fmt, ['10.0.0.0/8'], name='spool1', is_implicit=True,
self.fmt, [prefix], name='spool1', is_implicit=True,
shared=True, tenant_id='t2', admin=True)['subnetpool']
# A subnet created in T1 still gets the old pool ID
sub = self._make_subnet(
self.fmt, {'network': net}, '10.0.3.1',
'10.0.3.0/24', tenant_id='t1')['subnet']
self.fmt, {'network': net}, gw3,
sn3, tenant_id='t1',
ip_version=subnetpool_shared['ip_version'])['subnet']
# This time, SP ID is set
self.assertEqual(subnetpool['id'], sub['subnetpool_id'])
# Creating a subnet somewhere else, however, will get the SP ID from
@ -1819,10 +1823,25 @@ class TestAimMapping(ApicAimTestCase):
net = self._make_network(self.fmt, 'pvt-net3', True,
tenant_id='t3')['network']
sub = self._make_subnet(
self.fmt, {'network': net}, '10.0.1.1',
'10.0.1.0/24', tenant_id='t3')['subnet']
self.fmt, {'network': net}, gw1,
sn1, tenant_id='t3',
ip_version=subnetpool_shared['ip_version'])['subnet']
self.assertEqual(subnetpool_shared['id'], sub['subnetpool_id'])
def test_default_subnetpool(self):
# First do a set with the v4 address family
self._test_default_subnetpool('10.0.0.0/8',
'10.0.1.0/24', '10.0.1.1',
'10.0.2.0/24', '10.0.2.1',
'10.0.3.0/24', '10.0.3.1')
# Do the same test with v6 (v4 still present), using the same tenants
# and shared properties. Since they are different address families,
# it should not conflict
self._test_default_subnetpool('2001:db8::1/56',
'2001:db8:0:1::0/64', '2001:db8:0:1::1',
'2001:db8:0:2::0/64', '2001:db8:0:2::1',
'2001:db8:0:3::0/64', '2001:db8:0:3::1')
def test_implicit_subnetpool(self):
# Create implicit SP (non-shared)
sp = self._make_subnetpool(
@ -1839,10 +1858,16 @@ class TestAimMapping(ApicAimTestCase):
'subnetpools', sp['id'],
{'subnetpool': {'is_implicit': True}})['subnetpool']
self.assertTrue(sp['is_implicit'])
# Create another implicit in the same tenant, it will fail
# Create another implicit in the same family, same tenant, it will fail
self.assertRaises(webob.exc.HTTPClientError, self._make_subnetpool,
self.fmt, ['11.0.0.0/8'], name='spool1',
tenant_id='t1', is_implicit=True)
# Create another implicit in different family, same tenant, it succeeds
sp2 = self._make_subnetpool(
self.fmt, ['2001:db8:1::0/56'], name='spool1',
is_implicit=True, tenant_id='t1')['subnetpool']
self.assertTrue(sp2['is_implicit'])
# Create a normal SP, will succeed
sp2 = self._make_subnetpool(
self.fmt, ['11.0.0.0/8'], name='spool2',
@ -1860,6 +1885,19 @@ class TestAimMapping(ApicAimTestCase):
self.assertTrue(sp3['is_implicit'])
# Update SP shared state is not supported by Neutron
# Create another shared implicit in the same family, it will fail
self.assertRaises(webob.exc.HTTPClientError, self._make_subnetpool,
self.fmt, ['12.0.0.0/8'], name='spool3',
tenant_id='t3', shared=True,
admin=True, is_implicit=True)
# Create a shared implicit SP in a different address family
sp3 = self._make_subnetpool(
self.fmt, ['2001:db8:2::0/56'], name='spoolSharedv6',
tenant_id='t2', shared=True, admin=True,
is_implicit=True)['subnetpool']
self.assertTrue(sp3['is_implicit'])
class TestSyncState(ApicAimTestCase):
@staticmethod