Fixes [ONOS-5415] and [ONOS-5491]
Changes:
- Introduces ordering between core ports and egress ports;
- Introduces ordering between egress ports;
- Introduces ttl_in and tll_out in the link collection compiler logic;
- Optimized push/pop mpls/vlan actions;
- Optimizes dec ttl related actions;
- Optimizes ttl_in and ttl_out actions;
- Makes the optimizations configurable;
Change-Id: I60f187031e89c366ec9c79d1652a5fb0da96c206
diff --git a/core/api/src/test/java/org/onosproject/net/NetTestTools.java b/core/api/src/test/java/org/onosproject/net/NetTestTools.java
index 842c86d..d6227a7 100644
--- a/core/api/src/test/java/org/onosproject/net/NetTestTools.java
+++ b/core/api/src/test/java/org/onosproject/net/NetTestTools.java
@@ -258,4 +258,17 @@
);
}
+ /**
+ * Builds a treatment which contains the dec ttl
+ * actions.
+ *
+ * @return the treatment
+ */
+ public static TrafficTreatment decTtlTreatment() {
+ return DefaultTrafficTreatment.builder()
+ .decMplsTtl()
+ .decNwTtl()
+ .build();
+ }
+
}
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/IntentConfigurableRegistrator.java b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/IntentConfigurableRegistrator.java
index 38be5e4..40f7f77 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/IntentConfigurableRegistrator.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/IntentConfigurableRegistrator.java
@@ -67,6 +67,18 @@
label = "Defines the label selection algorithm - RANDOM or FIRST_FIT")
private String labelSelection = DEFAULT_LABEL_SELECTION;
+ private static final boolean DEFAULT_FLOW_OPTIMIZATION = false;
+ @Property(name = "useFlowOptimization",
+ boolValue = DEFAULT_FLOW_OPTIMIZATION,
+ label = "Indicates whether or not to optimize the flows in the link collection compiler")
+ private boolean useFlowOptimization = DEFAULT_FLOW_OPTIMIZATION;
+
+ private static final boolean DEFAULT_COPY_TTL = false;
+ @Property(name = "useCopyTtl",
+ boolValue = DEFAULT_COPY_TTL,
+ label = "Indicates whether or not to use copy ttl in the link collection compiler")
+ private boolean useCopyTtl = DEFAULT_COPY_TTL;
+
private final Map<Class<Intent>, IntentCompiler<Intent>> flowRuleBased = Maps.newConcurrentMap();
private final Map<Class<Intent>, IntentCompiler<Intent>> flowObjectiveBased = Maps.newConcurrentMap();
@@ -87,6 +99,8 @@
if (context == null) {
log.info("Settings: useFlowObjectives={}", useFlowObjectives);
log.info("Settings: labelSelection={}", labelSelection);
+ log.info("Settings: useFlowOptimization={}", useFlowOptimization);
+ log.info("Settings: useCopyTtl={}", useCopyTtl);
return;
}
@@ -117,6 +131,34 @@
changeLabelSelections();
log.info("Settings: labelSelection={}", labelSelection);
}
+
+ boolean newFlowOptimization;
+ try {
+ String s = Tools.get(context.getProperties(), "useFlowOptimization");
+ newFlowOptimization = isNullOrEmpty(s) ? useFlowOptimization : Boolean.parseBoolean(s.trim());
+ } catch (ClassCastException e) {
+ newFlowOptimization = useFlowOptimization;
+ }
+
+ if (useFlowOptimization != newFlowOptimization) {
+ useFlowOptimization = newFlowOptimization;
+ changeFlowOptimization();
+ log.info("Settings: useFlowOptimization={}", useFlowOptimization);
+ }
+
+ boolean newCopyTtl;
+ try {
+ String s = Tools.get(context.getProperties(), "useCopyTtl");
+ newCopyTtl = isNullOrEmpty(s) ? useCopyTtl : Boolean.parseBoolean(s.trim());
+ } catch (ClassCastException e) {
+ newCopyTtl = useCopyTtl;
+ }
+
+ if (useCopyTtl != newCopyTtl) {
+ useCopyTtl = newCopyTtl;
+ changeCopyTtl();
+ log.info("Settings: useCopyTtl={}", useCopyTtl);
+ }
}
/**
@@ -173,4 +215,12 @@
LinkCollectionCompiler.labelAllocator.setLabelSelection(labelSelection);
}
+ private void changeFlowOptimization() {
+ LinkCollectionCompiler.optimize = useFlowOptimization;
+ }
+
+ private void changeCopyTtl() {
+ LinkCollectionCompiler.copyTtl = useCopyTtl;
+ }
+
}
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionCompiler.java b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionCompiler.java
index d5df116..2176a20 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionCompiler.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionCompiler.java
@@ -16,6 +16,7 @@
package org.onosproject.net.intent.impl.compiler;
+import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
@@ -29,9 +30,9 @@
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.FilteredConnectPoint;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.TrafficSelector;
@@ -47,17 +48,17 @@
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.L2ModificationInstruction.ModMplsLabelInstruction;
-import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsBosInstruction;
-import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModTunnelIdInstruction;
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.L3ModificationInstruction.ModArpIPInstruction;
-import org.onosproject.net.flow.instructions.L3ModificationInstruction.ModArpEthInstruction;
-import org.onosproject.net.flow.instructions.L3ModificationInstruction.ModArpOpInstruction;
import org.onosproject.net.flow.instructions.L4ModificationInstruction;
import org.onosproject.net.flow.instructions.L4ModificationInstruction.ModTransportPortInstruction;
import org.onosproject.net.intent.IntentCompilationException;
@@ -69,10 +70,9 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.stream.Collectors;
-import static org.onosproject.net.flow.criteria.Criterion.Type.MPLS_LABEL;
-import static org.onosproject.net.flow.criteria.Criterion.Type.TUNNEL_ID;
-import static org.onosproject.net.flow.criteria.Criterion.Type.VLAN_VID;
+import static org.onosproject.net.flow.criteria.Criterion.Type.*;
/**
* Shared APIs and implementations for Link Collection compilers.
@@ -85,6 +85,18 @@
static LabelAllocator labelAllocator;
/**
+ * Influence compiler behavior. If true the compiler
+ * try to optimize the chain of the actions.
+ */
+ static boolean optimize;
+
+ /**
+ * 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 =
@@ -248,6 +260,151 @@
}
/**
+ * 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 (optimize) {
+ /*
+ * 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.
*
@@ -285,42 +442,32 @@
.forEach(selectorBuilder::add);
/*
* In this scenario, potentially we can have several output
- * ports.
+ * 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 we are at the egress, we have to transit to the final
- * state.
- */
- if (filteredEgressPoint.isPresent()) {
- /*
- * 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(filteredIngressPoint.get().trafficSelector(),
- filteredEgressPoint.get().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);
+ if (!filteredEgressPoint.isPresent()) {
+ treatmentBuilder.setOutput(outPort);
+ } else {
+ egressPoints.add(filteredEgressPoint.get());
}
- /*
- * Finally we set the output action.
- */
- treatmentBuilder.setOutput(outPort);
}
+ /*
+ * 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 (optimize) {
+ egressPoints = orderedEgressPoints(prevState, egressPoints);
+ }
+ /*
+ * Then we deal with the egress points.
+ */
+ generateEgressActions(treatmentBuilder, egressPoints, prevState, intent);
}
/**
@@ -605,9 +752,11 @@
);
/*
* We need to order the actions. First the actions
- * related to the not-egress points.
+ * related to the not-egress points. At the same time we collect
+ * also the egress points.
*/
- outPorts.forEach(outPort -> {
+ List<FilteredConnectPoint> egressPoints = Lists.newArrayList();
+ for (PortNumber outPort : outPorts) {
Optional<FilteredConnectPoint> filteredEgressPoint =
getFilteredConnectPointFromIntent(deviceId, outPort, intent);
if (!filteredEgressPoint.isPresent()) {
@@ -651,45 +800,25 @@
* Finally we set the output action.
*/
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
+ * selector derived from the encpsulation constraint then
+ * the others.
+ */
+ TrafficSelector prevState = selectorBuilder.build();
+ if (optimize) {
+ egressPoints = orderedEgressPoints(prevState, egressPoints);
+ }
/*
* In this case, we have to transit to the final
* state.
*/
- outPorts.forEach(outPort -> {
- Optional<FilteredConnectPoint> filteredEgressPoint =
- getFilteredConnectPointFromIntent(deviceId, outPort, intent);
- if (filteredEgressPoint.isPresent()) {
- /*
- * We add the Intent treatment to the final
- * treatment.
- */
- intent.treatment().allInstructions().stream()
- .filter(inst -> inst.type() != Instruction.Type.NOACTION)
- .forEach(treatmentBuilder::add);
- /*
- * We generate the transition FIP->FEP.
- */
- TrafficTreatment forwardingTreatment =
- forwardingTreatment(selectorBuilder.build(),
- filteredEgressPoint.get().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(outPort);
- }
- });
-
+ generateEgressActions(treatmentBuilder, egressPoints, prevState, intent);
}
/**
@@ -891,6 +1020,9 @@
break;
case MPLS_LABEL:
+ if (copyTtl) {
+ builder.copyTtlIn();
+ }
builder.popMpls(ethType);
break;
@@ -909,6 +1041,9 @@
case MPLS_LABEL:
builder.pushMpls();
+ if (copyTtl) {
+ builder.copyTtlOut();
+ }
break;
default:
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompiler.java b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompiler.java
index e464e99..d93764b 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompiler.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompiler.java
@@ -30,9 +30,16 @@
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.flow.instructions.L2ModificationInstruction;
+import org.onosproject.net.flow.instructions.L3ModificationInstruction;
import org.onosproject.net.intent.FlowRuleIntent;
import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentCompilationException;
import org.onosproject.net.intent.IntentCompiler;
import org.onosproject.net.intent.LinkCollectionIntent;
import org.onosproject.net.intent.constraint.EncapsulationConstraint;
@@ -48,6 +55,8 @@
import java.util.Optional;
import java.util.Set;
+import static org.onosproject.net.flow.instructions.Instruction.Type.NOACTION;
+
/**
* Compiler to produce flow rules from link collections.
*/
@@ -56,6 +65,9 @@
extends LinkCollectionCompiler<FlowRule>
implements IntentCompiler<LinkCollectionIntent> {
+ private static final String UNKNOWN_INSTRUCTION = "Unknown instruction type";
+ private static final String UNSUPPORTED_INSTRUCTION = "Unsupported %s instruction";
+
private static Logger log = LoggerFactory.getLogger(LinkCollectionIntentCompiler.class);
@@ -138,6 +150,11 @@
labels
);
+ if (optimize) {
+ TrafficTreatment compactedTreatment = compactActions(instructions.treatment());
+ instructions = new ForwardingInstructions(compactedTreatment, instructions.selector());
+ }
+
FlowRule rule = DefaultFlowRule.builder()
.forDevice(deviceId)
.withSelector(instructions.selector())
@@ -152,4 +169,204 @@
return rules;
}
+
+ /**
+ * This method tries to optimize the chain of actions.
+ *
+ * @param oldTreatment the list of instructions to optimize
+ * @return the optimized set of actions
+ */
+ private TrafficTreatment compactActions(TrafficTreatment oldTreatment) {
+
+ TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
+ Instruction instruction;
+ Instruction newInstruction;
+
+ for (int index = 0; index < oldTreatment.allInstructions().size(); index++) {
+ instruction = oldTreatment.allInstructions().get(index);
+ /*
+ * if the action is not optimizable. We simply add
+ * to the builder.
+ */
+ if (checkInstruction(instruction)) {
+ treatmentBuilder.add(instruction);
+ continue;
+ }
+ /*
+ * We try to run an optimization;
+ */
+ newInstruction = optimizeInstruction(index, instruction, oldTreatment.allInstructions());
+ if (!newInstruction.type().equals(NOACTION)) {
+ treatmentBuilder.add(newInstruction);
+ }
+ }
+
+ return treatmentBuilder.build();
+ }
+
+ /**
+ * Verifies if the given L2 instruction can be optimized.
+ *
+ * @param l2instruction the l2 instruction to verify
+ * @return true if the instruction cannot be optimized. False otherwise
+ */
+ private boolean checkL2Instructions(L2ModificationInstruction l2instruction) {
+ switch (l2instruction.subtype()) {
+ /*
+ * These actions can be performed safely.
+ */
+ case ETH_SRC:
+ case ETH_DST:
+ case VLAN_ID:
+ case VLAN_PCP:
+ case MPLS_LABEL:
+ case MPLS_BOS:
+ case TUNNEL_ID:
+ case VLAN_PUSH:
+ case VLAN_POP:
+ case MPLS_PUSH:
+ case MPLS_POP:
+ return true;
+ /*
+ * We should avoid dec mpls ttl multiple
+ * times.
+ */
+ case DEC_MPLS_TTL:
+ return false;
+
+ default:
+ throw new IntentCompilationException(String.format(UNSUPPORTED_INSTRUCTION, "L2"));
+ }
+
+ }
+
+ /**
+ * Verifies if the given L3 instruction can be optimized.
+ *
+ * @param l3instruction the l3 instruction to verify
+ * @return true if the instruction cannot be optimized. False otherwise
+ */
+ private boolean checkL3Instructions(L3ModificationInstruction l3instruction) {
+ switch (l3instruction.subtype()) {
+ /*
+ * These actions can be performed several times.
+ */
+ case IPV4_SRC:
+ case IPV4_DST:
+ case IPV6_SRC:
+ case IPV6_DST:
+ case IPV6_FLABEL:
+ case ARP_SPA:
+ case ARP_SHA:
+ case ARP_OP:
+ case TTL_OUT:
+ case TTL_IN:
+ return true;
+ /*
+ * This action should be executed one time;
+ */
+ case DEC_TTL:
+ return false;
+ default:
+ throw new IntentCompilationException(String.format(UNSUPPORTED_INSTRUCTION, "L3"));
+ }
+ }
+
+ /**
+ * Helper method to handle the optimization of the ttl instructions.
+ *
+ * @param index the index of the instruction
+ * @param instruction the instruction to optimize
+ * @param instructions the list of instructions to optimize
+ * @return no action if the action can be removed. The same instruction
+ * if we have to perform it
+ */
+ private Instruction optimizeTtlInstructions(int index, Instruction instruction, List<Instruction> instructions) {
+ /**
+ * Here we handle the optimization of decrement mpls ttl. The optimization
+ * is to come back to the start of the list looking for the same
+ * action. If we find the same action, we can optimize.
+ */
+ Instruction currentInstruction;
+ for (int i = index - 1; i >= 0; i--) {
+ currentInstruction = instructions.get(i);
+ if (currentInstruction.equals(instruction)) {
+ return Instructions.createNoAction();
+
+ }
+ }
+ return instruction;
+ }
+
+ /**
+ * Helper method to handle the optimization of the instructions.
+ *
+ * @param index the index of the instruction
+ * @param instruction the instruction to optimize
+ * @param instructions the list of instructions to optimize
+ * @return no action if the action can be removed. The same instruction
+ * if we have to perform it
+ */
+ private Instruction optimizeInstruction(int index, Instruction instruction, List<Instruction> instructions) {
+
+ switch (instruction.type()) {
+ /*
+ * Here we have the chance to optimize the dec mpls ttl action.
+ */
+ case L2MODIFICATION:
+ /*
+ * Here we have the chance to optimize the ttl related actions.
+ */
+ case L3MODIFICATION:
+ return optimizeTtlInstructions(index, instruction, instructions);
+
+ default:
+ throw new IntentCompilationException(UNKNOWN_INSTRUCTION);
+
+ }
+
+ }
+
+ /**
+ * Helper method to verify if the instruction can be optimized.
+ *
+ * @param instruction the instruction to verify
+ * @return true if the action can be optimized. False otherwise.
+ */
+ private boolean checkInstruction(Instruction instruction) {
+
+ switch (instruction.type()) {
+ /*
+ * The following instructions are not supported.
+ */
+ case L0MODIFICATION:
+ case L1MODIFICATION:
+ case L4MODIFICATION:
+ case NOACTION:
+ case OUTPUT:
+ case GROUP:
+ case QUEUE:
+ case TABLE:
+ case METER:
+ case METADATA:
+ case EXTENSION:
+ return true;
+ /*
+ * Here we have the chance to optimize actions like dec mpls ttl.
+ */
+ case L2MODIFICATION:
+ return checkL2Instructions((L2ModificationInstruction) instruction);
+ /*
+ * Here we have the chance to optimize the ttl related actions.
+ */
+ case L3MODIFICATION:
+ return checkL3Instructions((L3ModificationInstruction) instruction);
+
+ default:
+ throw new IntentCompilationException(UNKNOWN_INSTRUCTION);
+
+ }
+
+ }
+
}
diff --git a/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/AbstractLinkCollectionTest.java b/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/AbstractLinkCollectionTest.java
index 9a8324c..f7389e7 100644
--- a/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/AbstractLinkCollectionTest.java
+++ b/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/AbstractLinkCollectionTest.java
@@ -53,7 +53,7 @@
final ConnectPoint d2p0 = connectPoint("s2", 0);
final ConnectPoint d2p1 = connectPoint("s2", 1);
- ConnectPoint d2p10 = connectPoint("s2", 10);
+ final ConnectPoint d2p10 = connectPoint("s2", 10);
final ConnectPoint d3p0 = connectPoint("s3", 0);
final ConnectPoint d3p1 = connectPoint("s3", 1);
@@ -78,6 +78,7 @@
final ConnectPoint d1p1 = connectPoint("s1", 1);
final ConnectPoint d1p10 = connectPoint("s1", 10);
final ConnectPoint d1p11 = connectPoint("s1", 11);
+ final ConnectPoint d1p12 = connectPoint("s1", 12);
final Set<Link> links = ImmutableSet.of(
link(d1p1, d2p0),
@@ -98,6 +99,8 @@
);
final TrafficTreatment treatment = emptyTreatment();
+ final TrafficTreatment ethDstTreatment = macDstTreatment("C0:FF:EE:C0:FF:EE");
+ final TrafficTreatment decTllTreatment = decTtlTreatment();
final TrafficSelector selector = emptySelector();
final TrafficSelector vlan69Selector = vlanSelector("69");
@@ -109,7 +112,6 @@
final TrafficSelector mpls100Selector = mplsSelector("100");
final TrafficSelector mpls200Selector = mplsSelector("200");
final TrafficSelector ipPrefixSelector = ipPrefixDstSelector("192.168.100.0/24");
- final TrafficTreatment ethDstTreatment = macDstTreatment("C0:FF:EE:C0:FF:EE");
final List<Constraint> constraintsForVlan = vlanConstraint();
final List<Constraint> constraintsForMPLS = mplsConstraint();
diff --git a/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/LinkCollectionEncapIntentCompilerTest.java b/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/LinkCollectionEncapIntentCompilerTest.java
index 588880a..295a998 100644
--- a/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/LinkCollectionEncapIntentCompilerTest.java
+++ b/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/LinkCollectionEncapIntentCompilerTest.java
@@ -82,6 +82,9 @@
sut.registrator = registrator;
sut.resourceService = new MockResourceService();
+ LinkCollectionCompiler.optimize = false;
+ LinkCollectionCompiler.copyTtl = false;
+
replay(coreService, intentExtensionService);
}
@@ -290,7 +293,7 @@
DefaultTrafficTreatment
.builder()
.pushMpls()
- .setMpls(MplsLabel.mplsLabel((LABEL)))
+ .setMpls(MplsLabel.mplsLabel(LABEL))
.setOutput(d3p0.port())
.build()
));
diff --git a/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompilerTest.java b/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompilerTest.java
index 87103aa..cfbddea 100644
--- a/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompilerTest.java
+++ b/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompilerTest.java
@@ -96,8 +96,10 @@
sut.registrator = registrator;
- replay(coreService, intentExtensionService);
+ LinkCollectionCompiler.optimize = false;
+ LinkCollectionCompiler.copyTtl = false;
+ replay(coreService, intentExtensionService);
}
diff --git a/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/LinkCollectionOptimizationTest.java b/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/LinkCollectionOptimizationTest.java
new file mode 100644
index 0000000..4b58fba
--- /dev/null
+++ b/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/LinkCollectionOptimizationTest.java
@@ -0,0 +1,1060 @@
+/*
+ * 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.ImmutableSet;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.MplsLabel;
+import org.onlab.packet.VlanId;
+import org.onosproject.cfg.ComponentConfigAdapter;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.FilteredConnectPoint;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.criteria.MplsCriterion;
+import org.onosproject.net.flow.criteria.VlanIdCriterion;
+import org.onosproject.net.intent.FlowRuleIntent;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentExtensionService;
+import org.onosproject.net.intent.LinkCollectionIntent;
+import org.onosproject.net.resource.MockResourceService;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.easymock.EasyMock.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.core.Is.is;
+import static org.onlab.packet.EthType.EtherType.IPV4;
+import static org.onosproject.net.NetTestTools.*;
+import static org.onosproject.net.flow.criteria.Criterion.Type.*;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction;
+
+/**
+ * This set of tests are meant to test the encapsulation
+ * in the LinkCollectionIntent.
+ */
+public class LinkCollectionOptimizationTest extends AbstractLinkCollectionTest {
+
+ @Before
+ public void setUp() {
+ sut = new LinkCollectionIntentCompiler();
+ coreService = createMock(CoreService.class);
+ expect(coreService.registerApplication("org.onosproject.net.intent"))
+ .andReturn(appId);
+ sut.coreService = coreService;
+
+ Intent.bindIdGenerator(idGenerator);
+
+ intentExtensionService = createMock(IntentExtensionService.class);
+ intentExtensionService.registerCompiler(LinkCollectionIntent.class, sut);
+ intentExtensionService.unregisterCompiler(LinkCollectionIntent.class);
+
+ registrator = new IntentConfigurableRegistrator();
+ registrator.extensionService = intentExtensionService;
+ registrator.cfgService = new ComponentConfigAdapter();
+ registrator.activate();
+
+ sut.registrator = registrator;
+ sut.resourceService = new MockResourceService();
+
+ /*
+ * We activate the optimizations.
+ */
+ LinkCollectionCompiler.optimize = true;
+ LinkCollectionCompiler.copyTtl = true;
+
+ replay(coreService, intentExtensionService);
+ }
+
+ @After
+ public void tearDown() {
+ Intent.unbindIdGenerator(idGenerator);
+ }
+
+ /**
+ * We test the proper optimization of sp2mp with dec tll
+ * and dec mpls ttl.
+ */
+ @Test
+ public void testDecTtlOptimization() {
+
+ intent = LinkCollectionIntent.builder()
+ .appId(APP_ID)
+ .selector(selector)
+ .treatment(decTllTreatment)
+ .links(linksForSp2Mp)
+ .filteredIngressPoints(ImmutableSet.of(new FilteredConnectPoint(d3p10)))
+ .filteredEgressPoints(ImmutableSet.of(
+ new FilteredConnectPoint(d1p10),
+ new FilteredConnectPoint(d1p11),
+ new FilteredConnectPoint(d2p10)
+ ))
+ .applyTreatmentOnEgress(true)
+ .build();
+
+ sut.activate();
+
+ List<Intent> compiled = sut.compile(intent, Collections.emptyList());
+ assertThat(compiled, hasSize(1));
+
+ Collection<FlowRule> rules = ((FlowRuleIntent) compiled.get(0)).flowRules();
+ assertThat(rules, hasSize(3));
+
+ Collection<FlowRule> rulesS3 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d3p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS3, hasSize(1));
+ FlowRule ruleS3 = rulesS3.iterator().next();
+ assertThat(ruleS3.selector(), is(
+ DefaultTrafficSelector
+ .builder()
+ .matchInPort(d3p10.port())
+ .build()
+ ));
+ assertThat(ruleS3.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .setOutput(d3p0.port())
+ .build()
+ ));
+
+ Collection<FlowRule> rulesS2 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d2p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS2, hasSize(1));
+ FlowRule ruleS2 = rulesS2.iterator().next();
+ assertThat(ruleS2.selector(), is(
+ DefaultTrafficSelector
+ .builder()
+ .matchInPort(d2p1.port())
+ .build()
+ ));
+ assertThat(ruleS2.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .setOutput(d2p0.port())
+ .decMplsTtl()
+ .decNwTtl()
+ .setOutput(d2p10.port())
+ .build()
+ ));
+
+ Collection<FlowRule> rulesS1 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d1p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS1, hasSize(1));
+ FlowRule ruleS1 = rulesS1.iterator().next();
+ assertThat(ruleS1.selector(), is(
+ DefaultTrafficSelector
+ .builder()
+ .matchInPort(d1p0.port())
+ .build()
+ ));
+ assertThat(ruleS1.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .decMplsTtl()
+ .decNwTtl()
+ .setOutput(d1p10.port())
+ .setOutput(d1p11.port())
+ .build()
+ ));
+
+ sut.deactivate();
+
+ }
+
+ /**
+ * We test the proper optimization of sp2mp with VLAN
+ * ingress point and different egress points: 1) it is
+ * a simple ingress point; 2) it is a vlan ingress point;
+ * 3) It is a simple ingress point. 1) and 2) share the same
+ * egress switch. The outcomes of the test are the re-ordering
+ * of the actions and the proper optimization of the chain.
+ */
+ @Test
+ public void testVlanOrder() {
+
+ intent = LinkCollectionIntent.builder()
+ .appId(APP_ID)
+ .selector(selector)
+ .treatment(treatment)
+ .links(linksForSp2Mp)
+ .applyTreatmentOnEgress(true)
+ .filteredIngressPoints(ImmutableSet.of(new FilteredConnectPoint(d3p10, vlan100Selector)))
+ .filteredEgressPoints(ImmutableSet.of(
+ new FilteredConnectPoint(d1p10),
+ new FilteredConnectPoint(d1p11, vlan200Selector),
+ new FilteredConnectPoint(d2p10)
+ ))
+ .build();
+
+ sut.activate();
+
+ List<Intent> compiled = sut.compile(intent, Collections.emptyList());
+ assertThat(compiled, hasSize(1));
+
+ Collection<FlowRule> rules = ((FlowRuleIntent) compiled.get(0)).flowRules();
+ assertThat(rules, hasSize(3));
+
+ Collection<FlowRule> rulesS3 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d3p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS3, hasSize(1));
+ FlowRule ruleS3 = rulesS3.iterator().next();
+ assertThat(ruleS3.selector(), is(
+ DefaultTrafficSelector
+ .builder(vlan100Selector)
+ .matchInPort(d3p10.port())
+ .build()
+ ));
+ assertThat(ruleS3.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .setOutput(d3p0.port())
+ .build()
+ ));
+
+ Collection<FlowRule> rulesS2 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d2p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS2, hasSize(1));
+ FlowRule ruleS2 = rulesS2.iterator().next();
+ assertThat(ruleS2.selector(), is(
+ DefaultTrafficSelector
+ .builder(vlan100Selector)
+ .matchInPort(d2p1.port())
+ .build()
+ ));
+ assertThat(ruleS2.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .setOutput(d2p0.port())
+ .popVlan()
+ .setOutput(d2p10.port())
+ .build()
+ ));
+
+ Collection<FlowRule> rulesS1 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d1p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS1, hasSize(1));
+ FlowRule ruleS1 = rulesS1.iterator().next();
+ assertThat(ruleS1.selector(), is(
+ DefaultTrafficSelector
+ .builder(vlan100Selector)
+ .matchInPort(d1p0.port())
+ .build()
+ ));
+ assertThat(ruleS1.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .setVlanId(((VlanIdCriterion) vlan200Selector.getCriterion(VLAN_VID)).vlanId())
+ .setOutput(d1p11.port())
+ .popVlan()
+ .setOutput(d1p10.port())
+ .build()
+ ));
+
+ sut.deactivate();
+
+ }
+
+ /**
+ * We test the proper optimization of sp2mp with MPLS
+ * ingress point and different egress points: 1) it is
+ * a vlan ingress point; 2) it is a mpls ingress point;
+ * 3) It is a simple ingress point. 1) and 2) share the same
+ * egress switch. The outcomes of the test are the re-ordering
+ * of the actions and the proper optimization of the chain.
+ */
+ @Test
+ public void testMplsOrder() {
+
+ intent = LinkCollectionIntent.builder()
+ .appId(APP_ID)
+ .selector(selector)
+ .treatment(treatment)
+ .links(linksForSp2Mp)
+ .applyTreatmentOnEgress(true)
+ .filteredIngressPoints(ImmutableSet.of(new FilteredConnectPoint(d3p10, mpls100Selector)))
+ .filteredEgressPoints(ImmutableSet.of(
+ new FilteredConnectPoint(d1p10, vlan100Selector),
+ new FilteredConnectPoint(d1p11, mpls200Selector),
+ new FilteredConnectPoint(d2p10)
+ ))
+ .build();
+
+ sut.activate();
+
+ List<Intent> compiled = sut.compile(intent, Collections.emptyList());
+ assertThat(compiled, hasSize(1));
+
+ Collection<FlowRule> rules = ((FlowRuleIntent) compiled.get(0)).flowRules();
+ assertThat(rules, hasSize(3));
+
+ Collection<FlowRule> rulesS3 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d3p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS3, hasSize(1));
+ FlowRule ruleS3 = rulesS3.iterator().next();
+ assertThat(ruleS3.selector(), is(
+ DefaultTrafficSelector
+ .builder(mpls100Selector)
+ .matchInPort(d3p10.port())
+ .build()
+ ));
+ assertThat(ruleS3.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .setOutput(d3p0.port())
+ .build()
+ ));
+
+ Collection<FlowRule> rulesS2 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d2p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS2, hasSize(1));
+ FlowRule ruleS2 = rulesS2.iterator().next();
+ assertThat(ruleS2.selector(), is(
+ DefaultTrafficSelector
+ .builder(mpls100Selector)
+ .matchInPort(d2p1.port())
+ .build()
+ ));
+ assertThat(ruleS2.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .setOutput(d2p0.port())
+ .copyTtlIn()
+ .popMpls(IPV4.ethType())
+ .setOutput(d2p10.port())
+ .build()
+ ));
+
+ Collection<FlowRule> rulesS1 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d1p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS1, hasSize(1));
+ FlowRule ruleS1 = rulesS1.iterator().next();
+ assertThat(ruleS1.selector(), is(
+ DefaultTrafficSelector
+ .builder(mpls100Selector)
+ .matchInPort(d1p0.port())
+ .build()
+ ));
+ assertThat(ruleS1.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .setMpls(((MplsCriterion) mpls200Selector.getCriterion(MPLS_LABEL)).label())
+ .setOutput(d1p11.port())
+ .copyTtlIn()
+ .popMpls(IPV4.ethType())
+ .pushVlan()
+ .setVlanId(((VlanIdCriterion) vlan100Selector.getCriterion(VLAN_VID)).vlanId())
+ .setOutput(d1p10.port())
+ .build()
+ ));
+
+ sut.deactivate();
+
+ }
+
+ /**
+ * We test the proper optimization of sp2mp with untagged
+ * ingress point and different egress points: 1) it is
+ * a vlan ingress point; 2) it is a mpls ingress point;
+ * 3) It is a simple ingress point. 1) and 2) share the same
+ * egress switch. The outcomes of the test are the re-ordering
+ * of the actions and the proper optimization of the chain.
+ */
+ @Test
+ public void testUntaggedOrder() {
+
+ intent = LinkCollectionIntent.builder()
+ .appId(APP_ID)
+ .selector(selector)
+ .treatment(treatment)
+ .links(linksForSp2Mp)
+ .applyTreatmentOnEgress(true)
+ .filteredIngressPoints(ImmutableSet.of(new FilteredConnectPoint(d3p10)))
+ .filteredEgressPoints(ImmutableSet.of(
+ new FilteredConnectPoint(d1p10, vlan100Selector),
+ new FilteredConnectPoint(d1p11, mpls200Selector),
+ new FilteredConnectPoint(d1p12),
+ new FilteredConnectPoint(d2p10)
+ ))
+ .build();
+
+ sut.activate();
+
+ List<Intent> compiled = sut.compile(intent, Collections.emptyList());
+ assertThat(compiled, hasSize(1));
+
+ Collection<FlowRule> rules = ((FlowRuleIntent) compiled.get(0)).flowRules();
+ assertThat(rules, hasSize(3));
+
+ Collection<FlowRule> rulesS3 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d3p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS3, hasSize(1));
+ FlowRule ruleS3 = rulesS3.iterator().next();
+ assertThat(ruleS3.selector(), is(
+ DefaultTrafficSelector
+ .builder()
+ .matchInPort(d3p10.port())
+ .build()
+ ));
+ assertThat(ruleS3.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .setOutput(d3p0.port())
+ .build()
+ ));
+
+
+ Collection<FlowRule> rulesS2 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d2p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS2, hasSize(1));
+ FlowRule ruleS2 = rulesS2.iterator().next();
+ assertThat(ruleS2.selector(), is(
+ DefaultTrafficSelector
+ .builder()
+ .matchInPort(d2p1.port())
+ .build()
+ ));
+ assertThat(ruleS2.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .setOutput(d2p0.port())
+ .setOutput(d2p10.port())
+ .build()
+ ));
+
+
+ Collection<FlowRule> rulesS1 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d1p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS1, hasSize(1));
+ FlowRule ruleS1 = rulesS1.iterator().next();
+ assertThat(ruleS1.selector(), is(
+ DefaultTrafficSelector
+ .builder()
+ .matchInPort(d1p0.port())
+ .build()
+ ));
+ assertThat(ruleS1.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .setOutput(d1p12.port())
+ .pushVlan()
+ .setVlanId(((VlanIdCriterion) vlan100Selector.getCriterion(VLAN_VID)).vlanId())
+ .setOutput(d1p10.port())
+ .popVlan()
+ .pushMpls()
+ .copyTtlOut()
+ .setMpls(((MplsCriterion) mpls200Selector.getCriterion(MPLS_LABEL)).label())
+ .setOutput(d1p11.port())
+ .build()
+ ));
+
+ sut.deactivate();
+
+ }
+
+ /**
+ * We test the proper optimization of sp2mp with VLAN ingress point,
+ * simple egress point, non trivial intent treatment and non trivial
+ * intent selector. The outcomes of the test are the proper optimization
+ * of the actions. It is expected that the actions related to the intent
+ * treatment and intent selector are not optimized. The optimization is
+ * performed only on the dec ttl actions and on the actions which create
+ * traffic slices. The rationale is that in the intent treatment we expect
+ * actions like set something so it is fine to perform them several times.
+ */
+ @Test
+ public void testPoPVlanOptimization() {
+
+ intent = LinkCollectionIntent.builder()
+ .appId(APP_ID)
+ .selector(ipPrefixSelector)
+ .treatment(ethDstTreatment)
+ .links(linksForSp2Mp)
+ .applyTreatmentOnEgress(true)
+ .filteredIngressPoints(ImmutableSet.of(new FilteredConnectPoint(d3p10, vlan100Selector)))
+ .filteredEgressPoints(ImmutableSet.of(
+ new FilteredConnectPoint(d1p10),
+ new FilteredConnectPoint(d1p11),
+ new FilteredConnectPoint(d1p12),
+ new FilteredConnectPoint(d2p10)
+ ))
+ .build();
+
+ sut.activate();
+
+ List<Intent> compiled = sut.compile(intent, Collections.emptyList());
+ assertThat(compiled, hasSize(1));
+
+ Collection<FlowRule> rules = ((FlowRuleIntent) compiled.get(0)).flowRules();
+ assertThat(rules, hasSize(3));
+
+ Collection<FlowRule> rulesS3 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d3p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS3, hasSize(1));
+ FlowRule ruleS3 = rulesS3.iterator().next();
+ assertThat(ruleS3.selector(), is(
+ DefaultTrafficSelector
+ .builder(ipPrefixSelector)
+ .matchVlanId(((VlanIdCriterion) vlan100Selector.getCriterion(VLAN_VID)).vlanId())
+ .matchInPort(d3p10.port())
+ .build()
+ ));
+ assertThat(ruleS3.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .setOutput(d3p0.port())
+ .build()
+ ));
+
+ Collection<FlowRule> rulesS2 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d2p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS2, hasSize(1));
+ FlowRule ruleS2 = rulesS2.iterator().next();
+ assertThat(ruleS2.selector(), is(
+ DefaultTrafficSelector
+ .builder(ipPrefixSelector)
+ .matchInPort(d2p1.port())
+ .matchVlanId(((VlanIdCriterion) vlan100Selector.getCriterion(VLAN_VID)).vlanId())
+ .build()
+ ));
+ assertThat(ruleS2.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .setOutput(d2p0.port())
+ .setEthDst(((ModEtherInstruction) ethDstTreatment
+ .allInstructions()
+ .stream()
+ .filter(instruction -> instruction instanceof ModEtherInstruction)
+ .findFirst().get()).mac())
+ .popVlan()
+ .setOutput(d2p10.port())
+ .build()
+ ));
+
+ Collection<FlowRule> rulesS1 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d1p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS1, hasSize(1));
+ FlowRule ruleS1 = rulesS1.iterator().next();
+ assertThat(ruleS1.selector(), is(
+ DefaultTrafficSelector
+ .builder(ipPrefixSelector)
+ .matchInPort(d1p0.port())
+ .matchVlanId(((VlanIdCriterion) vlan100Selector.getCriterion(VLAN_VID)).vlanId())
+ .build()
+ ));
+ assertThat(ruleS1.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .setEthDst(((ModEtherInstruction) ethDstTreatment
+ .allInstructions()
+ .stream()
+ .filter(instruction -> instruction instanceof ModEtherInstruction)
+ .findFirst().get()).mac())
+ .popVlan()
+ .setOutput(d1p12.port())
+ .setEthDst(((ModEtherInstruction) ethDstTreatment
+ .allInstructions()
+ .stream()
+ .filter(instruction -> instruction instanceof ModEtherInstruction)
+ .findFirst().get()).mac())
+ .setOutput(d1p10.port())
+ .setEthDst(((ModEtherInstruction) ethDstTreatment
+ .allInstructions()
+ .stream()
+ .filter(instruction -> instruction instanceof ModEtherInstruction)
+ .findFirst().get()).mac())
+ .setOutput(d1p11.port())
+ .build()
+ ));
+
+ sut.deactivate();
+
+ }
+
+ /**
+ * We test the proper optimization of sp2mp with the VLAN
+ * encapsulation and trivial selector.
+ */
+ @Test
+ public void testOptimizationMplsEncapTrivialSelectors() {
+
+ intent = LinkCollectionIntent.builder()
+ .appId(APP_ID)
+ .selector(selector)
+ .treatment(treatment)
+ .constraints(constraintsForMPLS)
+ .links(linksForSp2Mp)
+ .filteredIngressPoints(ImmutableSet.of(new FilteredConnectPoint(d3p10)))
+ .filteredEgressPoints(ImmutableSet.of(
+ new FilteredConnectPoint(d1p10),
+ new FilteredConnectPoint(d1p11),
+ new FilteredConnectPoint(d2p10)
+ ))
+ .build();
+
+ sut.activate();
+ /*
+ * We use the FIRST_FIT to simplify tests.
+ */
+ LinkCollectionCompiler.labelAllocator.setLabelSelection(LABEL_SELECTION);
+
+ List<Intent> compiled = sut.compile(intent, Collections.emptyList());
+ assertThat(compiled, hasSize(1));
+
+ Collection<FlowRule> rules = ((FlowRuleIntent) compiled.get(0)).flowRules();
+ assertThat(rules, hasSize(3));
+
+ Collection<FlowRule> rulesS3 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d3p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS3, hasSize(1));
+ FlowRule ruleS3 = rulesS3.iterator().next();
+ assertThat(ruleS3.selector(), is(
+ DefaultTrafficSelector
+ .builder()
+ .matchInPort(d3p10.port())
+ .build()
+ ));
+ assertThat(ruleS3.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .pushMpls()
+ .copyTtlOut()
+ .setMpls(MplsLabel.mplsLabel(LABEL))
+ .setOutput(d3p0.port())
+ .build()
+ ));
+
+ Collection<FlowRule> rulesS2 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d2p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS2, hasSize(1));
+ FlowRule ruleS2 = rulesS2.iterator().next();
+ assertThat(ruleS2.selector(), is(
+ DefaultTrafficSelector
+ .builder()
+ .matchInPort(d2p1.port())
+ .matchMplsLabel(MplsLabel.mplsLabel((LABEL)))
+ .matchEthType(Ethernet.MPLS_UNICAST)
+ .build()
+ ));
+ assertThat(ruleS2.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .setMpls(MplsLabel.mplsLabel(LABEL))
+ .setOutput(d2p0.port())
+ .copyTtlIn()
+ .popMpls(IPV4.ethType())
+ .setOutput(d2p10.port())
+ .build()
+ ));
+
+ Collection<FlowRule> rulesS1 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d1p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS1, hasSize(1));
+ FlowRule ruleS1 = rulesS1.iterator().next();
+ assertThat(ruleS1.selector(), is(
+ DefaultTrafficSelector
+ .builder()
+ .matchInPort(d1p0.port())
+ .matchMplsLabel(MplsLabel.mplsLabel((LABEL)))
+ .matchEthType(Ethernet.MPLS_UNICAST)
+ .build()
+ ));
+ assertThat(ruleS1.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .copyTtlIn()
+ .popMpls(IPV4.ethType())
+ .setOutput(d1p10.port())
+ .setOutput(d1p11.port())
+ .build()
+ ));
+
+ sut.deactivate();
+
+ }
+
+ /**
+ * We test the proper optimization of sp2mp with the VLAN
+ * encapsulation, filtered selectors.
+ */
+ @Test
+ public void testOptimizationVlanEncapMplsSelectors() {
+
+ intent = LinkCollectionIntent.builder()
+ .appId(APP_ID)
+ .selector(selector)
+ .treatment(treatment)
+ .constraints(constraintsForVlan)
+ .links(linksForSp2Mp)
+ .filteredIngressPoints(ImmutableSet.of(new FilteredConnectPoint(d3p10, mpls80Selector)))
+ .filteredEgressPoints(ImmutableSet.of(
+ new FilteredConnectPoint(d1p10, mpls100Selector),
+ new FilteredConnectPoint(d1p11, mpls200Selector),
+ new FilteredConnectPoint(d2p10, mpls69Selector)
+ ))
+ .build();
+
+ sut.activate();
+ /*
+ * We use the FIRST_FIT to simplify tests.
+ */
+ LinkCollectionCompiler.labelAllocator.setLabelSelection(LABEL_SELECTION);
+
+ List<Intent> compiled = sut.compile(intent, Collections.emptyList());
+ assertThat(compiled, hasSize(1));
+
+ Collection<FlowRule> rules = ((FlowRuleIntent) compiled.get(0)).flowRules();
+ assertThat(rules, hasSize(3));
+
+ Collection<FlowRule> rulesS3 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d3p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS3, hasSize(1));
+ FlowRule ruleS3 = rulesS3.iterator().next();
+ assertThat(ruleS3.selector(), is(
+ DefaultTrafficSelector
+ .builder(mpls80Selector)
+ .matchInPort(d3p10.port())
+ .build()
+ ));
+ assertThat(ruleS3.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .copyTtlIn()
+ .popMpls(IPV4.ethType())
+ .pushVlan()
+ .setVlanId(VlanId.vlanId(LABEL))
+ .setOutput(d3p0.port())
+ .build()
+ ));
+
+ Collection<FlowRule> rulesS2 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d2p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS2, hasSize(1));
+ FlowRule ruleS2 = rulesS2.iterator().next();
+ assertThat(ruleS2.selector(), is(
+ DefaultTrafficSelector
+ .builder()
+ .matchInPort(d2p1.port())
+ .matchVlanId(VlanId.vlanId(LABEL))
+ .build()
+ ));
+ assertThat(ruleS2.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .setVlanId(VlanId.vlanId(LABEL))
+ .setOutput(d2p0.port())
+ .popVlan()
+ .pushMpls()
+ .copyTtlOut()
+ .setMpls(((MplsCriterion) mpls69Selector.getCriterion(MPLS_LABEL)).label())
+ .setOutput(d2p10.port())
+ .build()
+ ));
+
+ Collection<FlowRule> rulesS1 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d1p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS1, hasSize(1));
+ FlowRule ruleS1 = rulesS1.iterator().next();
+ assertThat(ruleS1.selector(), is(
+ DefaultTrafficSelector
+ .builder()
+ .matchInPort(d1p0.port())
+ .matchVlanId(VlanId.vlanId(LABEL))
+ .build()
+ ));
+ assertThat(ruleS1.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .popVlan()
+ .pushMpls()
+ .copyTtlOut()
+ .setMpls(((MplsCriterion) mpls100Selector.getCriterion(MPLS_LABEL)).label())
+ .setOutput(d1p10.port())
+ .setMpls(((MplsCriterion) mpls200Selector.getCriterion(MPLS_LABEL)).label())
+ .setOutput(d1p11.port())
+ .build()
+ ));
+
+ sut.deactivate();
+
+ }
+
+ /**
+ * We test the proper optimization of sp2mp with the MPLS
+ * encapsulation, filtered selectors, intent selector, and
+ * intent treatment.
+ */
+ @Test
+ public void testOptimizationMplsEncapNonTrivial() {
+
+ intent = LinkCollectionIntent.builder()
+ .appId(APP_ID)
+ .selector(ipPrefixSelector)
+ .treatment(ethDstTreatment)
+ .constraints(constraintsForMPLS)
+ .links(linksForSp2Mp)
+ .filteredIngressPoints(ImmutableSet.of(new FilteredConnectPoint(d3p10, vlan69Selector)))
+ .filteredEgressPoints(ImmutableSet.of(
+ new FilteredConnectPoint(d1p10, vlan100Selector),
+ new FilteredConnectPoint(d1p11, vlan200Selector),
+ new FilteredConnectPoint(d2p10, vlan300Selector)
+ ))
+ .build();
+
+ sut.activate();
+ /*
+ * We use the FIRST_FIT to simplify tests.
+ */
+ LinkCollectionCompiler.labelAllocator.setLabelSelection(LABEL_SELECTION);
+
+ List<Intent> compiled = sut.compile(intent, Collections.emptyList());
+ assertThat(compiled, hasSize(1));
+
+ Collection<FlowRule> rules = ((FlowRuleIntent) compiled.get(0)).flowRules();
+ assertThat(rules, hasSize(3));
+
+ Collection<FlowRule> rulesS3 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d3p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS3, hasSize(1));
+ FlowRule ruleS3 = rulesS3.iterator().next();
+ assertThat(ruleS3.selector(), is(
+ DefaultTrafficSelector
+ .builder(ipPrefixSelector)
+ .matchInPort(d3p10.port())
+ .matchVlanId(((VlanIdCriterion) vlan69Selector.getCriterion(VLAN_VID)).vlanId())
+ .build()
+ ));
+ assertThat(ruleS3.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .popVlan()
+ .pushMpls()
+ .copyTtlOut()
+ .setMpls(MplsLabel.mplsLabel(LABEL))
+ .setOutput(d3p0.port())
+ .build()
+ ));
+
+ Collection<FlowRule> rulesS2 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d2p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS2, hasSize(1));
+ FlowRule ruleS2 = rulesS2.iterator().next();
+ assertThat(ruleS2.selector(), is(
+ DefaultTrafficSelector
+ .builder()
+ .matchInPort(d2p1.port())
+ .matchMplsLabel(MplsLabel.mplsLabel(LABEL))
+ .matchEthType(Ethernet.MPLS_UNICAST)
+ .build()
+ ));
+ assertThat(ruleS2.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .setMpls(MplsLabel.mplsLabel(LABEL))
+ .setOutput(d2p0.port())
+ .setEthDst(((ModEtherInstruction) ethDstTreatment
+ .allInstructions()
+ .stream()
+ .filter(instruction -> instruction instanceof ModEtherInstruction)
+ .findFirst().get()).mac())
+ .copyTtlIn()
+ .popMpls(IPV4.ethType())
+ .pushVlan()
+ .setVlanId(((VlanIdCriterion) vlan300Selector.getCriterion(VLAN_VID)).vlanId())
+ .setOutput(d2p10.port())
+ .build()
+ ));
+
+ Collection<FlowRule> rulesS1 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d1p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS1, hasSize(1));
+ FlowRule ruleS1 = rulesS1.iterator().next();
+ assertThat(ruleS1.selector(), is(
+ DefaultTrafficSelector
+ .builder()
+ .matchInPort(d1p0.port())
+ .matchMplsLabel(MplsLabel.mplsLabel(LABEL))
+ .matchEthType(Ethernet.MPLS_UNICAST)
+ .build()
+ ));
+ assertThat(ruleS1.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .setEthDst(((ModEtherInstruction) ethDstTreatment
+ .allInstructions()
+ .stream()
+ .filter(instruction -> instruction instanceof ModEtherInstruction)
+ .findFirst().get()).mac())
+ .copyTtlIn()
+ .popMpls(IPV4.ethType())
+ .pushVlan()
+ .setVlanId(((VlanIdCriterion) vlan100Selector.getCriterion(VLAN_VID)).vlanId())
+ .setOutput(d1p10.port())
+ .setEthDst(((ModEtherInstruction) ethDstTreatment
+ .allInstructions()
+ .stream()
+ .filter(instruction -> instruction instanceof ModEtherInstruction)
+ .findFirst().get()).mac())
+ .setVlanId(((VlanIdCriterion) vlan200Selector.getCriterion(VLAN_VID)).vlanId())
+ .setOutput(d1p11.port())
+ .build()
+ ));
+
+ sut.deactivate();
+
+ }
+
+ /**
+ * We test the proper compilation of sp2mp with the VLAN
+ * encapsulation and filtered selectors of different type.
+ */
+ @Test
+ public void testOptimizationVlanEncapDifferentSelectors() {
+
+ intent = LinkCollectionIntent.builder()
+ .appId(APP_ID)
+ .selector(selector)
+ .treatment(treatment)
+ .constraints(constraintsForVlan)
+ .links(linksForSp2Mp)
+ .filteredIngressPoints(ImmutableSet.of(new FilteredConnectPoint(d3p10, vlan69Selector)))
+ .filteredEgressPoints(ImmutableSet.of(
+ new FilteredConnectPoint(d1p10, mpls100Selector),
+ new FilteredConnectPoint(d1p11, vlan200Selector),
+ new FilteredConnectPoint(d1p12),
+ new FilteredConnectPoint(d2p10, mpls200Selector)
+ ))
+ .build();
+
+ sut.activate();
+ /*
+ * We use the FIRST_FIT to simplify tests.
+ */
+ LinkCollectionCompiler.labelAllocator.setLabelSelection(LABEL_SELECTION);
+
+ List<Intent> compiled = sut.compile(intent, Collections.emptyList());
+ assertThat(compiled, hasSize(1));
+
+ Collection<FlowRule> rules = ((FlowRuleIntent) compiled.get(0)).flowRules();
+ assertThat(rules, hasSize(3));
+
+ Collection<FlowRule> rulesS3 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d3p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS3, hasSize(1));
+ FlowRule ruleS3 = rulesS3.iterator().next();
+ assertThat(ruleS3.selector(), is(
+ DefaultTrafficSelector
+ .builder(vlan69Selector)
+ .matchInPort(d3p10.port())
+ .build()
+ ));
+ assertThat(ruleS3.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .setVlanId(VlanId.vlanId(LABEL))
+ .setOutput(d3p0.port())
+ .build()
+ ));
+
+ Collection<FlowRule> rulesS2 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d2p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS2, hasSize(1));
+ FlowRule ruleS2 = rulesS2.iterator().next();
+ assertThat(ruleS2.selector(), is(
+ DefaultTrafficSelector
+ .builder()
+ .matchInPort(d2p1.port())
+ .matchVlanId(VlanId.vlanId(LABEL))
+ .build()
+ ));
+ assertThat(ruleS2.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .setVlanId(VlanId.vlanId(LABEL))
+ .setOutput(d2p0.port())
+ .popVlan()
+ .pushMpls()
+ .copyTtlOut()
+ .setMpls(((MplsCriterion) mpls200Selector.getCriterion(MPLS_LABEL)).label())
+ .setOutput(d2p10.port())
+ .build()
+ ));
+
+ Collection<FlowRule> rulesS1 = rules.stream()
+ .filter(rule -> rule.deviceId().equals(d1p0.deviceId()))
+ .collect(Collectors.toSet());
+ assertThat(rulesS1, hasSize(1));
+ FlowRule ruleS1 = rulesS1.iterator().next();
+ assertThat(ruleS1.selector(), is(
+ DefaultTrafficSelector
+ .builder()
+ .matchInPort(d1p0.port())
+ .matchVlanId(VlanId.vlanId(LABEL))
+ .build()
+ ));
+ assertThat(ruleS1.treatment(), is(
+ DefaultTrafficTreatment
+ .builder()
+ .setVlanId(((VlanIdCriterion) vlan200Selector.getCriterion(VLAN_VID)).vlanId())
+ .setOutput(d1p11.port())
+ .popVlan()
+ .setOutput(d1p12.port())
+ .pushMpls()
+ .copyTtlOut()
+ .setMpls(((MplsCriterion) mpls100Selector.getCriterion(MPLS_LABEL)).label())
+ .setOutput(d1p10.port())
+ .build()
+ ));
+
+ sut.deactivate();
+
+ }
+
+}