[AIM][SFC] Use single contract per provider

For scalability purposes, create only one contract per
provider/graph pair. This will not affect the user intent as far
as the datapath logic is concerned

Change-Id: I4ca7c9d8f7a32faa7b627e8c3154bb76d76fad15
This commit is contained in:
Ivar Lazzaro 2018-05-24 17:40:51 -07:00
parent 9b7b759221
commit 9d005febc4
2 changed files with 119 additions and 67 deletions

View File

@ -10,6 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import hashlib
from aim import aim_manager
from aim.api import resource as aim_resource
from aim.api import service_graph as aim_sg
@ -459,17 +461,18 @@ class SfcAIMDriver(SfcAIMDriverBase):
# the number of nodes in the chain.
p_tenants = set()
for flc in flowcs:
p_tenant = self._get_flowc_provider_group(plugin_context,
flc).tenant_name
p_group = self._get_flowc_provider_group(plugin_context, flc)
p_tenant = p_group.tenant_name
sg = self._get_pc_service_graph(p_ctx.session, pc, p_tenant)
contract = self._get_flc_contract(p_ctx.session, flc, p_tenant)
contract = self._get_flc_contract(p_ctx.session, p_group, sg)
subject = aim_resource.ContractSubject(
tenant_name=contract.tenant_name, contract_name=contract.name,
name=sg.name, service_graph_name=sg.name,
bi_filters=[self.aim_mech._any_filter_name])
self.aim.create(aim_ctx, contract)
self.aim.create(aim_ctx, subject)
self._map_flow_classifier(p_ctx, flc, p_tenant)
if not self.aim.get(aim_ctx, contract):
self.aim.create(aim_ctx, contract)
self.aim.create(aim_ctx, subject)
self._map_flow_classifier(p_ctx, flc, pc, p_tenant)
# Map device clusters for each flow tenant
if p_tenant not in p_tenants:
for ppg in ppgs:
@ -520,16 +523,16 @@ class SfcAIMDriver(SfcAIMDriverBase):
aim_ctx = aim_context.AimContext(session)
deleted_ppgs = set()
for flc in flowcs:
tenant = self._get_flowc_provider_group(plugin_context,
flc).tenant_name
p_group = self._get_flowc_provider_group(plugin_context, flc)
tenant = p_group.tenant_name
for ppg in ppgs:
key = (tenant, ppg['id'])
if key not in deleted_ppgs:
self._delete_port_pair_group_mapping(p_ctx, ppg, tenant)
deleted_ppgs.add(key)
self._delete_flow_classifier_mapping(p_ctx, flc, tenant)
contract = self._get_flc_contract(p_ctx.session, flc, tenant)
self._delete_flow_classifier_mapping(p_ctx, flc, pc, tenant)
sg = self._get_pc_service_graph(p_ctx.session, pc, tenant)
contract = self._get_flc_contract(p_ctx.session, p_group, sg)
self.aim.delete(aim_ctx, contract, cascade=True)
self.aim.delete(aim_ctx, sg, cascade=True)
for ppg_id in pc['port_pair_groups']:
@ -569,7 +572,7 @@ class SfcAIMDriver(SfcAIMDriverBase):
net_id)
self.aim.update(aim_ctx, epg, sync=True)
def _map_flow_classifier(self, plugin_context, flowc, tenant):
def _map_flow_classifier(self, plugin_context, flowc, pc, tenant):
"""Map flowclassifier to AIM model
If source/destination ports are plugged to external networks, create
@ -580,10 +583,11 @@ class SfcAIMDriver(SfcAIMDriverBase):
:return:
"""
aim_ctx = aim_context.AimContext(plugin_context.session)
cons_group = self._get_flowc_consumer_group(plugin_context, flowc)
prov_group = self._get_flowc_provider_group(plugin_context, flowc)
contract = self._get_flc_contract(plugin_context.session, flowc,
tenant)
cons_group = self._map_flowc_consumer_group(plugin_context, flowc)
prov_group = self._map_flowc_provider_group(plugin_context, flowc)
sg = self._get_pc_service_graph(plugin_context.session, pc, tenant)
contract = self._get_flc_contract(plugin_context.session, prov_group,
sg)
# TODO(ivar): if provider/consumer are in different tenants, export
# the contract
cons_group.consumed_contract_names.append(contract.name)
@ -621,23 +625,21 @@ class SfcAIMDriver(SfcAIMDriverBase):
return self.aim_mech._get_epg_by_network_id(plugin_context.session,
net['id'])
def _delete_flow_classifier_mapping(self, plugin_context, flowc, tenant):
def _delete_flow_classifier_mapping(self, plugin_context, flowc, pc,
tenant):
source_net = self._get_flowc_src_network(plugin_context, flowc)
dest_net = self._get_flowc_dst_network(plugin_context, flowc)
self._delete_flowc_network_group_mapping(
plugin_context, source_net, flowc, tenant,
flowc['source_ip_prefix'], FLOWC_SRC)
plugin_context, source_net, flowc, pc, tenant, FLOWC_SRC)
self._delete_flowc_network_group_mapping(
plugin_context, dest_net, flowc, tenant,
flowc['destination_ip_prefix'], FLOWC_DST)
plugin_context, dest_net, flowc, pc, tenant, FLOWC_DST)
def _delete_flowc_network_group_mapping(self, plugin_context, net, flowc,
tenant, cidr, prefix=''):
pc, tenant, prefix=''):
flc_aid = self._get_external_group_aim_name(plugin_context, flowc,
prefix)
aim_ctx = aim_context.AimContext(plugin_context.session)
l3out = self.aim_mech._get_svi_net_l3out(net)
cidr = netaddr.IPNetwork(cidr)
ext_net = None
if l3out:
ext_net = aim_resource.ExternalNetwork(
@ -648,8 +650,10 @@ class SfcAIMDriver(SfcAIMDriverBase):
epg = self.aim.get(aim_ctx, self.aim_mech._get_epg_by_network_id(
plugin_context.session, net['id']))
if epg:
contract = self._get_flc_contract(plugin_context.session, flowc,
tenant)
p_group = self._get_flowc_provider_group(plugin_context, flowc)
sg = self._get_pc_service_graph(plugin_context.session, pc, tenant)
contract = self._get_flc_contract(plugin_context.session, p_group,
sg)
try:
if prefix == FLOWC_SRC:
epg.consumed_contract_names.remove(contract.name)
@ -658,11 +662,12 @@ class SfcAIMDriver(SfcAIMDriverBase):
except ValueError:
LOG.warning("Contract %s not present in EPG %s" %
(contract.name, epg))
self.aim.create(aim_ctx, epg, overwrite=True)
else:
epg = self.aim.create(aim_ctx, epg, overwrite=True)
if (ext_net and not epg.consumed_contract_names and not
epg.provided_contract_names):
# Only remove external network if completely empty
self.aim.delete(aim_ctx, ext_net, cascade=True)
self.aim.delete(aim_ctx, epg, cascade=True)
def _get_chains_by_classifier_id(self, plugin_context, flowc_id):
context = plugin_context
@ -768,12 +773,27 @@ class SfcAIMDriver(SfcAIMDriverBase):
return aim_sg.ServiceGraph(tenant_name=tenant_aid, name=pc_aid,
display_name=pc_aname)
def _get_flc_contract(self, session, flc, tenant):
tenant_id = tenant
flc_aid = self.name_mapper.flow_classifier(session, flc['id'])
flc_aname = aim_utils.sanitize_display_name(flc['name'])
return aim_resource.Contract(tenant_name=tenant_id, name=flc_aid,
display_name=flc_aname)
def _get_flc_contract(self, session, provider, graph):
tenant_id = provider.tenant_name
display_name = ''
if provider.display_name or graph.display_name:
display_name = aim_utils.sanitize_display_name(
provider.display_name + '_' + graph.display_name)
# NOTE(ivar): For scalability purposes, we want to limit the number
# of contracts by creating one per provider/graph pair.
# In order to keep the contract name unique, hashing the provider and
# graph names (which are already unique) together seems like the
# safest choice, considering that truncating those names to fit 64
# bytes would cause a huge loss of entropy. This is especially true
# for SVI networks, where the provider group can use as much as
# 23 bytes only for the prefix ([network cidr] + _net_)
return aim_resource.Contract(
tenant_name=tenant_id,
name=self._generate_contract_name(provider.name, graph.name),
display_name=display_name)
def _generate_contract_name(self, prov_name, graph_name):
return hashlib.sha256(prov_name + graph_name).hexdigest()
def _get_ppg_service_redirect_policy(self, session, ppg, direction,
tenant):
@ -941,14 +961,39 @@ class SfcAIMDriver(SfcAIMDriverBase):
break
return flowcs, ppgs
def _get_flowc_network_group(self, plugin_context, flowc, net, prefix):
flc_aid = self._get_external_group_aim_name(plugin_context, flowc,
prefix)
l3out = self.aim_mech._get_svi_net_l3out(net)
if l3out:
# Create ExternalNetwork and ExternalSubnet on the proper
# L3Out. Return the External network
ext_net = aim_resource.ExternalNetwork(
tenant_name=l3out.tenant_name, l3out_name=l3out.name,
name=flc_aid)
return ext_net
else:
return self.aim_mech._get_epg_by_network_id(plugin_context.session,
net['id'])
def _get_flowc_provider_group(self, plugin_context, flowc):
net = self._get_flowc_dst_network(plugin_context, flowc)
return self._get_flowc_network_group(plugin_context, flowc, net,
FLOWC_DST)
def _get_flowc_consumer_group(self, plugin_context, flowc):
net = self._get_flowc_src_network(plugin_context, flowc)
return self._get_flowc_network_group(plugin_context, flowc, net,
FLOWC_SRC)
def _map_flowc_provider_group(self, plugin_context, flowc):
aim_ctx = aim_context.AimContext(plugin_context.session)
net = self._get_flowc_dst_network(plugin_context, flowc)
return self.aim.get(aim_ctx, self._map_flowc_network_group(
plugin_context, net, flowc['destination_ip_prefix'], flowc,
FLOWC_DST))
def _get_flowc_consumer_group(self, plugin_context, flowc):
def _map_flowc_consumer_group(self, plugin_context, flowc):
aim_ctx = aim_context.AimContext(plugin_context.session)
net = self._get_flowc_src_network(plugin_context, flowc)
return self.aim.get(aim_ctx, self._map_flowc_network_group(

View File

@ -385,17 +385,6 @@ class TestAIMServiceFunctionChainingBase(test_aim_base.AIMBaseTestCase):
ppgs = [self.show_port_pair_group(x)['port_pair_group'] for x in
pc['port_pair_groups']]
if not multiple:
routers_count = ctx.db_session.query(l3_db.Router).count()
self.assertEqual(
len(flowcs),
len(self.aim_mgr.find(ctx, aim_res.Contract)) - routers_count)
self.assertEqual(
len(flowcs),
len(self.aim_mgr.find(
ctx, aim_res.ContractSubject)) - routers_count)
self.assertEqual(
len(flowcs), len(self.aim_mgr.find(
ctx, aim_res.ContractSubject)) - routers_count)
self.assertEqual(
len(flowc_tenants) * len(ppgs),
len(self.aim_mgr.find(ctx, aim_sg.DeviceClusterContext)))
@ -416,42 +405,60 @@ class TestAIMServiceFunctionChainingBase(test_aim_base.AIMBaseTestCase):
sg = self.aim_mgr.get(ctx, aim_sg.ServiceGraph(
tenant_name=apic_tn, name='ptc_' + pc['id']))
self.assertIsNotNone(sg)
# Verify Flow Classifier mapping
contract = self.aim_mgr.get(
ctx, aim_res.Contract(tenant_name=apic_tn,
name='flc_' + flowc['id']))
self.assertIsNotNone(contract)
subject = self.aim_mgr.get(
ctx, aim_res.ContractSubject(
tenant_name=apic_tn, contract_name='flc_' + flowc['id'],
name='ptc_' + pc['id']))
self.assertIsNotNone(subject)
self.assertEqual(['openstack_AnyFilter'], subject.bi_filters)
src_cidr = flowc['source_ip_prefix']
dst_cird = flowc['destination_ip_prefix']
for net, pref, cidr in [(src_net, 'src_', src_cidr),
(dst_net, 'dst_', dst_cird)]:
def get_net_group(net, cidr):
if net['apic:svi']:
# TODO(ivar): this will not work, there's no L3Outside
# DN extension for external networks.
ext = aim_res.ExternalNetwork.from_dn(
net['apic:distinguished_names']['ExternalNetwork'])
name_prefix = cidr.replace('/', '_')
subnets = [cidr]
if cidr in ['0.0.0.0/0', '::/0']:
# use default external EPG
name_prefix = 'default'
subnets = ['128.0.0.0/1', '0.0.0.0/1', '8000::/1',
'::/1']
ext_net = self.aim_mgr.get(
ctx, aim_res.ExternalNetwork(
tenant_name=ext.tenant_name,
l3out_name=ext.l3out_name,
name=name_prefix + '_' + 'net_' + net['id']))
return ext_net
else:
epg = self.aim_mgr.get(
ctx, aim_res.EndpointGroup.from_dn(
net['apic:distinguished_names']['EndpointGroup']))
return epg
provider = get_net_group(dst_net, dst_cird)
# Verify Flow Classifier mapping
contract = self.aim_mgr.get(
ctx, aim_res.Contract(
tenant_name=apic_tn,
name=self.sfc_driver._generate_contract_name(provider.name,
sg.name)))
self.assertIsNotNone(contract)
subject = self.aim_mgr.get(
ctx, aim_res.ContractSubject(
tenant_name=apic_tn,
contract_name=contract.name,
name='ptc_' + pc['id']))
self.assertIsNotNone(subject)
self.assertEqual(['openstack_AnyFilter'], subject.bi_filters)
for net, pref, cidr in [(src_net, 'src_', src_cidr),
(dst_net, 'dst_', dst_cird)]:
group = get_net_group(net, cidr)
if net['apic:svi']:
ext_net = group
subnets = [cidr]
if cidr in ['0.0.0.0/0', '::/0']:
# use default external EPG
subnets = ['128.0.0.0/1', '0.0.0.0/1', '8000::/1',
'::/1']
for sub in subnets:
ext_sub = self.aim_mgr.get(ctx, aim_res.ExternalSubnet(
tenant_name=ext.tenant_name,
l3out_name=ext.l3out_name,
tenant_name=ext_net.tenant_name,
l3out_name=ext_net.l3out_name,
external_network_name=ext_net.name, cidr=sub))
self.assertIsNotNone(ext_sub)
@ -463,9 +470,7 @@ class TestAIMServiceFunctionChainingBase(test_aim_base.AIMBaseTestCase):
"%s not in ext net %s" % (contract.name,
ext_net.__dict__))
else:
epg = self.aim_mgr.get(
ctx, aim_res.EndpointGroup.from_dn(
net['apic:distinguished_names']['EndpointGroup']))
epg = group
self.assertTrue(
contract.name in (epg.consumed_contract_names if
pref == 'src_' else
@ -1236,7 +1241,7 @@ class TestPortChain(TestAIMServiceFunctionChainingBase):
'l7_parameters']['logical_source_network'],
'logical_destination_network': fc[
'l7_parameters']['logical_destination_network']},
source_ip_prefix='192.168.%s.0/24' % (i + 3),
source_ip_prefix='192.198.%s.0/24' % (i + 3),
destination_ip_prefix=fc['destination_ip_prefix'],
expected_res_status=201)['flow_classifier'])
# We have four FCs
@ -1259,13 +1264,15 @@ class TestPortChain(TestAIMServiceFunctionChainingBase):
self._verify_pc_mapping(pc1, multiple=True)
self._verify_pc_mapping(pc2, multiple=True)
if self.dst_svi:
self.delete_port_chain(pc1['id'])
dst_net_id = fc['l7_parameters']['logical_destination_network']
ext_net = self.aim_mgr.find(
self._aim_context, aim_res.ExternalNetwork,
name=fc['destination_ip_prefix'].replace(
'/', '_') + '_' + 'net_' + dst_net_id)[0]
self.assertEqual(2, len(ext_net.provided_contract_names))
self.delete_port_chain(pc1['id'])
ext_net = self.aim_mgr.get(self._aim_context, ext_net)
self.assertEqual(1, len(ext_net.provided_contract_names))
self.delete_port_chain(pc2['id'])
self.assertIsNone(self.aim_mgr.get(self._aim_context, ext_net))