blob: e65d2564e78cff17a16fc3675195d878c0308e27 [file] [log] [blame]
/*
* 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();
}
}