[apic-mapping] Automatic PTG per L2P

This change automatically creates a PTG per L2P. This PTG is created as a
reverse map of the "shadow" EPG that was already being created per L2P by
the apic_mapping policy driver.. We will henceforth refer to this PTG as
"auto" PTG.

The ID of the auto PTG is derived from the ID of the L2P as a MD5 hash
calculation (for uniqueness) and persisted in the format:
"auto<hash_of_l2p_id>". It is thus always possible to determine the ID of the
auto PTG from the ID of the L2P and no additional state needs to be maintained.

In order to maintain the reverse-mapping integrity between the shadow EPG and
the auto PTG, an entry is created in the apic name-mapping DB that maps the ID
of the auto PTG to the "apic-name" of the "shadow" EPG.

The initial name of the auto PTG is derived from the ID of the L2P to ease
debugging and troubleshooting, and takes the form: "auto-ptg-<l2p_id>". This
name is mutable (just like any other PTG). The apic_mapping driver does not
have any specical meaning for this name, and does not care about after it
implicitly sets it at the time of the auto PTG creation.

The auto PTG cannot be deleted by the end user and doing so will result in
an error.

The user can update the name, description, provided and consumed PRS for the
auto PTG, but cannot update any other attributes and doing so will result in
an error.

The shared status of the auto PTG is made consistent with the shared status
of the L2P (once set, it cannot be changed).

The auto PTG is deleted when the corresponding L2P is deleted (attempted in
the pre-commit phase).

To prevent forward mapping of the auto PTG to a new EPG, all above
operations are invoked on the GBP DB mixin (parent of the GBP plugin). This
ensures that the apic_mapping policy driver is not invoked for the create and
delete auto PTG operations during L2P creation and deletion.

The creation of the auto PTG is controlled by a configuration and is disabled
by default thus allowing this new feature to be turned ON only where needed.
All existing deployments should not see any change in behavior as long
as they choose not to turn ON this feature. This configuration is as follows:

[apic_mapping]
create_auto_ptg=<True or False>

As the commit title suggests, this is currently only a apic_mapping driver
specific feature. It may evolve to a GBP feature with a well defined auto PTG
attribute definition for the L2P (and/or accessor APIs). The convention used
for the Auto PTG name and the ID format could change as a part of this
evolution.

Change-Id: Ie132ace0fc9f78baa0034a6f30f2ee758bb271c0
This commit is contained in:
Sumit Naiksatam 2016-09-01 01:48:06 -07:00
parent adc1a2fe9f
commit 6d56931196
3 changed files with 162 additions and 9 deletions

View File

