Merge "[AIM][SFC] Add support for health check policy"

This commit is contained in:
Zuul 2018-04-03 23:25:25 +00:00 committed by Gerrit Code Review
commit e231340d20
4 changed files with 213 additions and 42 deletions

View File

@ -227,6 +227,7 @@ try:
from networking_sfc.db import flowclassifier_db
from networking_sfc.db import sfc_db
from networking_sfc.extensions import flowclassifier as fc_ext
from networking_sfc.extensions import sfc as sfc_ext # noqa
from networking_sfc.services.flowclassifier.common import (
exceptions as fc_exc)
from networking_sfc.services.flowclassifier import driver_manager as fc_mgr
@ -247,6 +248,14 @@ try:
('networking_sfc.extensions.'
'flowclassifier')].SUPPORTED_L7_PARAMETERS.update(
sfc_cts.AIM_FLC_L7_PARAMS)
if 'sfc' in sys.modules:
sys.modules['sfc'].RESOURCE_ATTRIBUTE_MAP['port_pair_groups'][
'port_pair_group_parameters']['validate']['type:dict'].update(
sfc_cts.AIM_PPG_PARAMS)
if 'networking_sfc.extensions.sfc' in sys.modules:
sys.modules['networking_sfc.extensions.sfc'].RESOURCE_ATTRIBUTE_MAP[
'port_pair_groups']['port_pair_group_parameters']['validate'][
'type:dict'].update(sfc_cts.AIM_PPG_PARAMS)
# REVISIT(ivar): The following diff will fix flow classifier creation
# method when using L7 parameters.
# - key: L7Parameter(key, val)

View File

