[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:
parent
51d7d95ad2
commit
2e865e96ce
|
@ -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)
|
|
@ -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
|
|
@ -1 +1 @@
|
|||
a707faecf518
|
||||
75aa8a37a8de
|
|
@ -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 {}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue