/* * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.groupbasedpolicy.renderer.ios_xe_provider.impl.util; import static org.opendaylight.groupbasedpolicy.renderer.ios_xe_provider.impl.manager.PolicyManagerImpl.ActionCase; import static org.opendaylight.groupbasedpolicy.renderer.ios_xe_provider.impl.util.PolicyManagerUtil.ActionInDirection; import static org.opendaylight.groupbasedpolicy.renderer.ios_xe_provider.impl.util.PolicyManagerUtil.createClassMap; import static org.opendaylight.groupbasedpolicy.renderer.ios_xe_provider.impl.util.PolicyManagerUtil.createPolicyMapEntry; import static org.opendaylight.groupbasedpolicy.renderer.ios_xe_provider.impl.util.PolicyManagerUtil.createSecurityGroupMatch; import static org.opendaylight.groupbasedpolicy.renderer.ios_xe_provider.impl.util.PolicyManagerUtil.generateClassMapName; import static org.opendaylight.groupbasedpolicy.renderer.ios_xe_provider.impl.util.PolicyManagerUtil.getTenantId; import static org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.HasDirection.Direction.In; import static org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.HasDirection.Direction.Out; import static org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.EndpointPolicyParticipation.CONSUMER; import static org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.EndpointPolicyParticipation.PROVIDER; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Consumer; import java.util.function.Supplier; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; import com.google.common.util.concurrent.CheckedFuture; import com.google.common.util.concurrent.Futures; import org.opendaylight.controller.md.sal.binding.api.DataBroker; import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction; import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; import org.opendaylight.groupbasedpolicy.api.sf.ChainActionDefinition; import org.opendaylight.groupbasedpolicy.renderer.ios_xe_provider.impl.manager.PolicyConfigurationContext; import org.opendaylight.groupbasedpolicy.renderer.ios_xe_provider.impl.manager.PolicyManagerImpl; import org.opendaylight.groupbasedpolicy.renderer.ios_xe_provider.impl.writer.NetconfTransactionCreator; import org.opendaylight.groupbasedpolicy.renderer.ios_xe_provider.impl.writer.PolicyWriterUtil; import org.opendaylight.sfc.provider.api.SfcProviderRenderedPathAPI; import org.opendaylight.sfc.provider.api.SfcProviderServiceForwarderAPI; import org.opendaylight.sfc.provider.api.SfcProviderServicePathAPI; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.rsp.manager.rev160421.RendererPathStates; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.rsp.manager.rev160421.renderer.path.states.RendererPathState; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.rsp.manager.rev160421.renderer.path.states.RendererPathStateKey; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.rsp.manager.rev160421.renderer.path.states.renderer.path.state.ConfiguredRenderedPaths; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.rsp.manager.rev160421.renderer.path.states.renderer.path.state.configured.rendered.paths.ConfiguredRenderedPath; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.rsp.manager.rev160421.renderer.path.states.renderer.path.state.configured.rendered.paths.ConfiguredRenderedPathKey; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.common.rev151017.RendererName; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.common.rev151017.RspName; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.common.rev151017.SfcName; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.common.rev151017.SffName; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.rsp.rev140701.CreateRenderedPathInput; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.rsp.rev140701.CreateRenderedPathInputBuilder; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.rsp.rev140701.rendered.service.paths.RenderedServicePath; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.rsp.rev140701.rendered.service.paths.rendered.service.path.RenderedServicePathHop; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sff.rev140701.service.function.forwarder.base.SffDataPlaneLocator; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sff.rev140701.service.function.forwarder.base.sff.data.plane.locator.DataPlaneLocator; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sff.rev140701.service.function.forwarders.ServiceFunctionForwarder; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfp.rev140701.ServiceFunctionPaths; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfp.rev140701.service.function.paths.ServiceFunctionPath; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sl.rev140701.data.plane.locator.LocatorType; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sl.rev140701.data.plane.locator.locator.type.Ip; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Address; import org.opendaylight.yang.gen.v1.urn.ios.rev160308._native.ClassMap; import org.opendaylight.yang.gen.v1.urn.ios.rev160308._native.ServiceChain; import org.opendaylight.yang.gen.v1.urn.ios.rev160308._native.ServiceChainBuilder; import org.opendaylight.yang.gen.v1.urn.ios.rev160308._native._class.map.Match; import org.opendaylight.yang.gen.v1.urn.ios.rev160308._native.config.service.chain.grouping.IpBuilder; import org.opendaylight.yang.gen.v1.urn.ios.rev160308._native.policy.map.Class; import org.opendaylight.yang.gen.v1.urn.ios.rev160308._native.service.chain.ServicePath; import org.opendaylight.yang.gen.v1.urn.ios.rev160308._native.service.chain.ServicePathBuilder; import org.opendaylight.yang.gen.v1.urn.ios.rev160308._native.service.chain.ServicePathKey; import org.opendaylight.yang.gen.v1.urn.ios.rev160308._native.service.chain.service.function.forwarder.ServiceFfName; import org.opendaylight.yang.gen.v1.urn.ios.rev160308._native.service.chain.service.function.forwarder.ServiceFfNameBuilder; import org.opendaylight.yang.gen.v1.urn.ios.rev160308._native.service.chain.service.function.forwarder.ServiceFfNameKey; import org.opendaylight.yang.gen.v1.urn.ios.rev160308._native.service.chain.service.path.ConfigServiceChainPathModeBuilder; import org.opendaylight.yang.gen.v1.urn.ios.rev160308._native.service.chain.service.path.config.service.chain.path.mode.ServiceIndexBuilder; import org.opendaylight.yang.gen.v1.urn.ios.rev160308._native.service.chain.service.path.config.service.chain.path.mode.service.index.Services; import org.opendaylight.yang.gen.v1.urn.ios.rev160308._native.service.chain.service.path.config.service.chain.path.mode.service.index.ServicesBuilder; import org.opendaylight.yang.gen.v1.urn.ios.rev160308._native.service.chain.service.path.config.service.chain.path.mode.service.index.services.ServiceTypeChoice; import org.opendaylight.yang.gen.v1.urn.ios.rev160308._native.service.chain.service.path.config.service.chain.path.mode.service.index.services.service.type.choice.ServiceFunctionForwarderBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.RuleName; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.TenantId; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.HasDirection.Direction; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.subject.feature.instance.ParameterValue; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.EndpointPolicyParticipation; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.has.unconfigured.rule.groups.unconfigured.rule.group.UnconfiguredResolvedRuleBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.policy.configuration.renderer.endpoints.renderer.endpoint.PeerEndpoint; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.resolved.policy.rev150828.has.actions.Action; import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.database.rev160308.Sgt; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ServiceChainingUtil { private static final Logger LOG = LoggerFactory.getLogger(ServiceChainingUtil.class); private static final String RSP_SUFFIX = "-gbp-rsp"; private static final String RSP_REVERSED_SUFFIX = "-gbp-rsp-Reverse"; private static long timeout = 5000L; private ServiceChainingUtil() { throw new IllegalAccessError("instance of util class not supported"); } /** * According to provided action, this method gets service function path and collects all info about participation * and orientation of path. According to path symmetricity, participation and direction, one of these cases happens: * 1. Path is asymmetric, and it starts in this classifier (specified by context) - direct chain is created * 2. Path is asymmetric, and it starts in classifier on opposite side of the chain - skipped * 3. Path is symmetric, and it starts in this classifier - direct chain is created * 2. Path is symmetric, and it starts in classifier on opposite side of the chain - reversed path is created * <p> * Behaviour is correct also in case when "this" and "opposite" classifier is the same * * @param peerEndpoint - peer endpoint, used to generate status and access to tenant ID * @param sourceSgt - security group tag of source endpoint * @param destinationSgt - security group tag of destination endpoint * @param actionMap - contains all info to evaluate correct chain orientation according to endpoint participation * @param context - contains policy-map location and status info * @param dataBroker - to access odl datastore */ static void newChainAction(final PeerEndpoint peerEndpoint, final Sgt sourceSgt, final Sgt destinationSgt, final Map<PolicyManagerImpl.ActionCase, ActionInDirection> actionMap, final PolicyConfigurationContext context, final DataBroker dataBroker) { final ActionInDirection actionInDirection = actionMap.get(ActionCase.CHAIN); if (actionInDirection == null) { return; } context.setCurrentUnconfiguredRule(new UnconfiguredResolvedRuleBuilder() .setRuleName(new RuleName(actionInDirection.getRuleName())).build()); // Rule action + orientation final Action action = actionInDirection.getAction(); final EndpointPolicyParticipation participation = actionInDirection.getParticipation(); final Direction direction = actionInDirection.getDirection(); // Get service function path final ServiceFunctionPath servicePath = ServiceChainingUtil.findServicePathFromParameterValues(action.getParameterValue()); if (servicePath == null || servicePath.getName() == null) { final String info = String.format("service-path not found (sourceSgt=%s, destinationSgt=%s)", sourceSgt, destinationSgt); context.appendUnconfiguredRendererEP(StatusUtil.assembleNotConfigurableRendererEPForPeer(context, peerEndpoint, info)); return; } final TenantId tenantId = PolicyManagerUtil.getTenantId(peerEndpoint); if (tenantId == null) { final String info = String.format("tenant-id not found (sourceSgt=%s, destinationSgt=%s)", sourceSgt, destinationSgt); context.appendUnconfiguredRendererEP(StatusUtil.assembleNotConfigurableRendererEPForPeer(context, peerEndpoint, info)); return; } boolean sfcPartSuccessful = true; // Creates direct path in corresponding direction if ((participation.equals(PROVIDER) && direction.equals(Out)) || (participation.equals(CONSUMER) && direction.equals(In))) { final RenderedServicePath renderedServicePath = ServiceChainingUtil.resolveRenderedServicePath(servicePath, tenantId, dataBroker, sourceSgt, destinationSgt, context); sfcPartSuccessful = resolveRemoteSfcComponents(renderedServicePath, context); // Creates reversed path if symmetric } else if (servicePath.isSymmetric()) { final RenderedServicePath renderedServicePath = ServiceChainingUtil.resolveReversedRenderedServicePath(servicePath, tenantId, dataBroker, sourceSgt, destinationSgt, context); sfcPartSuccessful = resolveRemoteSfcComponents(renderedServicePath, context); } if (!sfcPartSuccessful) { final String info = String.format("failed during sfc-part execution (sourceSgt=%s, destinationSgt=%s)", sourceSgt, destinationSgt); context.appendUnconfiguredRendererEP(StatusUtil.assembleNotConfigurableRendererEPForPeer(context, peerEndpoint, info)); } } /** * According to service function path and direction, creates appropriate rendered service path name {@link RspName} * and starts appropriate method which removes policy for resolved endpoint pair * * @param peerEndpoint - contains info about tenant ID * @param sourceSgt - security group tag of source endpoint * @param destinationSgt - security group tag of destination endpoint * @param actionMap - contains all info to evaluate correct chain orientation according to endpoint participation * @param context - contains policy-map location and status info */ static void resolveRemovedChainAction(final PeerEndpoint peerEndpoint, final Sgt sourceSgt, final Sgt destinationSgt, final Map<ActionCase, ActionInDirection> actionMap, final PolicyConfigurationContext context) { final ActionInDirection actionInDirection = actionMap.get(ActionCase.CHAIN); final Action action = actionInDirection.getAction(); final EndpointPolicyParticipation participation = actionInDirection.getParticipation(); final Direction direction = actionInDirection.getDirection(); final ServiceFunctionPath servicePath = ServiceChainingUtil.findServicePathFromParameterValues(action.getParameterValue()); if (servicePath == null || servicePath.getName() == null) { return; } final TenantId tenantId = getTenantId(peerEndpoint); if (tenantId == null) { return; } //Symmetric chain if ((participation.equals(PROVIDER) && direction.equals(Out)) || (participation.equals(CONSUMER) && direction.equals(In))) { final RspName rspName = generateRspName(servicePath, tenantId); resolveRemovedRenderedServicePath(rspName, sourceSgt, destinationSgt, context); } else if (servicePath.isSymmetric()) { final RspName rspName = generateReversedRspName(servicePath, tenantId); resolveRemovedRenderedServicePath(rspName, sourceSgt, destinationSgt, context); } } /** * Service-path (netconf) is created on every netconf device, which contains service function belonging to specific * chain. Classifier has to be able to reach first service function forwarder in order to send packet to chain. If * first service function forwarder is present on the same node as classifier, service-path entry should be already * present (created by IOS-XE renderer in SFC) also with appropriate remote SFF if necessary. If first SFF is on * different node (remote classifier), classifier has to create it's own service-path entry with remote SFF. * * @param renderedServicePath - path classifier has to reach * @param context - contains policy-map location and status info * @return true if everything went good, false otherwise */ public static boolean resolveRemoteSfcComponents(final RenderedServicePath renderedServicePath, final PolicyConfigurationContext context) { final PolicyManagerImpl.PolicyMapLocation location = context.getPolicyMapLocation(); final ServiceFunctionForwarder forwarder = getFirstHopSff(renderedServicePath); if (forwarder == null) { return false; } final SffName sffName = forwarder.getName(); if (forwarder.getSffDataPlaneLocator() == null || forwarder.getSffDataPlaneLocator().isEmpty()) { LOG.warn("Service function forwarder {} does not contain data plane locator", sffName.getValue()); return false; } // TODO only first dpl resolved final SffDataPlaneLocator sffDataPlaneLocator = forwarder.getSffDataPlaneLocator().get(0); final DataPlaneLocator dataPlaneLocator = sffDataPlaneLocator.getDataPlaneLocator(); final LocatorType locatorType = dataPlaneLocator.getLocatorType(); if (locatorType != null && locatorType instanceof Ip) { final IpAddress remoteForwarderIpAddress = (((Ip) locatorType).getIp()); if (remoteForwarderIpAddress == null || remoteForwarderIpAddress.getIpv4Address() == null) { LOG.warn("Service function forwarder {} data plane locator does not contain ip address", sffName.getValue()); return false; } final String remoteForwarderStringIp = remoteForwarderIpAddress.getIpv4Address().getValue(); final java.util.Optional<IpAddress> optionalIpMgmtAddress = java.util.Optional.ofNullable(forwarder.getIpMgmtAddress()); return optionalIpMgmtAddress.map(IpAddress::getIpv4Address) .map(Ipv4Address::getValue) .map(addressValue -> { final ServiceTypeChoice serviceTypeChoice; if (!addressValue.equals(location.getManagementIpAddress())) { // Remote forwarder final ServiceFfNameBuilder remoteSffBuilder = new ServiceFfNameBuilder(); remoteSffBuilder.setName(sffName.getValue()) .setKey(new ServiceFfNameKey(sffName.getValue())) .setIp(new IpBuilder().setAddress(new Ipv4Address(remoteForwarderStringIp)).build()); boolean rsResult = PolicyWriterUtil.writeRemote(remoteSffBuilder.build(), location); context.setFutureResult(Futures.immediateCheckedFuture(rsResult)); serviceTypeChoice = createForwarderTypeChoice(sffName.getValue()); // Service chain final List<Services> services = new ArrayList<>(); final ServicesBuilder servicesBuilder = new ServicesBuilder(); servicesBuilder.setServiceIndexId(renderedServicePath.getStartingIndex()) .setServiceTypeChoice(serviceTypeChoice); services.add(servicesBuilder.build()); final List<ServicePath> servicePaths = new ArrayList<>(); final ServicePathBuilder servicePathBuilder = new ServicePathBuilder(); servicePathBuilder.setKey(new ServicePathKey(renderedServicePath.getPathId())) .setServicePathId(renderedServicePath.getPathId()) .setConfigServiceChainPathMode(new ConfigServiceChainPathModeBuilder() .setServiceIndex(new ServiceIndexBuilder() .setServices(services).build()).build()); servicePaths.add(servicePathBuilder.build()); final ServiceChainBuilder chainBuilder = new ServiceChainBuilder(); chainBuilder.setServicePath(servicePaths); final ServiceChain serviceChain = chainBuilder.build(); boolean scResult = PolicyWriterUtil.writeServicePath(serviceChain, location); context.setFutureResult(Futures.immediateCheckedFuture(scResult)); } return true; }).orElseGet(createNegativePathWithLogSupplier(sffName.getValue(), (value) -> LOG.error("Cannot create remote forwarder, SFF {} does not contain management ip address", value)) ); } return false; } /** * Investigates provided parameter values and derives service chain name. This name is used to find service function * path * * @param params - list of parameters * @return - service function path if found, null if provided parameters does not correspond with any chain or there * is no service function path defined by that chain */ @Nullable static ServiceFunctionPath findServicePathFromParameterValues(final List<ParameterValue> params) { if (params == null || params.isEmpty()) { LOG.error("Cannot found service path, parameter value is null"); return null; } final Map<String, Object> paramsMap = new HashMap<>(); for (ParameterValue value : params) { if (value.getName() == null) continue; if (value.getIntValue() != null) { paramsMap.put(value.getName().getValue(), value.getIntValue()); } else if (value.getStringValue() != null) { paramsMap.put(value.getName().getValue(), value.getStringValue()); } } String chainName = null; for (String name : paramsMap.keySet()) { if (name.equals(ChainActionDefinition.SFC_CHAIN_NAME)) { chainName = (String) paramsMap.get(name); } } if (chainName == null) { LOG.error("Cannot found service path, chain name is null"); return null; } final ServiceFunctionPath serviceFunctionPath = findServiceFunctionPathFromServiceChainName(new SfcName(chainName)); if (serviceFunctionPath == null) { LOG.error("Service function path not found for name {}", chainName); return null; } return serviceFunctionPath; } static ServiceFunctionPath findServiceFunctionPathFromServiceChainName(@Nonnull final SfcName chainName) { final ServiceFunctionPaths allPaths = SfcProviderServicePathAPI.readAllServiceFunctionPaths(); if (allPaths == null || allPaths.getServiceFunctionPath() == null || allPaths.getServiceFunctionPath().isEmpty()) { return null; } for (ServiceFunctionPath serviceFunctionPath : allPaths.getServiceFunctionPath()) { if (chainName.equals(serviceFunctionPath.getServiceChainName())) { return serviceFunctionPath; } } return null; } /** * Creates {@link RenderedServicePath} if not exist. If created, ios-xe renderer in SFC is invoked, so this method * has to wait till SFC part is done to prevent transaction collisions in {@link this#checkRspManagerStatus(RspName, * DataBroker)}. If this operation is successful, class-map {@link ClassMap} and entry in policy-map {@link Class} * is written * * @param sfp - path used to create RSP * @param tenantId - used to generate RSP name according to GBP standards * @param dataBroker - data provider to access odl controller * @param sourceSgt - source security group tag * @param destinationSgt - destination security group tag * @param context - contains policy-map location and status info * @return read/created RSP */ static RenderedServicePath resolveRenderedServicePath(final ServiceFunctionPath sfp, final TenantId tenantId, final DataBroker dataBroker, final Sgt sourceSgt, final Sgt destinationSgt, final PolicyConfigurationContext context) { // Get rendered service path final RspName rspName = generateRspName(sfp, tenantId); RenderedServicePath renderedServicePath = SfcProviderRenderedPathAPI.readRenderedServicePath(rspName); if (renderedServicePath == null) { LOG.info("Rendered service path with name {} not found, creating a new one ..", rspName.getValue()); final CreateRenderedPathInput input = new CreateRenderedPathInputBuilder() .setParentServiceFunctionPath(sfp.getName().getValue()) .setName(rspName.getValue()) .setSymmetric(sfp.isSymmetric()) .build(); renderedServicePath = SfcProviderRenderedPathAPI.createRenderedServicePathAndState(sfp, input); LOG.info("Rendered service path {} created", rspName.getValue()); checkRspManagerStatus(rspName, dataBroker); } // Create class-map and policy-map entry final String classMapName = generateClassMapName(sourceSgt.getValue(), destinationSgt.getValue()); final Match match = createSecurityGroupMatch(sourceSgt.getValue(), destinationSgt.getValue()); final ClassMap classMap = createClassMap(classMapName, match); final Class policyMapEntry = createPolicyMapEntry(classMapName, renderedServicePath, ActionCase.CHAIN); boolean cmResult = PolicyWriterUtil.writeClassMap(classMap, context.getPolicyMapLocation()); context.setFutureResult(Futures.immediateCheckedFuture(cmResult)); boolean pmeResult = PolicyWriterUtil.writePolicyMapEntry(policyMapEntry, context.getPolicyMapLocation()); context.setFutureResult(Futures.immediateCheckedFuture(pmeResult)); return renderedServicePath; } /** * Creates reversed {@link RenderedServicePath} if not exist. To be successful, direct path has to exist. * If created, ios-xe renderer in SFC is invoked, so this method has to wait till SFC part is done to prevent * transaction collisions. If this operation is successful, class-map {@link ClassMap} and entry in policy-map * {@link Class} is written * * @param sfp - path used to create RSP * @param tenantId - used to generate RSP name according to GBP standards * @param dataBroker - data provider to access odl controller * @param sourceSgt - source security group tag * @param destinationSgt - destination security group tag * @param context - contains policy-map location and status info * @return read/created RSP */ public static RenderedServicePath resolveReversedRenderedServicePath(final ServiceFunctionPath sfp, final TenantId tenantId, final DataBroker dataBroker, final Sgt sourceSgt, final Sgt destinationSgt, final PolicyConfigurationContext context) { // Get rendered service path final RspName rspName = generateRspName(sfp, tenantId); RenderedServicePath renderedServicePath = SfcProviderRenderedPathAPI.readRenderedServicePath(rspName); if (renderedServicePath == null) { LOG.info("Rendered service path with name {} not found, creating a new one ..", rspName.getValue()); final CreateRenderedPathInput input = new CreateRenderedPathInputBuilder() .setParentServiceFunctionPath(sfp.getName().getValue()) .setName(rspName.getValue()) .setSymmetric(sfp.isSymmetric()) .build(); renderedServicePath = SfcProviderRenderedPathAPI.createRenderedServicePathAndState(sfp, input); LOG.info("Rendered service path {} created", rspName.getValue()); checkRspManagerStatus(rspName, dataBroker); } // Get reversed rendered service path final RspName reversedRspName = generateReversedRspName(sfp, tenantId); RenderedServicePath reversedRenderedPath = SfcProviderRenderedPathAPI.readRenderedServicePath(reversedRspName); if (reversedRenderedPath == null) { LOG.info("Reversed rendered service path with name {} not found, creating a new one ..", reversedRspName.getValue()); reversedRenderedPath = SfcProviderRenderedPathAPI.createReverseRenderedServicePathEntry(renderedServicePath); LOG.info("Rendered service path {} created", reversedRspName.getValue()); checkRspManagerStatus(reversedRspName, dataBroker); } // Create class-map and policy-map entry final String classMapName = generateClassMapName(sourceSgt.getValue(), destinationSgt.getValue()); final Match match = createSecurityGroupMatch(sourceSgt.getValue(), destinationSgt.getValue()); final ClassMap classMap = createClassMap(classMapName, match); final Class policyMapEntry = createPolicyMapEntry(classMapName, renderedServicePath, ActionCase.CHAIN); boolean cmResult = PolicyWriterUtil.writeClassMap(classMap, context.getPolicyMapLocation()); context.setFutureResult(Futures.immediateCheckedFuture(cmResult)); boolean pmeResult = PolicyWriterUtil.writePolicyMapEntry(policyMapEntry, context.getPolicyMapLocation()); context.setFutureResult(Futures.immediateCheckedFuture(pmeResult)); resolveRemoteSfcComponents(renderedServicePath, context); return reversedRenderedPath; } /** * Removes all policy setup created according to rendered service path. * * @param rspName - rendered service path name * @param sourceSgt - source security group tag * @param destinationSgt - destination security group tag * @param context - context with policy-map location */ private static void resolveRemovedRenderedServicePath(final RspName rspName, final Sgt sourceSgt, final Sgt destinationSgt, final PolicyConfigurationContext context) { final String classMapName = PolicyManagerUtil.generateClassMapName(sourceSgt.getValue(), destinationSgt.getValue()); final ClassMap classMap = PolicyManagerUtil.createClassMap(classMapName, null); final Class policyMapEntry = PolicyManagerUtil.createPolicyMapEntry(classMapName, null, PolicyManagerImpl.ActionCase.CHAIN); PolicyWriterUtil.removePolicyMapEntry(policyMapEntry, context.getPolicyMapLocation()); PolicyWriterUtil.removeClassMap(classMap, context.getPolicyMapLocation()); final RenderedServicePath renderedServicePath = SfcProviderRenderedPathAPI.readRenderedServicePath(rspName); final ServiceFunctionForwarder firstHopSff = getFirstHopSff(renderedServicePath); if (firstHopSff != null && firstHopSff.getIpMgmtAddress() != null && firstHopSff.getIpMgmtAddress().getIpv4Address() != null) { final String sffMgmtIpValue = firstHopSff.getIpMgmtAddress().getIpv4Address().getValue(); if (!sffMgmtIpValue.equals(context.getPolicyMapLocation().getManagementIpAddress())) { // Remove service chain and remote forwarder final ServiceChain serviceChain = createServiceChain(renderedServicePath); final ServiceFfName remoteForwarder = createRemoteForwarder(firstHopSff); PolicyWriterUtil.removeServicePath(serviceChain, context.getPolicyMapLocation()); PolicyWriterUtil.removeRemote(remoteForwarder, context.getPolicyMapLocation()); } } } static ServiceFfName createRemoteForwarder(ServiceFunctionForwarder firstHopSff) { final ServiceFfNameBuilder serviceFfNameBuilder = new ServiceFfNameBuilder(); serviceFfNameBuilder.setName(firstHopSff.getName().getValue()); return serviceFfNameBuilder.build(); } private static ServiceTypeChoice createForwarderTypeChoice(final String forwarderName) { final ServiceFunctionForwarderBuilder sffBuilder = new ServiceFunctionForwarderBuilder(); sffBuilder.setServiceFunctionForwarder(forwarderName); return sffBuilder.build(); } /** * Creates service-chain with name/key only, using rendered service path id. This object contains no data, it is used * to create instance identifier when appropriate service-chain is removed from particular device * * @param renderedServicePath - it's path id is used as a identifier * @return service-chain object with id */ private static ServiceChain createServiceChain(final RenderedServicePath renderedServicePath) { final Long pathId = renderedServicePath.getPathId(); final ServicePathBuilder servicePathBuilder = new ServicePathBuilder(); final ServiceChainBuilder serviceChainBuilder = new ServiceChainBuilder(); servicePathBuilder.setServicePathId(pathId) .setKey(new ServicePathKey(pathId)); serviceChainBuilder.setServicePath(Collections.singletonList(servicePathBuilder.build())); return serviceChainBuilder.build(); } private static <T> Supplier<Boolean> createNegativePathWithLogSupplier(final T value, final Consumer<T> logCommand) { return () -> { // fireLog logCommand.accept(value); return false; }; } private static ServiceFunctionForwarder getFirstHopSff(RenderedServicePath renderedServicePath) { if (renderedServicePath == null || renderedServicePath.getRenderedServicePathHop() == null || renderedServicePath.getRenderedServicePathHop().isEmpty()) { return null; } final RenderedServicePathHop firstHop = renderedServicePath.getRenderedServicePathHop().get(0); final SffName firstHopSff = firstHop.getServiceFunctionForwarder(); if (firstHopSff == null) { return null; } return SfcProviderServiceForwarderAPI.readServiceFunctionForwarder(firstHopSff); } private static RspName generateRspName(final ServiceFunctionPath serviceFunctionPath, final TenantId tenantId) { return new RspName(serviceFunctionPath.getName().getValue() + "-" + tenantId.getValue() + RSP_SUFFIX); } private static RspName generateReversedRspName(final ServiceFunctionPath serviceFunctionPath, final TenantId tenantId) { return new RspName(serviceFunctionPath.getName().getValue() + "-" + tenantId.getValue() + RSP_REVERSED_SUFFIX); } private static void checkRspManagerStatus(final RspName rspName, final DataBroker dataBroker) { // TODO A better way to do this is to register listener and wait for notification than using hardcoded timeout // with Thread.sleep(). Example in class BridgeDomainManagerImpl ConfiguredRenderedPath renderedPath = null; LOG.debug("Waiting for SFC to configure path {} ...", rspName.getValue()); byte attempt = 0; do { attempt++; // Wait try { Thread.sleep(timeout); } catch (InterruptedException e) { LOG.error("Thread interrupted while waiting ... {} ", e); } // Read actual status final InstanceIdentifier<ConfiguredRenderedPath> statusIid = InstanceIdentifier.builder(RendererPathStates.class) .child(RendererPathState.class, new RendererPathStateKey(new RendererName("ios-xe-renderer"))) .child(ConfiguredRenderedPaths.class) .child(ConfiguredRenderedPath.class, new ConfiguredRenderedPathKey(rspName)).build(); final java.util.Optional<ReadWriteTransaction> optionalTransaction = NetconfTransactionCreator.netconfReadWriteTransaction(dataBroker); if (!optionalTransaction.isPresent()) { LOG.warn("Failed to create transaction, mountpoint: {}", dataBroker); return; } ReadWriteTransaction transaction = optionalTransaction.get(); try { final CheckedFuture<Optional<ConfiguredRenderedPath>, ReadFailedException> submitFuture = transaction.read(LogicalDatastoreType.OPERATIONAL, statusIid); final Optional<ConfiguredRenderedPath> optionalPath = submitFuture.checkedGet(); if (optionalPath.isPresent()) { renderedPath = optionalPath.get(); } } catch (ReadFailedException e) { LOG.warn("Failed while read rendered path status ... {} ", e.getMessage()); } if (renderedPath == null || renderedPath.getPathStatus() == null || renderedPath.getPathStatus().equals(ConfiguredRenderedPath.PathStatus.InProgress)) { LOG.info("Still waiting for SFC ... "); } else if (renderedPath.getPathStatus().equals(ConfiguredRenderedPath.PathStatus.Failure)) { LOG.warn("SFC failed to configure rsp"); } else if (renderedPath.getPathStatus().equals(ConfiguredRenderedPath.PathStatus.Success)) { LOG.debug("RSP {} configured by SFC", rspName.getValue()); try { Thread.sleep(timeout); // Just for sure, maybe will be safe to remove this } catch (InterruptedException e) { LOG.error("Thread interrupted while waiting ... {} ", e); } return; } } while (attempt <= 6); LOG.warn("Maximum number of attempts reached"); } /** * Only for test purposes * * @param value - set actual timeout value */ @VisibleForTesting public static void setTimeout(long value) { timeout = value; } }