@ -15,10 +15,14 @@
# could be dangerous for compatibility once they suddenly start supporting
# them. We create our own resource type and make sure to modify it once
# support is added to the SFC project.
from networking_sfc.extensions import flowclassifier
GBP_FLOW_CLASSIFIER = 'gbp_flowclassifier'
GBP_PORT = 'gbp_port'
LOGICAL_SRC_NET = 'logical_source_network'
LOGICAL_DST_NET = 'logical_destination_network'
HEALTHCHECK_POLICY = 'healthcheck_policy'
AIM_FLC_L7_PARAMS = {
LOGICAL_SRC_NET: {
'allow_post': True, 'allow_put': False,
@ -29,6 +33,17 @@ AIM_FLC_L7_PARAMS = {
'is_visible': True, 'default': None,
'validate': {'type:uuid_or_none': None}}
}
AIM_PPG_PARAMS = {
'healthcheck_type': {
'type:values': ['', 'icmp', 'tcp']
},
'healthcheck_frequency': {
'type:non_negative': None
},
'healthcheck_tcp_port': {
'convert_to': flowclassifier.normalize_port_value
},
}
AIM_FLC_PARAMS = ['source_ip_prefix', 'destination_ip_prefix']
GBP_NETWORK_VRF = 'gbp_network_vrf'
GBP_NETWORK_EPG = 'gbp_network_epg'

View File

@ -333,6 +333,7 @@ class SfcAIMDriver(SfcAIMDriverBase):
aim_ctx = aim_context.AimContext(session)
# Create Logical device model, container for all the PPG port pairs.
dc = self._get_ppg_device_cluster(session, ppg, tenant)
mp = self._get_ppg_monitoring_policy(session, ppg, tenant)
type, domain = self._get_ppg_domain(plugin_context, ppg)
if not type and not domain:
raise exceptions.PortPairsNoUniqueDomain(id=ppg['port_pairs'])
@ -342,7 +343,23 @@ class SfcAIMDriver(SfcAIMDriverBase):
else:
dc.device_type = 'VIRTUAL'
dc.vmm_domain = [{'type': type, 'name': domain}]
internal_dci = aim_sg.DeviceClusterInterface(
tenant_name=dc.tenant_name, device_cluster_name=dc.name,
name=INGRESS, display_name=INGRESS)
external_dci = aim_sg.DeviceClusterInterface(
tenant_name=dc.tenant_name, device_cluster_name=dc.name,
name=EGRESS, display_name=EGRESS)
# Create 2 PBR rules per PPG, one per direction.
ipbr = self._get_ppg_service_redirect_policy(session, ppg, INGRESS,
tenant)
epbr = self._get_ppg_service_redirect_policy(session, ppg, EGRESS,
tenant)
self.aim.create(aim_ctx, dc)
if mp:
for pbr in [ipbr, epbr]:
pbr.monitoring_policy_tenant_name = mp.tenant_name
pbr.monitoring_policy_name = mp.name
self.aim.create(aim_ctx, mp)
# For each port pair, create the corresponding Concrete Devices
# (represented by the static path of each interface)
ingress_cdis = []
@ -357,8 +374,14 @@ class SfcAIMDriver(SfcAIMDriverBase):
cd = aim_sg.ConcreteDevice(
tenant_name=dc.tenant_name, device_cluster_name=dc.name,
name=pp_id, display_name=pp_name)
# Create ConcreteDevice
self.aim.create(aim_ctx, cd)
srhg = None
if mp:
# Create health groups
srhg = aim_sg.ServiceRedirectHealthGroup(
tenant_name=dc.tenant_name, name=pp_id,
display_name=pp_name)
self.aim.create(aim_ctx, srhg)
for p, store in [(ingress_port, ingress_cdis),
(egress_port, egress_cdis)]:
p_id = self.name_mapper.port(session, p['id'])
@ -377,39 +400,27 @@ class SfcAIMDriver(SfcAIMDriverBase):
device_name=cd.name, name=p_id, display_name=p_name,
path=path, host=host)
cdi = self.aim.create(aim_ctx, cdi)
store.append((cdi, encap, pp_name, p))
# Ingress and Egress CDIs have the same length.
# All the ingress devices must be load balanced, and so must the egress
# (for reverse path). Create the proper PBR policies as well as
# the Logical Interfaces (which see all the physical interfaces of a
# specific direction as they were one).
internal_dci = aim_sg.DeviceClusterInterface(
tenant_name=dc.tenant_name, device_cluster_name=dc.name,
name=INGRESS, display_name=INGRESS)
external_dci = aim_sg.DeviceClusterInterface(
tenant_name=dc.tenant_name, device_cluster_name=dc.name,
name=EGRESS, display_name=EGRESS)
# Create 2 PBR rules per PPG, one per direction.
ipbr = self._get_ppg_service_redirect_policy(session, ppg, INGRESS,
tenant)
epbr = self._get_ppg_service_redirect_policy(session, ppg, EGRESS,
tenant)
store.append((cdi, encap, p, srhg, pp_id))
for i in range(len(ingress_cdis)):
icdi, iencap, pp_name, iport = ingress_cdis[i]
ecdi, eencap, _, eport = egress_cdis[i]
icdi, iencap, iport, isrhg, pp_id = ingress_cdis[i]
ecdi, eencap, eport, esrhg, _ = egress_cdis[i]
internal_dci.encap = iencap
external_dci.encap = eencap
internal_dci.concrete_interfaces.append(icdi.dn)
external_dci.concrete_interfaces.append(ecdi.dn)
if iport['fixed_ips']:
ipbr.destinations.append(
{'ip': iport['fixed_ips'][0]['ip_address'],
'mac': iport['mac_address'], 'name': pp_name})
destination = {'ip': iport['fixed_ips'][0]['ip_address'],
'mac': iport['mac_address'], 'name': pp_id}
if isrhg:
destination['redirect_health_group_dn'] = isrhg.dn
ipbr.destinations.append(destination)
if eport['fixed_ips']:
epbr.destinations.append(
{'ip': eport['fixed_ips'][0]['ip_address'],
'mac': eport['mac_address'], 'name': pp_name})
destination = {'ip': eport['fixed_ips'][0]['ip_address'],
'mac': eport['mac_address'], 'name': pp_id}
if esrhg:
destination['redirect_health_group_dn'] = esrhg.dn
epbr.destinations.append(destination)
self.aim.create(aim_ctx, internal_dci)
self.aim.create(aim_ctx, external_dci)
@ -421,7 +432,15 @@ class SfcAIMDriver(SfcAIMDriverBase):
session = plugin_context.session
aim_ctx = aim_context.AimContext(session)
dc = self._get_ppg_device_cluster(session, ppg, tenant)
mp = self._get_ppg_monitoring_policy(session, ppg, tenant)
self.aim.delete(aim_ctx, dc, cascade=True)
if mp:
self.aim.delete(aim_ctx, mp, cascade=True)
for pp in ppg['port_pairs']:
pp_id = self.name_mapper.port_pair(session, pp)
srhg = aim_sg.ServiceRedirectHealthGroup(
tenant_name=dc.tenant_name, name=pp_id)
self.aim.delete(aim_ctx, srhg)
for prefix in [PBR_INGR_PREFIX, PBR_EGR_PREFIX]:
pbr_id = self.name_mapper.port_pair_group(session, ppg['id'],
prefix=prefix)
@ -696,7 +715,11 @@ class SfcAIMDriver(SfcAIMDriverBase):
def _should_regenerate_ppg(self, context):
attrs = ['port_pairs', 'name']
return any(context.current[a] != context.original[a] for a in attrs)
param_curr = context.current['port_pair_group_parameters']
param_orig = context.original['port_pair_group_parameters']
return (any(context.current[a] != context.original[a] for a in attrs)
or any(param_curr[x] != param_orig[x] for x in
sfc_cts.AIM_PPG_PARAMS.keys()))
def _should_regenerate_pc(self, context):
attrs = ['flow_classifiers', 'port_pair_groups', 'name']
@ -720,6 +743,24 @@ class SfcAIMDriver(SfcAIMDriverBase):
return aim_sg.DeviceCluster(tenant_name=tenant_aid, name=ppg_aid,
display_name=ppg_aname, managed=False)
def _get_ppg_monitoring_policy(self, session, ppg, tenant):
tenant_aid = tenant
ppg_aid = self.name_mapper.port_pair_group(session, ppg['id'])
ppg_aname = aim_utils.sanitize_display_name(ppg['name'])
result = None
ppg_params = ppg.get('port_pair_group_parameters', {})
monitoring_policy = ppg_params.get('healthcheck_type')
if monitoring_policy:
kwargs = {'type': monitoring_policy}
if 'healthcheck_frequency' in ppg_params:
kwargs['frequency'] = ppg_params['healthcheck_frequency']
if 'healthcheck_tcp_port' in ppg_params:
kwargs['tcp_port'] = ppg_params['healthcheck_tcp_port']
result = aim_sg.ServiceRedirectMonitoringPolicy(
tenant_name=tenant_aid, name=ppg_aid,
display_name=ppg_aname, **kwargs)
return result
def _get_ppg_domain(self, plugin_context, ppg):
pp = self.sfc_plugin.get_port_pair(plugin_context,
ppg['port_pairs'][0])

