[apic-aim] Isomorphic address scopes
Allow a single IPv4 address scope and a single IPv6 address scope to reference the same VRF, which may be pre-existing or mapped from one of the address scopes. Change-Id: Ibe5288a3a6d5032e4c0ac509a0857ce5defafa9c
This commit is contained in:
parent
ace2a20d7b
commit
71b3b2df67
|
@ -13,6 +13,7 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from neutron.db.models import address_scope as as_db
|
||||
from neutron_lib.db import model_base
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
@ -181,7 +182,8 @@ class ExtensionDbMixin(object):
|
|||
db_obj.vrf_dn = res_dict[cisco_apic.VRF]
|
||||
session.add(db_obj)
|
||||
|
||||
def get_address_scope_by_vrf_dn(self, session, vrf_dn):
|
||||
db_obj = (session.query(AddressScopeExtensionDb)
|
||||
.filter_by(vrf_dn=vrf_dn).first())
|
||||
return db_obj.address_scope_id if db_obj else None
|
||||
def get_address_scopes_by_vrf_dn(self, session, vrf_dn):
|
||||
return (session.query(as_db.AddressScope).
|
||||
join(AddressScopeExtensionDb).
|
||||
filter(AddressScopeExtensionDb.vrf_dn == vrf_dn).
|
||||
all())
|
||||
|
|
|
@ -152,13 +152,16 @@ class ApicExtensionDriver(api_plus.ExtensionDriver,
|
|||
raise n_exc.InvalidInput(
|
||||
error_message=('%s is not valid VRF DN' % dn))
|
||||
session = plugin_context.session
|
||||
# check if there is another address-scope mapping to same VRF
|
||||
# Check if there is another address scope of the same
|
||||
# address family mapping to same VRF.
|
||||
#
|
||||
# Case 1: Another address-scope with pre-existing VRF
|
||||
scope = self.get_address_scope_by_vrf_dn(session, dn)
|
||||
if scope:
|
||||
raise n_exc.InvalidInput(
|
||||
error_message=('VRF %s is already in use by '
|
||||
'address-scope %s' % (dn, scope)))
|
||||
scopes = self.get_address_scopes_by_vrf_dn(session, dn)
|
||||
for scope in scopes:
|
||||
if scope.ip_version == data['ip_version']:
|
||||
raise n_exc.InvalidInput(
|
||||
error_message=('VRF %s is already in use by '
|
||||
'address-scope %s' % (dn, scope)))
|
||||
# Case 2: Another address-scope with orchestrated VRF
|
||||
#
|
||||
# REVISIT: We don't filter by the project ID because the
|
||||
|
@ -170,7 +173,7 @@ class ApicExtensionDriver(api_plus.ExtensionDriver,
|
|||
scope = (session.query(address_scope_db.AddressScope)
|
||||
.filter_by(id=scope_id)
|
||||
.first())
|
||||
if scope:
|
||||
if scope and scope.ip_version == data['ip_version']:
|
||||
raise n_exc.InvalidInput(
|
||||
error_message=('VRF %s is already in use by '
|
||||
'address-scope %s' % (dn, scope)))
|
||||
|
|
|
@ -568,6 +568,16 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
|
|||
|
||||
vrf = self.aim.get(aim_ctx, self._map_address_scope(session, current))
|
||||
if vrf and not vrf.monitored:
|
||||
# Don't delete VRF if isomorphic scope uses this scope's
|
||||
# VRF.
|
||||
id = self.name_mapper.reverse_address_scope(
|
||||
session, vrf.name, enforce=False)
|
||||
if id != current['id'] and self._scope_by_id(session, id):
|
||||
return
|
||||
extn_db = extension_db.ExtensionDbMixin()
|
||||
for scope in extn_db.get_address_scopes_by_vrf_dn(session, vrf.dn):
|
||||
if scope.id != current['id']:
|
||||
return
|
||||
self.aim.delete(aim_ctx, vrf)
|
||||
|
||||
def extend_address_scope_dict(self, session, scope_db, result):
|
||||
|
@ -1445,8 +1455,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
|
|||
if network_db:
|
||||
return self._get_routed_vrf_for_network(session, network_db)
|
||||
|
||||
def _get_address_scope_id_for_vrf(self, session, vrf):
|
||||
# Returns the ID of an address-scope that corresponds to a VRF,
|
||||
def _get_address_scope_ids_for_vrf(self, session, vrf):
|
||||
# Returns the IDs of address-scopes that corresponds to a VRF,
|
||||
# if any.
|
||||
|
||||
# Check if unrouted VRF
|
||||
|
@ -1455,19 +1465,20 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
|
|||
|
||||
# Check if pre-existing VRF for an address-scope
|
||||
extn_db = extension_db.ExtensionDbMixin()
|
||||
scope_id = extn_db.get_address_scope_by_vrf_dn(session, vrf.dn)
|
||||
scope_ids = [scope.id for scope in
|
||||
extn_db.get_address_scopes_by_vrf_dn(session, vrf.dn)]
|
||||
|
||||
# Check if orchestrated VRF for an address-scope
|
||||
if not scope_id and vrf.name != DEFAULT_VRF_NAME:
|
||||
scope_id = self.name_mapper.reverse_address_scope(session,
|
||||
vrf.name)
|
||||
return scope_id
|
||||
if not scope_ids and vrf.name != DEFAULT_VRF_NAME:
|
||||
scope_ids = [self.name_mapper.reverse_address_scope(session,
|
||||
vrf.name)]
|
||||
return scope_ids
|
||||
|
||||
def _get_routers_for_vrf(self, session, vrf):
|
||||
# REVISIT: Persist router/VRF relationship?
|
||||
|
||||
scope_id = self._get_address_scope_id_for_vrf(session, vrf)
|
||||
if scope_id:
|
||||
scope_ids = self._get_address_scope_ids_for_vrf(session, vrf)
|
||||
if scope_ids:
|
||||
rtr_dbs = (session.query(l3_db.Router)
|
||||
.join(l3_db.RouterPort)
|
||||
.join(models_v2.Port)
|
||||
|
@ -1478,8 +1489,8 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
|
|||
models_v2.SubnetPool.id)
|
||||
.filter(l3_db.RouterPort.port_type ==
|
||||
n_constants.DEVICE_OWNER_ROUTER_INTF)
|
||||
.filter(models_v2.SubnetPool.address_scope_id ==
|
||||
scope_id)
|
||||
.filter(models_v2.SubnetPool.address_scope_id.in_(
|
||||
scope_ids))
|
||||
.distinct())
|
||||
else:
|
||||
# For an unscoped VRF, first find all the routed BDs
|
||||
|
@ -1752,7 +1763,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
|
|||
def _scope_by_id(self, session, scope_id):
|
||||
return (session.query(address_scope_db.AddressScope).
|
||||
filter_by(id=scope_id).
|
||||
one())
|
||||
first())
|
||||
|
||||
def _map_network(self, session, network, vrf, bd_only=False):
|
||||
tenant_aname = (vrf.tenant_name
|
||||
|
|
|
@ -2002,17 +2002,18 @@ class AIMMappingDriver(nrd.CommonNeutronBase, aim_rpc.AIMMappingRPCMixin):
|
|||
# get all subnets of the specified VRF
|
||||
with session.begin(subtransactions=True):
|
||||
# Find VRF's address_scope first
|
||||
address_scope_id = (
|
||||
self.aim_mech_driver._get_address_scope_id_for_vrf(
|
||||
address_scope_ids = (
|
||||
self.aim_mech_driver._get_address_scope_ids_for_vrf(
|
||||
session,
|
||||
aim_resource.VRF(tenant_name=vrf_tenant_name,
|
||||
name=vrf_name)))
|
||||
if address_scope_id:
|
||||
subnetpools = self._get_subnetpools(
|
||||
plugin_context,
|
||||
filters={'address_scope_id': [address_scope_id]})
|
||||
for pool in subnetpools:
|
||||
result.extend(pool['prefixes'])
|
||||
if address_scope_ids:
|
||||
for address_scope_id in address_scope_ids:
|
||||
subnetpools = self._get_subnetpools(
|
||||
plugin_context,
|
||||
filters={'address_scope_id': [address_scope_id]})
|
||||
for pool in subnetpools:
|
||||
result.extend(pool['prefixes'])
|
||||
else:
|
||||
aim_ctx = aim_context.AimContext(db_session=session)
|
||||
if vrf_tenant_name != md.COMMON_TENANT_NAME:
|
||||
|
|
|
@ -2790,22 +2790,84 @@ class TestExtensionAttributes(ApicAimTestCase):
|
|||
extn = extn_db.ExtensionDbMixin()
|
||||
aim_ctx = aim_context.AimContext(db_session=session)
|
||||
|
||||
# create with APIC DN
|
||||
# Create VRF.
|
||||
self.aim_mgr.create(
|
||||
aim_ctx, aim_resource.Tenant(name=self.t1_aname, monitored=True))
|
||||
vrf = aim_resource.VRF(tenant_name=self.t1_aname, name='ctx1',
|
||||
monitored=True)
|
||||
self.aim_mgr.create(aim_ctx, vrf)
|
||||
scope = self._make_address_scope_for_vrf(vrf.dn)['address_scope']
|
||||
|
||||
# Create v4 scope with APIC DN.
|
||||
scope4 = self._make_address_scope_for_vrf(vrf.dn)['address_scope']
|
||||
self.assertEqual({'VRF': vrf.dn},
|
||||
extn.get_address_scope_extn_db(session, scope['id']))
|
||||
extn.get_address_scope_extn_db(session, scope4['id']))
|
||||
self._check_dn(scope4, vrf, 'VRF')
|
||||
|
||||
scope = self._show('address-scopes', scope4['id'])['address_scope']
|
||||
self._check_dn(scope, vrf, 'VRF')
|
||||
|
||||
scope = self._show('address-scopes', scope['id'])['address_scope']
|
||||
self._check_dn(scope, vrf, 'VRF')
|
||||
# Create (isomorphic) v6 scope with same APIC DN.
|
||||
scope6 = self._make_address_scope_for_vrf(
|
||||
vrf.dn, n_constants.IP_VERSION_6)['address_scope']
|
||||
self.assertEqual({'VRF': vrf.dn},
|
||||
extn.get_address_scope_extn_db(session, scope6['id']))
|
||||
self._check_dn(scope6, vrf, 'VRF')
|
||||
|
||||
self._delete('address-scopes', scope['id'])
|
||||
self.assertFalse(extn.get_address_scope_extn_db(session, scope['id']))
|
||||
scope = self._show('address-scopes', scope6['id'])['address_scope']
|
||||
self._check_dn(scope6, vrf, 'VRF')
|
||||
|
||||
# Delete scopes.
|
||||
self._delete('address-scopes', scope4['id'])
|
||||
self.assertFalse(extn.get_address_scope_extn_db(session, scope4['id']))
|
||||
|
||||
self._delete('address-scopes', scope6['id'])
|
||||
self.assertFalse(extn.get_address_scope_extn_db(session, scope6['id']))
|
||||
|
||||
vrf = self.aim_mgr.get(aim_ctx, vrf)
|
||||
self.assertIsNotNone(vrf)
|
||||
|
||||
def test_isomorphic_address_scopes_lifecycle(self):
|
||||
# Create initial v4 scope.
|
||||
scope4 = self._make_address_scope(
|
||||
self.fmt, 4, name='as')['address_scope']
|
||||
dn = scope4['apic:distinguished_names']['VRF']
|
||||
|
||||
# Create isomorphic v6 scope, using v4 scope's pre-existing
|
||||
# DN.
|
||||
scope6 = self._make_address_scope_for_vrf(
|
||||
dn, n_constants.IP_VERSION_6)['address_scope']
|
||||
self.assertEqual(dn, scope6['apic:distinguished_names']['VRF'])
|
||||
|
||||
# Delete v4 scope.
|
||||
self._delete('address-scopes', scope4['id'])
|
||||
vrf = self._find_by_dn(dn, aim_resource.VRF)
|
||||
self.assertIsNotNone(vrf)
|
||||
|
||||
# Delete v6 scope.
|
||||
self._delete('address-scopes', scope6['id'])
|
||||
vrf = self._find_by_dn(dn, aim_resource.VRF)
|
||||
self.assertIsNone(vrf)
|
||||
|
||||
# Create another initial v4 scope.
|
||||
scope4 = self._make_address_scope(
|
||||
self.fmt, 4, name='as')['address_scope']
|
||||
dn = scope4['apic:distinguished_names']['VRF']
|
||||
|
||||
# Create isomorphic v6 scope, using v4 scope's pre-existing
|
||||
# DN.
|
||||
scope6 = self._make_address_scope_for_vrf(
|
||||
dn, n_constants.IP_VERSION_6)['address_scope']
|
||||
self.assertEqual(dn, scope6['apic:distinguished_names']['VRF'])
|
||||
|
||||
# Delete v6 scope.
|
||||
self._delete('address-scopes', scope6['id'])
|
||||
vrf = self._find_by_dn(dn, aim_resource.VRF)
|
||||
self.assertIsNotNone(vrf)
|
||||
|
||||
# Delete v4 scope.
|
||||
self._delete('address-scopes', scope4['id'])
|
||||
vrf = self._find_by_dn(dn, aim_resource.VRF)
|
||||
self.assertIsNone(vrf)
|
||||
|
||||
def test_address_scope_fail(self):
|
||||
# APIC DN not specified
|
||||
|
|
Loading…
Reference in New Issue