/* * Copyright 2016-present Open Networking Laboratory * * 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. */ package org.onosproject.net.intent.impl.compiler; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.SetMultimap; import com.google.common.collect.Sets; import org.onlab.packet.EthType; import org.onlab.packet.Ethernet; import org.onlab.packet.Ip4Address; import org.onlab.packet.IpPrefix; import org.onlab.packet.MplsLabel; import org.onlab.packet.VlanId; import org.onlab.util.Identifier; import org.onosproject.net.ConnectPoint; import org.onosproject.net.DeviceId; import org.onosproject.net.EncapsulationType; import org.onosproject.net.FilteredConnectPoint; import org.onosproject.net.Link; import org.onosproject.net.PortNumber; import org.onosproject.net.domain.DomainId; import org.onosproject.net.domain.DomainPointToPointIntent; import org.onosproject.net.domain.DomainService; import org.onosproject.net.flow.DefaultTrafficSelector; import org.onosproject.net.flow.DefaultTrafficTreatment; import org.onosproject.net.flow.TrafficSelector; import org.onosproject.net.flow.TrafficTreatment; import org.onosproject.net.flow.criteria.Criteria; import org.onosproject.net.flow.criteria.Criterion; import org.onosproject.net.flow.criteria.EthTypeCriterion; import org.onosproject.net.flow.criteria.MplsCriterion; import org.onosproject.net.flow.criteria.TunnelIdCriterion; import org.onosproject.net.flow.criteria.VlanIdCriterion; import org.onosproject.net.flow.instructions.Instruction; import org.onosproject.net.flow.instructions.L0ModificationInstruction; import org.onosproject.net.flow.instructions.L1ModificationInstruction; import org.onosproject.net.flow.instructions.L2ModificationInstruction; import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction; import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsBosInstruction; import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsLabelInstruction; import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModTunnelIdInstruction; import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction; import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanPcpInstruction; import org.onosproject.net.flow.instructions.L3ModificationInstruction; import org.onosproject.net.flow.instructions.L3ModificationInstruction.ModArpEthInstruction; import org.onosproject.net.flow.instructions.L3ModificationInstruction.ModArpIPInstruction; import org.onosproject.net.flow.instructions.L3ModificationInstruction.ModArpOpInstruction; import org.onosproject.net.flow.instructions.L3ModificationInstruction.ModIPInstruction; import org.onosproject.net.flow.instructions.L3ModificationInstruction.ModIPv6FlowLabelInstruction; import org.onosproject.net.flow.instructions.L4ModificationInstruction; import org.onosproject.net.flow.instructions.L4ModificationInstruction.ModTransportPortInstruction; import org.onosproject.net.intent.Intent; import org.onosproject.net.intent.IntentCompilationException; import org.onosproject.net.intent.LinkCollectionIntent; import org.onosproject.net.intent.constraint.DomainConstraint; import org.onosproject.net.intent.constraint.EncapsulationConstraint; import org.onosproject.net.resource.impl.LabelAllocator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import static org.onosproject.net.domain.DomainId.LOCAL; import static org.onosproject.net.flow.criteria.Criterion.Type.*; /** * Shared APIs and implementations for Link Collection compilers. */ public abstract class LinkCollectionCompiler<T> { /** * Reference to the label allocator. */ static LabelAllocator labelAllocator; /** * Influence compiler behavior. If true the compiler * try to optimize the chain of the actions. */ static boolean optimizeInstructions; /** * Influence compiler behavior. If true the compiler * try to optimize the copy ttl actions. */ static boolean copyTtl; /** * The allowed tag criterions. */ private static final Set<Criterion.Type> TAG_CRITERION_TYPES = Sets.immutableEnumSet(VLAN_VID, MPLS_LABEL, TUNNEL_ID); /** * Error message for wrong egress scenario. */ private static final String WRONG_EGRESS = "Egress points not equal to 1 " + "and apply treatment at ingress, " + "which treatments should I apply ???"; /** * Error message for wrong ingress scenario. */ private static final String WRONG_INGRESS = "Ingress points not equal to 1 " + "and apply treatment at egress, " + "how can I match in the core ???"; /** * Error message for wrong encapsulation scenario. */ private static final String WRONG_ENCAPSULATION = "Wrong scenario - 1 hop with " + "encapsualtion"; /** * Error message for unavailable labels. */ private static final String NO_LABELS = "No available label for %s"; /** * Error message for wrong encapsulation. */ private static final String UNKNOWN_ENCAPSULATION = "Unknown encapsulation type"; /** * Error message for unsupported L0 instructions. */ private static final String UNSUPPORTED_L0 = "L0 not supported"; /** * Error message for unsupported L1 instructions. */ private static final String UNSUPPORTED_L1 = "L1 not supported"; /** * Error message for unsupported eth subtype. */ private static final String UNSUPPORTED_ETH_SUBTYPE = "Bad eth subtype"; /** * Error message for unsupported pop action. */ private static final String UNSUPPORTED_POP_ACTION = "Can't handle pop label"; /** * Error message for unsupported L2 instructions. */ private static final String UNSUPPORTED_L2 = "Unknown L2 Modification instruction"; /** * Error message for unsupported IP subtype. */ private static final String UNSUPPORTED_IP_SUBTYPE = "Bad ip subtype"; /** * Error message for unsupported ARP. */ private static final String UNSUPPORTED_ARP = "IPv6 not supported for ARP"; /** * Error message for unsupported L3 instructions. */ private static final String UNSUPPORTED_L3 = "Unknown L3 Modification instruction"; /** * Error message for unsupported L4 subtype. */ private static final String UNSUPPORTED_L4_SUBTYPE = "Unknown L4 subtype"; /** * Error message for unsupported L4 instructions. */ private static final String UNSUPPORTED_L4 = "Unknown L4 Modification instruction"; /** * Error message for unsupported instructions. */ private static final String UNSUPPORTED_INSTRUCTION = "Unknown instruction type"; private static Logger log = LoggerFactory.getLogger(LinkCollectionCompiler.class); /** * Influence compiler behavior. * * @return true if we need the compiler optimizeTreatments the chain of the actions. */ abstract boolean optimizeTreatments(); /** * Creates the flows representations. This default implementation does * nothing. Subclasses should override this method to create their * specific flows representations (flow rule, flow objective). * * @param intent the intent to compile * @param deviceId the affected device * @param inPorts the input ports * @param outPorts the output ports * @param labels the labels for the label switching hop by hop * @return the list of flows representations */ protected List<T> createRules(LinkCollectionIntent intent, DeviceId deviceId, Set<PortNumber> inPorts, Set<PortNumber> outPorts, Map<ConnectPoint, Identifier<?>> labels) { return null; } /** * Helper method to handle the different scenario (not encap, single hop, encap). * * @param encapConstraint the encapsulation constraint if it is present * @param intent the link collection intent * @param inPort the in port * @param outPorts the out ports * @param deviceId the current device * @param labels the labels used by the encapsulation * @return the forwarding instruction */ protected ForwardingInstructions createForwardingInstruction(Optional<EncapsulationConstraint> encapConstraint, LinkCollectionIntent intent, PortNumber inPort, Set<PortNumber> outPorts, DeviceId deviceId, Map<ConnectPoint, Identifier<?>> labels) { ForwardingInstructions instructions = null; /* * If not encapsulation or single hop. */ if (!encapConstraint.isPresent() || intent.links().isEmpty()) { instructions = this.createForwardingInstructions( intent, inPort, deviceId, outPorts ); /* * If encapsulation is present. We retrieve the labels * for this iteration; */ } else { Identifier<?> inLabel = labels.get(new ConnectPoint(deviceId, inPort)); Map<ConnectPoint, Identifier<?>> outLabels = Maps.newHashMap(); outPorts.forEach(outPort -> { ConnectPoint key = new ConnectPoint(deviceId, outPort); outLabels.put(key, labels.get(key)); }); instructions = this.createForwardingInstructions( intent, inPort, inLabel, deviceId, outPorts, outLabels, encapConstraint.get().encapType() ); } return instructions; } /** * Helper method which handles the proper generation of the ouput actions. * * @param outPorts the output ports * @param deviceId the current device * @param intent the intent to compile * @param outLabels the output labels * @param type the encapsulation type * @param preCondition the previous state * @param treatmentBuilder the builder to update with the ouput actions */ private void manageOutputPorts(Set<PortNumber> outPorts, DeviceId deviceId, LinkCollectionIntent intent, Map<ConnectPoint, Identifier<?>> outLabels, EncapsulationType type, TrafficSelector.Builder preCondition, TrafficTreatment.Builder treatmentBuilder) { /* * We need to order the actions. First the actions * related to the not-egress points. At the same time we collect * also the egress points. */ List<FilteredConnectPoint> egressPoints = Lists.newArrayList(); for (PortNumber outPort : outPorts) { Optional<FilteredConnectPoint> filteredEgressPoint = getFilteredConnectPointFromIntent(deviceId, outPort, intent); if (!filteredEgressPoint.isPresent()) { /* * We build a temporary selector for the encapsulation. */ TrafficSelector.Builder encapBuilder = DefaultTrafficSelector.builder(); /* * We retrieve the associated label to the output port. */ ConnectPoint cp = new ConnectPoint(deviceId, outPort); Identifier<?> outLabel = outLabels.get(cp); /* * If there are not labels, we cannot handle. */ if (outLabel == null) { throw new IntentCompilationException(String.format(NO_LABELS, cp)); } /* * In the core we match using encapsulation. */ updateSelectorFromEncapsulation( encapBuilder, type, outLabel ); /* * We generate the transition. */ TrafficTreatment forwardingTreatment = forwardingTreatment(preCondition.build(), encapBuilder.build(), getEthType(intent.selector())); /* * We add the instruction necessary to the transition. */ forwardingTreatment.allInstructions().stream() .filter(inst -> inst.type() != Instruction.Type.NOACTION) .forEach(treatmentBuilder::add); /* * Finally we set the output action. */ treatmentBuilder.setOutput(outPort); /* * The encapsulation modifies the packet. If we are optimizing * we have to update the state. */ if (optimizeTreatments()) { preCondition = encapBuilder; } } else { egressPoints.add(filteredEgressPoint.get()); } } /* * The idea is to order the egress points. Before we deal * with the egress points which looks like similar to the * selector derived from the previous state then the * the others. */ TrafficSelector prevState = preCondition.build(); if (optimizeTreatments()) { egressPoints = orderedEgressPoints(prevState, egressPoints); } /* * In this case, we have to transit to the final * state. */ generateEgressActions(treatmentBuilder, egressPoints, prevState, intent); } /** * Helper method to generate the egress actions. * * @param treatmentBuilder the treatment builder to update * @param egressPoints the egress points * @param initialState the initial state of the transition */ private void generateEgressActions(TrafficTreatment.Builder treatmentBuilder, List<FilteredConnectPoint> egressPoints, TrafficSelector initialState, LinkCollectionIntent intent) { TrafficSelector prevState = initialState; for (FilteredConnectPoint egressPoint : egressPoints) { /* * If we are at the egress, we have to transit to the final * state. First we add the Intent treatment. */ intent.treatment().allInstructions().stream() .filter(inst -> inst.type() != Instruction.Type.NOACTION) .forEach(treatmentBuilder::add); /* * We generate the transition FIP->FEP. */ TrafficTreatment forwardingTreatment = forwardingTreatment(prevState, egressPoint.trafficSelector(), getEthType(intent.selector())); /* * We add the instruction necessary to the transition. * Potentially we override the intent treatment. */ forwardingTreatment.allInstructions().stream() .filter(inst -> inst.type() != Instruction.Type.NOACTION) .forEach(treatmentBuilder::add); /* * Finally we set the output action. */ treatmentBuilder.setOutput(egressPoint.connectPoint().port()); if (optimizeTreatments()) { /* * We update the previous state. In this way instead of * transiting from FIP->FEP we do FEP->FEP and so on. */ prevState = egressPoint.trafficSelector(); } } } /** * Helper method to order the egress ports according to a * specified criteria. The idea is to generate first the actions * for the egress ports which are similar to the specified criteria * then the others. In this way we can mitigate the problems related * to the chain of actions and we can optimize also the number of * actions. * * @param orderCriteria the ordering criteria * @param pointsToOrder the egress points to order * @return a list of port ordered */ private List<FilteredConnectPoint> orderedEgressPoints(TrafficSelector orderCriteria, List<FilteredConnectPoint> pointsToOrder) { /* * We are interested only to the labels. The idea is to order * by the tags. * */ Criterion vlanIdCriterion = orderCriteria.getCriterion(VLAN_VID); Criterion mplsLabelCriterion = orderCriteria.getCriterion(MPLS_LABEL); /* * We collect all the untagged points. * */ List<FilteredConnectPoint> untaggedEgressPoints = pointsToOrder .stream() .filter(pointToOrder -> { TrafficSelector selector = pointToOrder.trafficSelector(); return selector.getCriterion(VLAN_VID) == null && selector.getCriterion(MPLS_LABEL) == null; }).collect(Collectors.toList()); /* * We collect all the vlan points. */ List<FilteredConnectPoint> vlanEgressPoints = pointsToOrder .stream() .filter(pointToOrder -> { TrafficSelector selector = pointToOrder.trafficSelector(); return selector.getCriterion(VLAN_VID) != null && selector.getCriterion(MPLS_LABEL) == null; }).collect(Collectors.toList()); /* * We collect all the mpls points. */ List<FilteredConnectPoint> mplsEgressPoints = pointsToOrder .stream() .filter(pointToOrder -> { TrafficSelector selector = pointToOrder.trafficSelector(); return selector.getCriterion(VLAN_VID) == null && selector.getCriterion(MPLS_LABEL) != null; }).collect(Collectors.toList()); /* * We create the final list of ports. */ List<FilteredConnectPoint> orderedList = Lists.newArrayList(); /* * The ordering criteria is vlan id. First we add the vlan * ports. Then the others. */ if (vlanIdCriterion != null && mplsLabelCriterion == null) { orderedList.addAll(vlanEgressPoints); orderedList.addAll(untaggedEgressPoints); orderedList.addAll(mplsEgressPoints); return orderedList; } /* * The ordering criteria is mpls label. First we add the mpls * ports. Then the others. */ if (vlanIdCriterion == null && mplsLabelCriterion != null) { orderedList.addAll(mplsEgressPoints); orderedList.addAll(untaggedEgressPoints); orderedList.addAll(vlanEgressPoints); return orderedList; } /* * The ordering criteria is untagged. First we add the untagged * ports. Then the others. */ if (vlanIdCriterion == null && mplsLabelCriterion == null) { orderedList.addAll(untaggedEgressPoints); orderedList.addAll(vlanEgressPoints); orderedList.addAll(mplsEgressPoints); return orderedList; } /* * Unhandled scenario. */ orderedList.addAll(vlanEgressPoints); orderedList.addAll(mplsEgressPoints); orderedList.addAll(untaggedEgressPoints); return orderedList; } /** * Manages the Intents with a single ingress point (p2p, sp2mp) * creating properly the selector builder and the treatment builder. * * @param selectorBuilder the selector builder to update * @param treatmentBuilder the treatment builder to update * @param intent the intent to compile * @param deviceId the current device * @param outPorts the output ports of this device */ private void manageSpIntent(TrafficSelector.Builder selectorBuilder, TrafficTreatment.Builder treatmentBuilder, LinkCollectionIntent intent, DeviceId deviceId, Set<PortNumber> outPorts) { /* * Sanity check. */ if (intent.filteredIngressPoints().size() != 1) { throw new IntentCompilationException(WRONG_INGRESS); } /* * For the p2p and sp2mp the transition initial state * to final state is performed at the egress. */ Optional<FilteredConnectPoint> filteredIngressPoint = intent.filteredIngressPoints().stream().findFirst(); /* * We build the final selector, adding the selector * of the FIP to the Intent selector and potentially * overriding its matches. */ filteredIngressPoint.get() .trafficSelector() .criteria() .forEach(selectorBuilder::add); /* * In this scenario, potentially we can have several output * ports. First we have to insert in the treatment the actions * for the core. */ List<FilteredConnectPoint> egressPoints = Lists.newArrayList(); for (PortNumber outPort : outPorts) { Optional<FilteredConnectPoint> filteredEgressPoint = getFilteredConnectPointFromIntent(deviceId, outPort, intent); if (!filteredEgressPoint.isPresent()) { treatmentBuilder.setOutput(outPort); } else { egressPoints.add(filteredEgressPoint.get()); } } /* * The idea is to order the egress points. Before we deal * with the egress points which looks like similar to the ingress * point then the others. */ TrafficSelector prevState = filteredIngressPoint.get().trafficSelector(); if (optimizeTreatments()) { egressPoints = orderedEgressPoints(prevState, egressPoints); } /* * Then we deal with the egress points. */ generateEgressActions(treatmentBuilder, egressPoints, prevState, intent); } /** * Manages the Intents with multiple ingress points creating properly * the selector builder and the treatment builder. * * @param selectorBuilder the selector builder to update * @param treatmentBuilder the treatment builder to update * @param intent the intent to compile * @param inPort the input port of the current device * @param deviceId the current device * @param outPorts the output ports of this device */ private void manageMpIntent(TrafficSelector.Builder selectorBuilder, TrafficTreatment.Builder treatmentBuilder, LinkCollectionIntent intent, PortNumber inPort, DeviceId deviceId, Set<PortNumber> outPorts) { /* * Sanity check */ if (intent.filteredEgressPoints().size() != 1) { throw new IntentCompilationException(WRONG_EGRESS); } /* * We try to understand if the device is one of the ingress points. */ Optional<FilteredConnectPoint> filteredIngressPoint = getFilteredConnectPointFromIntent(deviceId, inPort, intent); /* * We retrieve from the Intent the unique egress points. */ Optional<FilteredConnectPoint> filteredEgressPoint = intent.filteredEgressPoints().stream().findFirst(); /* * We check if the device is the ingress device */ if (filteredIngressPoint.isPresent()) { /* * We are at ingress, so basically what we have to do is this: * apply a set of operations (treatment, FEP) in order to have * a transition from the initial state to the final state. * * We initialize the treatment with the Intent treatment */ intent.treatment().allInstructions().stream() .filter(inst -> inst.type() != Instruction.Type.NOACTION) .forEach(treatmentBuilder::add); /* * We build the final selector, adding the selector * of the FIP to the Intent selector and potentially * overriding its matches. */ filteredIngressPoint.get() .trafficSelector() .criteria() .forEach(selectorBuilder::add); /* * We define the transition FIP->FEP, basically * the set of the operations we need for reaching * the final state. */ TrafficTreatment forwardingTreatment = forwardingTreatment(filteredIngressPoint.get().trafficSelector(), filteredEgressPoint.get().trafficSelector(), getEthType(intent.selector())); /* * We add to the treatment the actions necessary for the * transition, potentially overriding the treatment of the * Intent. The Intent treatment has always a low priority * in respect of the FEP. */ forwardingTreatment.allInstructions().stream() .filter(inst -> inst.type() != Instruction.Type.NOACTION) .forEach(treatmentBuilder::add); } else { /* * We are in the core or in the egress switch. * The packets are in their final state. We need * to match against this final state. * * we derive the final state defined by the intent * treatment. */ updateBuilder(selectorBuilder, intent.treatment()); /* * We derive the final state defined by the unique * FEP. We merge the two states. */ filteredEgressPoint.get() .trafficSelector() .criteria() .forEach(selectorBuilder::add); } /* * Finally we set the output action. */ outPorts.forEach(treatmentBuilder::setOutput); } /** * Computes treatment and selector which will be used * in the flow representation (Rule, Objective). * * @param intent the intent to compile * @param inPort the input port of this device * @param deviceId the current device * @param outPorts the output ports of this device * @return the forwarding instruction object which encapsulates treatment and selector */ protected ForwardingInstructions createForwardingInstructions(LinkCollectionIntent intent, PortNumber inPort, DeviceId deviceId, Set<PortNumber> outPorts) { /* * We build an empty treatment and we initialize the selector with * the intent selector. */ TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment .builder(); TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector .builder(intent.selector()) .matchInPort(inPort); if (!intent.applyTreatmentOnEgress()) { manageMpIntent(selectorBuilder, treatmentBuilder, intent, inPort, deviceId, outPorts ); } else { manageSpIntent(selectorBuilder, treatmentBuilder, intent, deviceId, outPorts ); } /* * We return selector and treatment necessary to build the flow rule * or the flow objective. */ return new ForwardingInstructions(treatmentBuilder.build(), selectorBuilder.build()); } /** * Manages the ingress of the Intents (p2p, sp2mp, mp2sp) with encapsulation. * * @param selectorBuilder the selector builder to update * @param treatmentBuilder the treatment builder to update * @param intent the intent to compile * @param inPort the input port of this device * @param deviceId the current device * @param outPorts the output ports of this device * @param outLabels the labels associated to the output port * @param type the encapsulation type */ private void manageEncapAtIngress(TrafficSelector.Builder selectorBuilder, TrafficTreatment.Builder treatmentBuilder, LinkCollectionIntent intent, PortNumber inPort, DeviceId deviceId, Set<PortNumber> outPorts, Map<ConnectPoint, Identifier<?>> outLabels, EncapsulationType type) { Optional<FilteredConnectPoint> filteredIngressPoint = getFilteredConnectPointFromIntent(deviceId, inPort, intent); /* * We fill the selector builder with the intent selector. */ intent.selector().criteria().forEach(selectorBuilder::add); /* * We build the final selector, adding the selector * of the FIP to the Intent selector and potentially * overriding its matches. */ filteredIngressPoint.get() .trafficSelector() .criteria() .forEach(selectorBuilder::add); /* * In this case the precondition is the selector of the filtered * ingress point. */ TrafficSelector.Builder preCondition = DefaultTrafficSelector .builder(filteredIngressPoint.get().trafficSelector()); /* * Generate the output actions. */ manageOutputPorts( outPorts, deviceId, intent, outLabels, type, preCondition, treatmentBuilder ); } /** * Manages the core and transit of the Intents (p2p, sp2mp, mp2sp) * with encapsulation. * * @param selectorBuilder the selector builder to update * @param treatmentBuilder the treatment builder to update * @param intent the intent to compile * @param inPort the input port of this device * @param inLabel the label associated to the input port * @param deviceId the current device * @param outPorts the output ports of this device * @param outLabels the labels associated to the output port * @param type the encapsulation type */ private void manageEncapAtCoreAndEgress(TrafficSelector.Builder selectorBuilder, TrafficTreatment.Builder treatmentBuilder, LinkCollectionIntent intent, PortNumber inPort, Identifier<?> inLabel, DeviceId deviceId, Set<PortNumber> outPorts, Map<ConnectPoint, Identifier<?>> outLabels, EncapsulationType type) { /* * If there are not labels, we cannot handle. */ ConnectPoint inCp = new ConnectPoint(deviceId, inPort); if (inLabel == null) { throw new IntentCompilationException(String.format(NO_LABELS, inCp)); } /* * In the core and at egress we match using encapsulation. */ updateSelectorFromEncapsulation( selectorBuilder, type, inLabel ); /* * Generate the output actions. */ manageOutputPorts( outPorts, deviceId, intent, outLabels, type, selectorBuilder, treatmentBuilder ); } /** * Computes treatment and selector which will be used * in the flow representation (Rule, Objective). * * @param intent the intent to compile * @param inPort the input port of this device * @param inLabel the label associated to the input port * @param deviceId the current device * @param outPorts the output ports of this device * @param outLabels the labels associated to the output port * @param type the encapsulation type * @return the forwarding instruction object which encapsulates treatment and selector */ protected ForwardingInstructions createForwardingInstructions(LinkCollectionIntent intent, PortNumber inPort, Identifier<?> inLabel, DeviceId deviceId, Set<PortNumber> outPorts, Map<ConnectPoint, Identifier<?>> outLabels, EncapsulationType type) { /* * We build an empty treatment and an empty selector. */ TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder(); TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder(); selectorBuilder.matchInPort(inPort); Optional<FilteredConnectPoint> filteredIngressPoint = getFilteredConnectPointFromIntent(deviceId, inPort, intent); if (filteredIngressPoint.isPresent()) { manageEncapAtIngress(selectorBuilder, treatmentBuilder, intent, inPort, deviceId, outPorts, outLabels, type ); } else { manageEncapAtCoreAndEgress(selectorBuilder, treatmentBuilder, intent, inPort, inLabel, deviceId, outPorts, outLabels, type); } /* * We return selector and treatment necessary to build the flow rule * or the flow objective. */ return new ForwardingInstructions(treatmentBuilder.build(), selectorBuilder.build()); } /** * Helper class to encapsulate treatment and selector * in an unique abstraction. */ protected class ForwardingInstructions { private TrafficTreatment trafficTreatment; private TrafficSelector trafficSelector; public ForwardingInstructions(TrafficTreatment treatment, TrafficSelector selector) { this.trafficTreatment = treatment; this.trafficSelector = selector; } public TrafficTreatment treatment() { return this.trafficTreatment; } public TrafficSelector selector() { return this.trafficSelector; } } /** * Helper method to compute input and output ports * for each device crossed in the path. * * @param intent the related intents * @param inputPorts the input ports to compute * @param outputPorts the output ports to compute */ protected void computePorts(LinkCollectionIntent intent, SetMultimap<DeviceId, PortNumber> inputPorts, SetMultimap<DeviceId, PortNumber> outputPorts) { for (Link link : intent.links()) { inputPorts.put(link.dst().deviceId(), link.dst().port()); outputPorts.put(link.src().deviceId(), link.src().port()); } for (ConnectPoint ingressPoint : intent.ingressPoints()) { inputPorts.put(ingressPoint.deviceId(), ingressPoint.port()); } for (ConnectPoint egressPoint : intent.egressPoints()) { outputPorts.put(egressPoint.deviceId(), egressPoint.port()); } } /** * Retrieves the encapsulation constraint from the link collection intent. * * @param intent the intent to analyze * @return the encapsulation constraint */ protected Optional<EncapsulationConstraint> getIntentEncapConstraint(LinkCollectionIntent intent) { return intent.constraints().stream() .filter(constraint -> constraint instanceof EncapsulationConstraint) .map(x -> (EncapsulationConstraint) x).findAny(); } /** * Checks if domain processing is enabled for this intent by looking for the {@link DomainConstraint}. * * @param intent the intent to be checked * @return is the processing of domains enabled */ protected boolean isDomainProcessingEnabled(LinkCollectionIntent intent) { return intent.constraints().contains(DomainConstraint.domain()); } /** * Creates the domain intents that the {@link LinkCollectionIntent} contains. * * @param intent the link collection intent * @param domainService the domain service * @return the resulting list of domain intents */ protected List<Intent> getDomainIntents(LinkCollectionIntent intent, DomainService domainService) { ImmutableList.Builder<Intent> intentList = ImmutableList.builder(); // domain handling is only applied for a single entry and exit point // TODO: support multi point to multi point if (intent.filteredIngressPoints().size() != 1 || intent .filteredEgressPoints().size() != 1) { log.warn("Multiple ingress or egress ports not supported!"); return intentList.build(); } ImmutableList.Builder<Link> domainLinks = ImmutableList.builder(); // get the initial ingress connection point FilteredConnectPoint ingress = intent.filteredIngressPoints().iterator().next(); FilteredConnectPoint egress; DeviceId currentDevice = ingress.connectPoint().deviceId(); // the current domain (or LOCAL) DomainId currentDomain = domainService.getDomain(currentDevice); // if we entered a domain store the domain ingress FilteredConnectPoint domainIngress = LOCAL.equals(currentDomain) ? null : ingress; // loop until (hopefully) all links have been checked once // this is necessary because a set is not sorted by default for (int i = 0; i < intent.links().size(); i++) { // find the next link List<Link> nextLinks = getEgressLinks(intent.links(), currentDevice); // no matching link exists if (nextLinks.isEmpty()) { throw new IntentCompilationException( "No matching link starting at " + ingress .connectPoint().deviceId()); } // get the first link Link nextLink = nextLinks.get(0); ingress = new FilteredConnectPoint(nextLink.src()); egress = new FilteredConnectPoint(nextLink.dst()); // query the domain for the domain of the link's destination DomainId dstDomain = domainService .getDomain(egress.connectPoint().deviceId()); if (!currentDomain.equals(dstDomain)) { // we are leaving the current domain or LOCAL log.debug("Domain transition from {} to {}.", currentDomain, dstDomain); if (!LOCAL.equals(currentDomain)) { // add the domain intent to the intent list intentList.add(createDomainP2PIntent(intent, domainIngress, ingress, domainLinks.build())); // TODO: might end up with an unused builder // reset domain links builder domainLinks = ImmutableList.builder(); } // update current domain (might be LOCAL) currentDomain = dstDomain; // update the domain's ingress domainIngress = LOCAL.equals(currentDomain) ? null : egress; } else { if (!LOCAL.equals(currentDomain)) { // we are staying in the same domain, store the traversed link domainLinks.add(nextLink); log.debug("{} belongs to the same domain.", egress.connectPoint().deviceId()); } } currentDevice = egress.connectPoint().deviceId(); } // get the egress point egress = intent.filteredEgressPoints().iterator().next(); // still inside a domain? if (!LOCAL.equals(currentDomain) && currentDomain.equals(domainService.getDomain( egress.connectPoint().deviceId()))) { // add intent intentList.add(createDomainP2PIntent(intent, domainIngress, egress, domainLinks.build())); } return intentList.build(); } /** * Create a domain point to point intent from the parameters. * * @param originalIntent the original intent to extract the app ID and key * @param ingress the ingress connection point * @param egress the egress connection point * @param domainLinks the list of traversed links * @return the domain point to point intent */ private static DomainPointToPointIntent createDomainP2PIntent( Intent originalIntent, FilteredConnectPoint ingress, FilteredConnectPoint egress, List<Link> domainLinks) { return DomainPointToPointIntent.builder() .appId(originalIntent.appId()) .filteredIngressPoint(ingress) .filteredEgressPoint(egress) .key(originalIntent.key()) .links(domainLinks) .build(); } /** * Get links originating from the source device ID. * * @param links list of available links * @param source the device ID of the source device * @return the list of links with the given source */ private List<Link> getEgressLinks(Set<Link> links, final DeviceId source) { return links.stream() .filter(link -> link.src().deviceId().equals(source)) .collect(Collectors.toList()); } /** * Get FilteredConnectPoint from LinkCollectionIntent. * * @param deviceId device Id for connect point * @param portNumber port number * @param intent source intent * @return filtered connetion point */ private Optional<FilteredConnectPoint> getFilteredConnectPointFromIntent(DeviceId deviceId, PortNumber portNumber, LinkCollectionIntent intent) { Set<FilteredConnectPoint> filteredConnectPoints = Sets.union(intent.filteredIngressPoints(), intent.filteredEgressPoints()); return filteredConnectPoints.stream() .filter(port -> port.connectPoint().deviceId().equals(deviceId)) .filter(port -> port.connectPoint().port().equals(portNumber)) .findFirst(); } /** * Get tag criterion from selector. * The criterion should be one of type in tagCriterionTypes. * * @param selector selector * @return Criterion that matched, if there is no tag criterion, return null */ private Criterion getTagCriterion(TrafficSelector selector) { return selector.criteria().stream() .filter(criterion -> TAG_CRITERION_TYPES.contains(criterion.type())) .findFirst() .orElse(Criteria.dummy()); } /** * Compares tag type between ingress and egress point and generate * treatment for egress point of intent. * * @param ingress ingress selector for the intent * @param egress egress selector for the intent * @param ethType the ethertype to use in mpls_pop * @return Builder of TrafficTreatment */ private TrafficTreatment forwardingTreatment(TrafficSelector ingress, TrafficSelector egress, EthType ethType) { if (ingress.equals(egress)) { return DefaultTrafficTreatment.emptyTreatment(); } TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder(); /* * "null" means there is no tag for the port * Tag criterion will be null if port is normal connection point */ Criterion ingressTagCriterion = getTagCriterion(ingress); Criterion egressTagCriterion = getTagCriterion(egress); if (ingressTagCriterion.type() != egressTagCriterion.type()) { /* * Tag type of ingress port and egress port are different. * Need to remove tag from ingress, then add new tag for egress. * Remove nothing if ingress port use VXLAN or there is no tag * on ingress port. */ switch (ingressTagCriterion.type()) { case VLAN_VID: builder.popVlan(); break; case MPLS_LABEL: if (copyTtl) { builder.copyTtlIn(); } builder.popMpls(ethType); break; default: break; } /* * Push new tag for egress port. */ switch (egressTagCriterion.type()) { case VLAN_VID: builder.pushVlan(); break; case MPLS_LABEL: builder.pushMpls(); if (copyTtl) { builder.copyTtlOut(); } break; default: break; } } switch (egressTagCriterion.type()) { case VLAN_VID: VlanIdCriterion vlanIdCriterion = (VlanIdCriterion) egressTagCriterion; builder.setVlanId(vlanIdCriterion.vlanId()); break; case MPLS_LABEL: MplsCriterion mplsCriterion = (MplsCriterion) egressTagCriterion; builder.setMpls(mplsCriterion.label()); break; case TUNNEL_ID: TunnelIdCriterion tunnelIdCriterion = (TunnelIdCriterion) egressTagCriterion; builder.setTunnelId(tunnelIdCriterion.tunnelId()); break; default: break; } return builder.build(); } /** * Update the selector builder using a L0 instruction. * * @param builder the builder to update * @param l0instruction the l0 instruction to use */ private void updateBuilder(TrafficSelector.Builder builder, L0ModificationInstruction l0instruction) { throw new IntentCompilationException(UNSUPPORTED_L0); } /** * Update the selector builder using a L1 instruction. * * @param builder the builder to update * @param l1instruction the l1 instruction to use */ private void updateBuilder(TrafficSelector.Builder builder, L1ModificationInstruction l1instruction) { throw new IntentCompilationException(UNSUPPORTED_L1); } /** * Update the selector builder using a L2 instruction. * * @param builder the builder to update * @param l2instruction the l2 instruction to use */ private void updateBuilder(TrafficSelector.Builder builder, L2ModificationInstruction l2instruction) { switch (l2instruction.subtype()) { case ETH_SRC: case ETH_DST: ModEtherInstruction ethInstr = (ModEtherInstruction) l2instruction; switch (ethInstr.subtype()) { case ETH_SRC: builder.matchEthSrc(ethInstr.mac()); break; case ETH_DST: builder.matchEthDst(ethInstr.mac()); break; default: throw new IntentCompilationException(UNSUPPORTED_ETH_SUBTYPE); } break; case VLAN_ID: ModVlanIdInstruction vlanIdInstr = (ModVlanIdInstruction) l2instruction; builder.matchVlanId(vlanIdInstr.vlanId()); break; case VLAN_PUSH: //FIXME break; case VLAN_POP: //TODO how do we handle dropped label? remove the selector? throw new IntentCompilationException(UNSUPPORTED_POP_ACTION); case VLAN_PCP: ModVlanPcpInstruction vlanPcpInstruction = (ModVlanPcpInstruction) l2instruction; builder.matchVlanPcp(vlanPcpInstruction.vlanPcp()); break; case MPLS_LABEL: case MPLS_PUSH: //FIXME ModMplsLabelInstruction mplsInstr = (ModMplsLabelInstruction) l2instruction; builder.matchMplsLabel(mplsInstr.label()); break; case MPLS_POP: //TODO how do we handle dropped label? remove the selector? throw new IntentCompilationException(UNSUPPORTED_POP_ACTION); case DEC_MPLS_TTL: // no-op break; case MPLS_BOS: ModMplsBosInstruction mplsBosInstr = (ModMplsBosInstruction) l2instruction; builder.matchMplsBos(mplsBosInstr.mplsBos()); break; case TUNNEL_ID: ModTunnelIdInstruction tunInstr = (ModTunnelIdInstruction) l2instruction; builder.matchTunnelId(tunInstr.tunnelId()); break; default: throw new IntentCompilationException(UNSUPPORTED_L2); } } /** * Update the selector builder using a L3 instruction. * * @param builder the builder to update * @param l3instruction the l3 instruction to use */ private void updateBuilder(TrafficSelector.Builder builder, L3ModificationInstruction l3instruction) { // TODO check ethernet proto switch (l3instruction.subtype()) { case IPV4_SRC: case IPV4_DST: case IPV6_SRC: case IPV6_DST: ModIPInstruction ipInstr = (ModIPInstruction) l3instruction; // TODO check if ip falls in original prefix IpPrefix prefix = ipInstr.ip().toIpPrefix(); switch (ipInstr.subtype()) { case IPV4_SRC: builder.matchIPSrc(prefix); break; case IPV4_DST: builder.matchIPSrc(prefix); break; case IPV6_SRC: builder.matchIPv6Src(prefix); break; case IPV6_DST: builder.matchIPv6Dst(prefix); break; default: throw new IntentCompilationException(UNSUPPORTED_IP_SUBTYPE); } break; case IPV6_FLABEL: ModIPv6FlowLabelInstruction ipFlowInstr = (ModIPv6FlowLabelInstruction) l3instruction; builder.matchIPv6FlowLabel(ipFlowInstr.flowLabel()); break; case DEC_TTL: // no-op break; case TTL_OUT: // no-op break; case TTL_IN: // no-op break; case ARP_SPA: ModArpIPInstruction arpIpInstr = (ModArpIPInstruction) l3instruction; if (arpIpInstr.ip().isIp4()) { builder.matchArpSpa((Ip4Address) arpIpInstr.ip()); } else { throw new IntentCompilationException(UNSUPPORTED_ARP); } break; case ARP_SHA: ModArpEthInstruction arpEthInstr = (ModArpEthInstruction) l3instruction; builder.matchArpSha(arpEthInstr.mac()); break; case ARP_OP: ModArpOpInstruction arpOpInstr = (ModArpOpInstruction) l3instruction; //FIXME is the long to int cast safe? builder.matchArpOp((int) arpOpInstr.op()); break; default: throw new IntentCompilationException(UNSUPPORTED_L3); } } /** * Update the selector builder using a L4 instruction. * * @param builder the builder to update * @param l4instruction the l4 instruction to use */ private void updateBuilder(TrafficSelector.Builder builder, L4ModificationInstruction l4instruction) { if (l4instruction instanceof ModTransportPortInstruction) { // TODO check IP proto ModTransportPortInstruction l4mod = (ModTransportPortInstruction) l4instruction; switch (l4mod.subtype()) { case TCP_SRC: builder.matchTcpSrc(l4mod.port()); break; case TCP_DST: builder.matchTcpDst(l4mod.port()); break; case UDP_SRC: builder.matchUdpSrc(l4mod.port()); break; case UDP_DST: builder.matchUdpDst(l4mod.port()); break; default: throw new IntentCompilationException(UNSUPPORTED_L4_SUBTYPE); } } else { throw new IntentCompilationException(UNSUPPORTED_L4); } } /** * Update selector builder by using treatment. * * @param builder builder to update * @param treatment traffic treatment */ private void updateBuilder(TrafficSelector.Builder builder, TrafficTreatment treatment) { treatment.allInstructions().forEach(instruction -> { switch (instruction.type()) { case L0MODIFICATION: updateBuilder(builder, (L0ModificationInstruction) instruction); break; case L1MODIFICATION: updateBuilder(builder, (L1ModificationInstruction) instruction); break; case L2MODIFICATION: updateBuilder(builder, (L2ModificationInstruction) instruction); break; case L3MODIFICATION: updateBuilder(builder, (L3ModificationInstruction) instruction); break; case L4MODIFICATION: updateBuilder(builder, (L4ModificationInstruction) instruction); break; case NOACTION: case OUTPUT: case GROUP: case QUEUE: case TABLE: case METER: case METADATA: case EXTENSION: // TODO is extension no-op or unsupported? // Nothing to do break; default: throw new IntentCompilationException(UNSUPPORTED_INSTRUCTION); } }); } /** * The method generates a selector starting from * the encapsulation information (type and label to match). * * @param selectorBuilder the builder to update * @param type the type of encapsulation * @param identifier the label to match */ private void updateSelectorFromEncapsulation(TrafficSelector.Builder selectorBuilder, EncapsulationType type, Identifier<?> identifier) { switch (type) { case MPLS: MplsLabel label = (MplsLabel) identifier; selectorBuilder.matchMplsLabel(label); selectorBuilder.matchEthType(Ethernet.MPLS_UNICAST); break; case VLAN: VlanId id = (VlanId) identifier; selectorBuilder.matchVlanId(id); break; default: throw new IntentCompilationException(UNKNOWN_ENCAPSULATION); } } /** * Helper function to define the match on the ethertype. * If the selector define an ethertype we will use it, * otherwise IPv4 will be used by default. * * @param selector the traffic selector * @return the ethertype we should match */ private EthType getEthType(TrafficSelector selector) { Criterion c = selector.getCriterion(Criterion.Type.ETH_TYPE); if (c != null && c instanceof EthTypeCriterion) { EthTypeCriterion ethertype = (EthTypeCriterion) c; return ethertype.ethType(); } return EthType.EtherType.IPV4.ethType(); } }