View File

@ -143,7 +143,8 @@ class TestAIMServiceFunctionChainingBase(test_aim_base.AIMBaseTestCase):
self._plugin.mechanism_manager.mech_drivers['apic_aim'].obj)
return self._aim_mech_driver
def _create_simple_ppg(self, pairs=2, leftn_id=None, rightn_id=None):
def _create_simple_ppg(self, pairs=2, leftn_id=None, rightn_id=None,
check_type=None, check_freq=None, check_port=None):
nets = []
# Pairs go in 2 networks
if not leftn_id or not rightn_id:
@ -167,9 +168,19 @@ class TestAIMServiceFunctionChainingBase(test_aim_base.AIMBaseTestCase):
expected_res_status=201)['port_pair']
port_pairs.append(pp)
# This goes through
kwargs = {}
port_pair_group_parameters = {}
if check_type:
port_pair_group_parameters['healthcheck_type'] = check_type
if check_freq:
port_pair_group_parameters['healthcheck_frequency'] = check_freq
if check_port:
port_pair_group_parameters['healthcheck_tcp_port'] = check_port
if port_pair_group_parameters:
kwargs['port_pair_group_parameters'] = port_pair_group_parameters
return self.create_port_pair_group(
port_pairs=[pp['id'] for pp in port_pairs],
expected_res_status=201)['port_pair_group']
expected_res_status=201, **kwargs)['port_pair_group']
def _create_simple_flowc(self, src_svi=False, dst_svi=False):
kwargs = {}
@ -258,15 +269,42 @@ class TestAIMServiceFunctionChainingBase(test_aim_base.AIMBaseTestCase):
# DeviceCluster. Only one created
dc = self.aim_mgr.get(ctx, aim_sg.DeviceCluster(
tenant_name=apic_tn, name='ppg_' + ppg['id']))
ppg_params = ppg['port_pair_group_parameters']
hcheck_type = ppg_params.get('healthcheck_type')
if hcheck_type:
hcheck_frequency = ppg_params.get('healthcheck_frequency')
hcheck_tcp_port = ppg_params.get('healthcheck_tcp_port')
mp = self.aim_mgr.get(ctx, aim_sg.ServiceRedirectMonitoringPolicy(
tenant_name=apic_tn, name='ppg_' + ppg['id']))
if hcheck_type:
self.assertIsNotNone(mp)
self.assertEqual(hcheck_type, mp.type)
if hcheck_frequency:
self.assertEqual(str(hcheck_frequency), mp.frequency)
if hcheck_tcp_port:
self.assertEqual(str(hcheck_tcp_port), mp.tcp_port)
else:
# Test hcheck toggle
self.assertIsNone(mp)
self.assertIsNotNone(dc)
self.assertEqual(self.physdom.name, dc.physical_domain_name)
pps = [self.show_port_pair(x)['port_pair'] for x in ppg['port_pairs']]
srgh_dn_by_pp = {}
for pp in pps:
self.assertIsNotNone(self.aim_mgr.get(
ctx, aim_sg.ConcreteDevice(tenant_name=apic_tn,
device_cluster_name=dc.name,
name='pp_' + pp['id'])))
srhg = aim_sg.ServiceRedirectHealthGroup(
tenant_name=dc.tenant_name, name='pp_' + pp['id'])
srgh_dn_by_pp[pp['id']] = srhg.dn
srhg = self.aim_mgr.get(ctx, srhg)
if hcheck_type:
self.assertIsNotNone(srhg)
else:
self.assertIsNone(srhg)
for pp in pps:
# Each of these CD have 2 CDIs
@ -317,7 +355,6 @@ class TestAIMServiceFunctionChainingBase(test_aim_base.AIMBaseTestCase):
self.assertEqual({egr.dn for egr in egr_cdis},
set(edci.concrete_interfaces))
# Redirect Policy Ingress
irp = self.aim_mgr.get(ctx, aim_sg.ServiceRedirectPolicy(
tenant_name=dc.tenant_name, name='ingr_ppg_' + ppg['id']))
@ -326,18 +363,17 @@ class TestAIMServiceFunctionChainingBase(test_aim_base.AIMBaseTestCase):
self.assertIsNotNone(irp)
self.assertIsNotNone(erp)
iprts = [(self._show_port(pp['ingress']), pp['name']) for pp in pps]
self.assertEqual(
sorted([{'ip': iprt[0]['fixed_ips'][0]['ip_address'],
'mac': iprt[0]['mac_address'],
'name': iprt[1]} for iprt in iprts]),
irp.destinations)
eprts = [(self._show_port(pp['egress']), pp['name']) for pp in pps]
self.assertEqual(
sorted([{'ip': eprt[0]['fixed_ips'][0]['ip_address'],
'mac': eprt[0]['mac_address'],
'name': eprt[1]} for eprt in eprts]),
erp.destinations)
# Ingress Ports
for type, pbr in [('ingress', irp), ('egress', erp)]:
prts = [(self._show_port(pp[type]), pp) for pp in pps]
observed_destinations = []
for port, pp in prts:
dst = {'ip': port['fixed_ips'][0]['ip_address'],
'mac': port['mac_address'], 'name': 'pp_' + pp['id']}
if hcheck_type:
dst['redirect_health_group_dn'] = srgh_dn_by_pp[pp['id']]
observed_destinations.append(dst)
self.assertEqual(sorted(observed_destinations), pbr.destinations)
def _verify_pc_mapping(self, pc, multiple=False):
ctx = self._aim_context
@ -509,6 +545,12 @@ class TestAIMServiceFunctionChainingBase(test_aim_base.AIMBaseTestCase):
0, len(self.aim_mgr.find(ctx, aim_sg.DeviceCluster)))
self.assertEqual(
0, len(self.aim_mgr.find(ctx, aim_sg.DeviceClusterInterface)))
self.assertEqual(
0, len(self.aim_mgr.find(ctx,
aim_sg.ServiceRedirectMonitoringPolicy)))
self.assertEqual(
0, len(self.aim_mgr.find(ctx, aim_sg.ServiceRedirectHealthGroup)))
ppgs = [self.show_port_pair_group(x)['port_pair_group'] for x in
pc['port_pair_groups']]
for ppg in ppgs:
@ -671,6 +713,58 @@ class TestPortPairGroup(TestAIMServiceFunctionChainingBase):
ppg2['id'], port_pairs=[pp3['id'], pp1['id']],
expected_res_status=500)
def test_healthcheck_group(self):
# Correct creation
net1 = self._make_network(self.fmt, 'net1', True)
self._make_subnet(self.fmt, net1, '192.168.0.1', '192.168.0.0/24')
net2 = self._make_network(self.fmt, 'net2', True)
self._make_subnet(self.fmt, net2, '192.168.1.1', '192.168.1.0/24')
# Service 1
p11 = self._make_port(self.fmt, net1['network']['id'])['port']
self._bind_port_to_host(p11['id'], 'h1')
p12 = self._make_port(self.fmt, net2['network']['id'])['port']
self._bind_port_to_host(p12['id'], 'h1')
pp1 = self.create_port_pair(ingress=p11['id'], egress=p12['id'],
expected_res_status=201)['port_pair']
ppg1 = self.create_port_pair_group(
port_pairs=[pp1['id']], port_pair_group_parameters={
'healthcheck_type': 'tcp', 'healthcheck_frequency': 60,
'healthcheck_tcp_port': 8080},
expected_res_status=201)['port_pair_group']
self.assertEqual('tcp', ppg1['port_pair_group_parameters'][
'healthcheck_type'])
self.assertEqual(60, ppg1['port_pair_group_parameters'][
'healthcheck_frequency'])
self.assertEqual(8080, ppg1['port_pair_group_parameters'][
'healthcheck_tcp_port'])
self.delete_port_pair_group(ppg1['id'])
self.create_port_pair_group(
port_pairs=[pp1['id']], port_pair_group_parameters={
'healthcheck_type': 'no', 'healthcheck_frequency': 60,
'healthcheck_tcp_port': 8080},
expected_res_status=400)
self.create_port_pair_group(
port_pairs=[pp1['id']], port_pair_group_parameters={
'healthcheck_type': 'tcp', 'healthcheck_frequency': -1,
'healthcheck_tcp_port': 8080},
expected_res_status=400)
self.create_port_pair_group(
port_pairs=[pp1['id']], port_pair_group_parameters={
'healthcheck_type': 'tcp', 'healthcheck_frequency': 60,
'healthcheck_tcp_port': 80800},
expected_res_status=400)
ppg1 = self.create_port_pair_group(
port_pairs=[pp1['id']], port_pair_group_parameters={
'healthcheck_type': 'icmp'},
expected_res_status=201)['port_pair_group']
self.assertEqual('icmp', ppg1['port_pair_group_parameters'][
'healthcheck_type'])
self.assertTrue('check_frequency' not in ppg1[
'port_pair_group_parameters'])
self.assertTrue('tcp_port' not in ppg1[
'port_pair_group_parameters'])
class TestFlowClassifier(TestAIMServiceFunctionChainingBase):
@ -1235,6 +1329,18 @@ class TestPortChain(TestAIMServiceFunctionChainingBase):
self.update_flow_classifier(fc['id'], name='newname')
self._verify_pc_mapping(pc)
def test_pc_validation_hcheck(self):
fc = self._create_simple_flowc(src_svi=self.src_svi,
dst_svi=self.dst_svi)
ppg = self._create_simple_ppg(pairs=2, check_type='icmp')
ppg2 = self._create_simple_ppg(pairs=2, check_type='tcp',
check_freq=31, check_port=90)
pc = self.create_port_chain(port_pair_groups=[ppg['id'], ppg2['id']],
flow_classifiers=[fc['id']],
expected_res_status=201)['port_chain']
self._verify_pc_mapping(pc)
self._verify_pc_delete(pc)
class TestPortChainSVI(TestPortChain):