@ -427,8 +427,11 @@ class GroupPolicyMappingDbPlugin(gpdb.GroupPolicyDbPlugin):
with context.session.begin(subtransactions=True):
if ptg['service_management']:
self._validate_service_management_ptg(context, tenant_id)
uuid = ptg.get('id')
if not uuid:
uuid = uuidutils.generate_uuid()
ptg_db = PolicyTargetGroupMapping(
id=uuidutils.generate_uuid(), tenant_id=tenant_id,
id=uuid, tenant_id=tenant_id,
name=ptg['name'], description=ptg['description'],
l2_policy_id=ptg['l2_policy_id'],
network_service_policy_id=ptg['network_service_policy_id'],

View File

@ -11,6 +11,7 @@
# under the License.
import copy
import hashlib
import netaddr
import re
@ -63,6 +64,7 @@ from gbpservice.neutron.services.grouppolicy.drivers.cisco.apic import (
from gbpservice.neutron.services.grouppolicy.drivers.cisco.apic import (
nova_client as nclient)
from gbpservice.neutron.services.grouppolicy import group_policy_context
from gbpservice.neutron.services.grouppolicy import plugin as gbp_plugin
HOST_SNAT_POOL = 'host-snat-pool-for-internal-use'
@ -74,6 +76,24 @@ LOG = logging.getLogger(__name__)
UNMANAGED_SEGMENT = _("External Segment %s is not managed by APIC mapping "
"driver.")
PRE_EXISTING_SEGMENT = _("Pre-existing external segment %s not found.")
AUTO_PTG_NAME_PREFIX = 'auto-ptg-%s'
# Note that this prefix should not exceede 4 characters
AUTO_PTG_ID_PREFIX = 'auto%s'
AUTO_PTG_MUTABLE_KEYS = ['name', 'description', 'consumed_policy_rule_sets',
'provided_policy_rule_sets']
opts = [
cfg.BoolOpt('create_auto_ptg',
default=False,
help=_("Automatically create a PTG when a L2 Policy "
"gets created. This is currently an apic_mapping "
"policy driver specific feature and may evolve "
"to be a broader GBP feature. As a part of the "
"evolution, new APIs may be added, the Auto PTG "
"naming, and ID format convention may change.")),
]
cfg.CONF.register_opts(opts, "apic_mapping")
class PolicyRuleUpdateNotSupportedOnApicDriver(gpexc.GroupPolicyBadRequest):
@ -196,6 +216,15 @@ class AdminOnlyOperation(gpexc.GroupPolicyBadRequest):
message = _("This operation is reserved to admins")
class AutoPTGAttrsUpdateNotSupported(gpexc.GroupPolicyBadRequest):
message = _("Update of attributes: %(attrs)s for auto PTG "
"%(id)s not supported.")
class AutoPTGDeleteNotSupported(gpexc.GroupPolicyBadRequest):
message = _("Auto PTG %(id)s cannot be deleted.")
class TenantSpecificNatEpg(model_base.BASEV2):
"""Tenants that use a specific NAT EPG for an external segment."""
__tablename__ = 'gp_apic_tenant_specific_nat_epg'
@ -277,6 +306,11 @@ class ApicMappingDriver(api.ResourceMappingDriver,
self.l3out_vlan_alloc = l3out_vlan_alloc.L3outVlanAlloc()
self.l3out_vlan_alloc.sync_vlan_allocations(
self.apic_manager.ext_net_dict)
self.create_auto_ptg = cfg.CONF.apic_mapping.create_auto_ptg
if self.create_auto_ptg:
LOG.info(_LI('Auto PTG creation configuration set, '
'this will result in automatic creation of a PTG '
'per L2 Policy'))
def _setup_rpc_listeners(self):
self.endpoints = [rpc.GBPServerRpcCallback(self, self.notifier)]
@ -1028,7 +1062,11 @@ class ApicMappingDriver(api.ResourceMappingDriver,
return
def delete_policy_target_group_precommit(self, context):
pass
if self.create_auto_ptg:
auto_ptg_id = self._get_auto_ptg_id(
context.current['l2_policy_id'])
if context.current['id'] == auto_ptg_id:
raise AutoPTGDeleteNotSupported(id=auto_ptg_id)
def delete_policy_target_group_postcommit(self, context):
if not self.name_mapper._is_apic_reference(context.current):
@ -1070,6 +1108,14 @@ class ApicMappingDriver(api.ResourceMappingDriver,
self._unset_any_contract(context.current)
def delete_l2_policy_precommit(self, context):
if self.create_auto_ptg:
auto_ptg_id = self._get_auto_ptg_id(context.current['id'])
auto_ptg = context._plugin.get_policy_target_group(
context._plugin_context, auto_ptg_id)
if auto_ptg:
self._db_plugin(context._plugin).delete_policy_target_group(
context._plugin_context, auto_ptg_id)
self.apic_manager.db.delete_apic_name(auto_ptg_id)
if not self.name_mapper._is_apic_reference(context.current):
super(ApicMappingDriver, self).delete_l2_policy_precommit(context)
@ -1167,6 +1213,15 @@ class ApicMappingDriver(api.ResourceMappingDriver,
self.create_policy_rule_postcommit(context, transaction=None)
def update_policy_target_group_precommit(self, context):
if self.create_auto_ptg:
auto_ptg_id = self._get_auto_ptg_id(
context.original['l2_policy_id'])
if context.current['id'] == auto_ptg_id:
updated_attrs = [key for key in context.current if
context.original[key] != context.current[key]]
if list(set(updated_attrs) - set(AUTO_PTG_MUTABLE_KEYS)):
raise AutoPTGAttrsUpdateNotSupported(
id=auto_ptg_id, attrs=updated_attrs)
if not self.name_mapper._is_apic_reference(context.current):
if set(context.original['subnets']) != set(
context.current['subnets']):
@ -2658,6 +2713,27 @@ class ApicMappingDriver(api.ResourceMappingDriver,
self.apic_manager.set_contract_for_epg(
tenant, shadow_epg, contract, provider=True,
contract_owner=tenant, transaction=trs)
if self.create_auto_ptg:
data = {
"id": self._get_auto_ptg_id(l2p['id']),
"name": self._get_auto_ptg_name(l2p),
"description": "System created auto PTG per L2P",
"l2_policy_id": l2p['id'],
"proxied_group_id": None,
"proxy_type": None,
"proxy_group_id": attributes.ATTR_NOT_SPECIFIED,
"network_service_policy_id": None,
"service_management": False,
"shared": l2p['shared'],
}
auto_ptg = self._db_plugin(
context._plugin).create_policy_target_group(
context._plugin_context,
{'policy_target_group': data})
# Since we are reverse mapping the APIC EPG to the default PTG
# we will map the id of the PTG to the APIC EPG name
self.apic_manager.db.update_apic_name(
auto_ptg['id'], 'policy_target_group', shadow_epg)
def _associate_service_filter(self, tenant, contract, filter_name,
entry_name, transaction=None, **attrs):
@ -3814,3 +3890,12 @@ class ApicMappingDriver(api.ResourceMappingDriver,
"""
if EOC_PREFIX in ptg.get('description', ''):
return ptg['description'][len(EOC_PREFIX):]
def _db_plugin(self, plugin_obj):
return super(gbp_plugin.GroupPolicyPlugin, plugin_obj)
def _get_auto_ptg_name(self, l2p):
return AUTO_PTG_NAME_PREFIX % l2p['id']
def _get_auto_ptg_id(self, l2p_id):
return AUTO_PTG_ID_PREFIX % hashlib.md5(l2p_id).hexdigest()

View File

@ -12,6 +12,7 @@
# limitations under the License.
import copy
import hashlib
import re
import sys
@ -2042,7 +2043,7 @@ class TestPolicyTargetGroupVlanNetwork(ApicMappingVlanTestCase,
self.assertFalse(port['port']['admin_state_up'])
class TestL2Policy(ApicMappingTestCase):
class TestL2PolicyBase(ApicMappingTestCase):
def _test_l2_policy_created_on_apic(self, shared=False):
l2p = self.create_l2_policy(name="l2p", shared=shared)['l2_policy']
@ -2056,12 +2057,6 @@ class TestL2Policy(ApicMappingTestCase):
tenant, amap.SHADOW_PREFIX + l2p['id'], bd_owner=tenant,
bd_name=l2p['id'], transaction=mock.ANY)
def test_l2_policy_created_on_apic(self):
self._test_l2_policy_created_on_apic()
def test_l2_policy_created_on_apic_shared(self):
self._test_l2_policy_created_on_apic(shared=True)
def _test_l2_policy_deleted_on_apic(self, shared=False):
l2p = self.create_l2_policy(name="l2p", shared=shared)['l2_policy']
req = self.new_delete_request('l2_policies', l2p['id'], self.fmt)
@ -2081,6 +2076,15 @@ class TestL2Policy(ApicMappingTestCase):
self._check_call_list(expected_calls,
mgr.delete_contract.call_args_list)
class TestL2Policy(TestL2PolicyBase):
def test_l2_policy_created_on_apic(self):
self._test_l2_policy_created_on_apic()
def test_l2_policy_created_on_apic_shared(self):
self._test_l2_policy_created_on_apic(shared=True)
def test_l2_policy_deleted_on_apic(self):
self._test_l2_policy_deleted_on_apic()
@ -2127,6 +2131,67 @@ class TestL2Policy(ApicMappingTestCase):
self.assertFalse(subnet & subnet2)
class TestL2PolicyWithAutoPTG(TestL2PolicyBase):
def setUp(self, **kwargs):
config.cfg.CONF.set_override(
'create_auto_ptg', True, group='apic_mapping')
super(TestL2PolicyWithAutoPTG, self).setUp(**kwargs)
self._neutron_context = context.Context(
'', kwargs.get('tenant_id', self._tenant_id),
is_admin_context=False)
def _test_auto_ptg_created(self, shared=False):
ptg = self._gbp_plugin.get_policy_target_groups(
self._neutron_context)[0]
l2p_id = ptg['l2_policy_id']
self.assertEqual(amap.AUTO_PTG_NAME_PREFIX % l2p_id, str(ptg['name']))
auto_ptg_id = amap.AUTO_PTG_ID_PREFIX % hashlib.md5(l2p_id).hexdigest()
self.assertEqual(auto_ptg_id, ptg['id'])
self.assertEqual(shared, ptg['shared'])
# Only certain attributes can be updated for auto PTG
prs1 = self.create_policy_rule_set(
name='p1', shared=shared)['policy_rule_set']
prs2 = self.create_policy_rule_set(
name='c1', shared=shared)['policy_rule_set']
self.update_policy_target_group(
ptg['id'], name='something-else', description='something-else',
provided_policy_rule_sets={prs1['id']: 'scope'},
consumed_policy_rule_sets={prs2['id']: 'scope'},
expected_res_status=webob.exc.HTTPOk.code)
# Update for other attributes fails
res = self.update_policy_target_group(
ptg['id'], service_management=True,
expected_res_status=webob.exc.HTTPBadRequest.code)
self.assertEqual('AutoPTGAttrsUpdateNotSupported',
res['NeutronError']['type'])
# Auto PTG cannot be deleted by user
res = self.delete_policy_target_group(
ptg['id'], expected_res_status=webob.exc.HTTPBadRequest.code)
self.assertEqual('AutoPTGDeleteNotSupported',
res['NeutronError']['type'])
def _test_auto_ptg_deleted(self, shared=False):
ptgs = self._gbp_plugin.get_policy_target_groups(self._neutron_context)
self.assertEqual(0, len(ptgs))
def test_l2_policy_created_on_apic(self):
self._test_l2_policy_created_on_apic()
self._test_auto_ptg_created()
def test_l2_policy_created_on_apic_shared(self):
self._test_l2_policy_created_on_apic(shared=True)
self._test_auto_ptg_created(shared=True)
def test_l2_policy_deleted_on_apic(self):
self._test_l2_policy_deleted_on_apic()
self._test_auto_ptg_deleted()
def test_l2_policy_deleted_on_apic_shared(self):
self._test_l2_policy_deleted_on_apic(shared=True)
self._test_auto_ptg_deleted()
class TestL3Policy(ApicMappingTestCase):
def _test_l3_policy_created_on_apic(self, shared=False):