[APIC-mapping] Implement option to reuse BD

This change adds APIC-specific extension attribute,
reuse_bd, that can be specified while creating an
L2 policy. The value should be the ID of another
L2 policy in the same L3 policy. If the option is
specified, the APIC driver uses the same BridgeDomain,
service EPG etc as the target L2 policy.

Closes-bug: 1642784

Change-Id: I23dad698a1f8d2f588575bf15e34ea78cd50c04c
Signed-off-by: Amit Bose <amitbose@gmail.com>
(cherry picked from commit 74b791f654)
This commit is contained in:
Amit Bose 2016-11-16 10:54:35 -08:00
parent 51d7d95ad2
commit 2e865e96ce
9 changed files with 312 additions and 5 deletions

View File

@ -0,0 +1,42 @@
# 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.
from neutron.db import model_base
import sqlalchemy as sa
class ApicReuseBdDB(model_base.BASEV2):
__tablename__ = 'gp_apic_mapping_reuse_bds'
l2_policy_id = sa.Column(
sa.String(36), sa.ForeignKey('gp_l2_policies.id',
ondelete='CASCADE'), primary_key=True)
target_l2_policy_id = sa.Column(
sa.String(36), sa.ForeignKey('gp_l2_policies.id'), nullable=False)
class ApicReuseBdDBMixin(object):
def get_reuse_bd_l2policy(self, session, l2_policy_id):
row = (session.query(ApicReuseBdDB).filter_by(
l2_policy_id=l2_policy_id).first())
return row
def add_reuse_bd_l2policy(self, session, l2_policy_id,
target_l2_policy_id):
with session.begin(subtransactions=True):
row = ApicReuseBdDB(l2_policy_id=l2_policy_id,
target_l2_policy_id=target_l2_policy_id)
session.add(row)
def is_reuse_bd_target(self, session, l2_policy_id):
return (session.query(ApicReuseBdDB).filter_by(
target_l2_policy_id=l2_policy_id).first() is not None)

View File

@ -0,0 +1,48 @@
# 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.
"""Reuse BD
Revision ID: 75aa8a37a8de
Revises: a707faecf518
Create Date: 2016-11-11 17:01:30.735865
"""
# revision identifiers, used by Alembic.
revision = '75aa8a37a8de'
down_revision = 'a707faecf518'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.create_table(
'gp_apic_mapping_reuse_bds',
sa.Column('l2_policy_id', sa.String(length=36), nullable=False),
sa.Column('target_l2_policy_id', sa.String(length=36),
nullable=False),
sa.ForeignKeyConstraint(
['l2_policy_id'], ['gp_l2_policies.id'],
name='gp_apic_mapping_reuse_bd_fk_l2pid',
ondelete='CASCADE'),
sa.ForeignKeyConstraint(
['target_l2_policy_id'], ['gp_l2_policies.id'],
name='gp_apic_mapping_reuse_bd_target_l2p_id_fk_l2pid'),
sa.PrimaryKeyConstraint('l2_policy_id')
)
def downgrade():
pass

View File

@ -1 +1 @@
a707faecf518
75aa8a37a8de

View File

@ -0,0 +1,53 @@
# 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.
from neutron.api import extensions
from gbpservice.neutron.extensions import group_policy as gp
CISCO_APIC_GBP_REUSE_BD_EXT = 'cisco_apic_gbp_reuse_bd'
EXTENDED_ATTRIBUTES_2_0 = {
gp.L2_POLICIES: {
'reuse_bd': {
'allow_post': True, 'allow_put': False, 'default': None,
'validate': {'type:uuid_or_none': None},
'is_visible': True},
},
}
class Apic_reuse_bd(extensions.ExtensionDescriptor):
@classmethod
def get_name(cls):
return "APIC GBP Reuse BD Extension"
@classmethod
def get_alias(cls):
return CISCO_APIC_GBP_REUSE_BD_EXT
@classmethod
def get_description(cls):
return _("This extension enables creating L2 policy objects that "
"use the same BridgeDomain on APIC")
@classmethod
def get_updated(cls):
return "2016-11-11T04:20:00-00:00"
def get_extended_resources(self, version):
if version == "2.0":
return EXTENDED_ATTRIBUTES_2_0
else:
return {}

View File

@ -51,6 +51,7 @@ from oslo_log import log as logging
from oslo_serialization import jsonutils
import sqlalchemy as sa
from gbpservice.neutron.db.grouppolicy.extensions import apic_reuse_bd_db
from gbpservice.neutron.db.grouppolicy import group_policy_mapping_db as gpdb
from gbpservice.neutron.extensions import group_policy as gpolicy
from gbpservice.neutron.services.grouppolicy.common import constants as g_const
@ -230,6 +231,11 @@ class SingleTenantAndPerTenantNatEPGNotSupported(gpexc.GroupPolicyBadRequest):
"per_tenant_nat_epg.")
class L3PolicyMustBeSame(gpexc.GroupPolicyBadRequest):
message = _("L2 policy cannot use a L3 policy different from the target "
"L2 policy when BridgeDomain is being reused.")
class TenantSpecificNatEpg(model_base.BASEV2):
"""Tenants that use a specific NAT EPG for an external segment."""
__tablename__ = 'gp_apic_tenant_specific_nat_epg'
@ -907,6 +913,7 @@ class ApicMappingDriver(api.ResourceMappingDriver,
tenant = self._tenant_by_sharing_policy(context.current)
l2p = self._get_l2_policy(context._plugin_context,
context.current['l2_policy_id'])
l2_policy = self.name_mapper.l2_policy(context, l2p)
epg = self.name_mapper.policy_target_group(context,
context.current)
@ -954,6 +961,15 @@ class ApicMappingDriver(api.ResourceMappingDriver,
def create_l2_policy_precommit(self, context):
if not self.name_mapper._is_apic_reference(context.current):
self._reject_non_shared_net_on_shared_l2p(context)
if context.current.get('reuse_bd'):
target_l2p = self._get_l2_policy(context._plugin_context,
context.current['reuse_bd'])
if context.current.get('l3_policy_id'):
if (context.current['l3_policy_id'] !=
target_l2p['l3_policy_id']):
raise L3PolicyMustBeSame()
else:
context.set_l3_policy_id(target_l2p['l3_policy_id'])
else:
self.name_mapper.has_valid_name(context.current)
@ -968,7 +984,8 @@ class ApicMappingDriver(api.ResourceMappingDriver,
def create_l2_policy_postcommit(self, context):
super(ApicMappingDriver, self).create_l2_policy_postcommit(context)
if not self.name_mapper._is_apic_reference(context.current):
if (not self.name_mapper._is_apic_reference(context.current) and
not context.current.get('reuse_bd')):
tenant = self._tenant_by_sharing_policy(context.current)
l3_policy_object = self._get_l3_policy(
context._plugin_context, context.current['l3_policy_id'])
@ -1133,6 +1150,10 @@ class ApicMappingDriver(api.ResourceMappingDriver,
self._unset_any_contract(context.current)
def delete_l2_policy_precommit(self, context):
# Disallow L2 policy delete if its BD is being reused
if apic_reuse_bd_db.ApicReuseBdDBMixin().is_reuse_bd_target(
context._plugin_context.session, context.current['id']):
raise gpolicy.L2PolicyInUse(l2_policy_id=context.current['id'])
if self.create_auto_ptg:
auto_ptg_id = self._get_auto_ptg_id(context.current['id'])
auto_ptg = context._plugin.get_policy_target_group(
@ -1148,7 +1169,8 @@ class ApicMappingDriver(api.ResourceMappingDriver,
# before removing the network, remove interfaces attached to router
self._cleanup_router_interface(context, context.current)
super(ApicMappingDriver, self).delete_l2_policy_postcommit(context)
if not self.name_mapper._is_apic_reference(context.current):
if (not self.name_mapper._is_apic_reference(context.current) and
not context.current.get('reuse_bd')):
tenant = self._tenant_by_sharing_policy(context.current)
l2_policy = self.name_mapper.l2_policy(context, context.current)
@ -2486,6 +2508,10 @@ class ApicMappingDriver(api.ResourceMappingDriver,
if self.single_tenant_mode:
return self.single_tenant_name
if object.get('reuse_bd'):
target_l2p = self._get_l2_policy(nctx.get_admin_context(),
object['reuse_bd'])
return self._tenant_by_sharing_policy(target_l2p)
if object.get('shared') and not self.name_mapper._is_apic_reference(
object):
return apic_manager.TENANT_COMMON

View File

@ -64,7 +64,10 @@ class ApicNameManager(object):
self._extract_apic_reference(obj))
result = self.name_mapper.pre_existing(context, parts[-1])
else:
result = getattr(self.name_mapper, obj_type)(context, obj['id'],
obj_id = (obj['reuse_bd']
if obj_type == 'l2_policy' and obj.get('reuse_bd')
else obj['id'])
result = getattr(self.name_mapper, obj_type)(context, obj_id,
prefix=prefix)
return result

View File

@ -0,0 +1,55 @@
# 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.
from neutron._i18n import _LI
from oslo_log import log as logging
from gbpservice.neutron.db.grouppolicy.extensions import (
apic_reuse_bd_db as db)
from gbpservice.neutron.db.grouppolicy import group_policy_db as gp_db
from gbpservice.neutron.extensions import apic_reuse_bd as ext
from gbpservice.neutron.extensions import group_policy as gpolicy
from gbpservice.neutron.services.grouppolicy import (
group_policy_driver_api as api)
LOG = logging.getLogger(__name__)
class ApicReuseBdExtensionDriver(api.ExtensionDriver,
db.ApicReuseBdDBMixin):
_supported_extension_alias = ext.CISCO_APIC_GBP_REUSE_BD_EXT
_extension_dict = ext.EXTENDED_ATTRIBUTES_2_0
def __init__(self):
LOG.info(_LI("ApicReuseBdExtensionDriver __init__"))
def initialize(self):
pass
@property
def extension_alias(self):
return self._supported_extension_alias
def process_create_l2_policy(self, session, data, result):
l2p = data['l2_policy']
if l2p.get('reuse_bd'):
target_l2p = (session.query(gp_db.L2Policy)
.filter_by(id=l2p['reuse_bd']).first())
if not target_l2p:
raise gpolicy.L2PolicyNotFound(l2_policy_id=l2p['reuse_bd'])
self.add_reuse_bd_l2policy(session, result['id'], l2p['reuse_bd'])
result['reuse_bd'] = l2p['reuse_bd']
def extend_l2_policy_dict(self, session, result):
row = self.get_reuse_bd_l2policy(session, l2_policy_id=result['id'])
if row:
result['reuse_bd'] = row.target_l2_policy_id

View File

@ -95,7 +95,8 @@ class MockCallRecorder(mock.Mock):
class ApicMappingTestCase(
test_rmd.ResourceMappingTestCase,
mocked.ControllerMixin, mocked.ConfigMixin):
_extension_drivers = ['apic_segmentation_label', 'apic_allowed_vm_name']
_extension_drivers = ['apic_segmentation_label', 'apic_allowed_vm_name',
'apic_reuse_bd']
_extension_path = None
def setUp(self, sc_plugin=None, nat_enabled=True,
@ -2326,6 +2327,72 @@ class TestL2Policy(TestL2PolicyBase):
[self._show_subnet(x)['subnet']['cidr'] for x in ptg['subnets']])
self.assertFalse(subnet & subnet2)
def _test_reuse_bd(self, target_l2p_shared=False):
target_l2p = self.create_l2_policy(
shared=target_l2p_shared)['l2_policy']
target_l2p_tenant = self._tenant(target_l2p['tenant_id'],
target_l2p_shared)
mgr = self.driver.apic_manager
mgr.ensure_bd_created_on_apic.reset_mock()
mgr.ensure_epg_created.reset_mock()
# create L2P with reuse_bd
l2p = self.create_l2_policy(reuse_bd=target_l2p['id'])['l2_policy']
self.assertEqual(target_l2p['id'], l2p['reuse_bd'])
self.assertEqual(target_l2p['l3_policy_id'], l2p['l3_policy_id'])
mgr.ensure_bd_created_on_apic.assert_not_called()
mgr.ensure_epg_created.assert_not_called()
# delete of target L2p is not allowed
res = self.delete_l2_policy(target_l2p['id'],
expected_res_status=409)
self.assertEqual('L2PolicyInUse', res['NeutronError']['type'])
# create in PTG in L2P
ptg = self.create_policy_target_group(
l2_policy_id=l2p['id'])['policy_target_group']
sn1 = self._show_subnet(ptg['subnets'][0])['subnet']
mgr.ensure_epg_created.assert_called_with(
self._tenant(ptg['tenant_id']), ptg['id'],
bd_name=target_l2p['id'], bd_owner=target_l2p_tenant)
mgr.ensure_subnet_created_on_apic.assert_called_with(
target_l2p_tenant, target_l2p['id'],
sn1['gateway_ip'] + '/' + sn1['cidr'].split('/')[1],
transaction=mock.ANY)
# delete PTG in L2P
self.delete_policy_target_group(ptg['id'])
mgr.delete_epg_for_network.assert_called_with(
self._tenant(ptg['tenant_id']), ptg['id'])
# delete L2P
mgr.delete_epg_for_network.reset_mock()
self.delete_l2_policy(l2p['id'])
mgr.delete_bd_on_apic.assert_not_called()
mgr.delete_epg_for_network.assert_not_called()
mgr.delete_contract.assert_not_called()
def test_reuse_bd(self):
self._test_reuse_bd(target_l2p_shared=False)
def test_reuse_bd_shared(self):
self._test_reuse_bd(target_l2p_shared=True)
def test_reuse_bd_invalid_target(self):
# Try to reuse BD of a non-existent target
res = self.create_l2_policy(
reuse_bd='50ae6722-f040-459c-bbfa-a4be1b5cab64',
expected_res_status=404)
self.assertEqual('L2PolicyNotFound', res['NeutronError']['type'])
# Try to reuse BD of L2P in different L3P
target_l2p = self.create_l2_policy()['l2_policy']
l3p = self.create_l3_policy()['l3_policy']
res = self.create_l2_policy(l3_policy_id=l3p['id'],
reuse_bd=target_l2p['id'],
expected_res_status=400)
self.assertEqual('L3PolicyMustBeSame', res['NeutronError']['type'])
class TestL2PolicySingleTenant(TestL2Policy):
def setUp(self):
@ -2395,6 +2462,18 @@ class TestL2PolicyWithAutoPTG(TestL2PolicyBase):
self._test_l2_policy_deleted_on_apic(shared=True)
self._test_auto_ptg_deleted()
def test_auto_ptg_not_created_with_reuse_bd(self):
target_l2p = self.create_l2_policy()['l2_policy']
ptgs_before = self._gbp_plugin.get_policy_target_groups(
self._neutron_context)
self.assertEqual(1, len(ptgs_before))
# create L2P with reuse_bd
self.create_l2_policy(reuse_bd=target_l2p['id'])
ptgs_after = self._gbp_plugin.get_policy_target_groups(
self._neutron_context)
self.assertEqual(ptgs_before, ptgs_after)
class TestL3Policy(ApicMappingTestCase):

View File

@ -59,6 +59,7 @@ gbpservice.neutron.group_policy.extension_drivers =
aim_extension = gbpservice.neutron.services.grouppolicy.drivers.extensions.aim_mapping_extension_driver:AIMExtensionDriver
apic_segmentation_label = gbpservice.neutron.services.grouppolicy.drivers.extensions.apic_segmentation_label_driver:ApicSegmentationLabelExtensionDriver
apic_allowed_vm_name = gbpservice.neutron.services.grouppolicy.drivers.extensions.apic_allowed_vm_name_driver:ApicAllowedVMNameExtensionDriver
apic_reuse_bd = gbpservice.neutron.services.grouppolicy.drivers.extensions.apic_reuse_bd_driver:ApicReuseBdExtensionDriver
gbpservice.neutron.group_policy.policy_drivers =
dummy = gbpservice.neutron.services.grouppolicy.drivers.dummy_driver:NoopDriver
implicit_policy = gbpservice.neutron.services.grouppolicy.drivers.implicit_policy:ImplicitPolicyDriver