Michele Santuari
Committed by Gerrit Code Review

Support encapsulation in PathIntent

- related to ONOS-3467
- unit test work
- depends on ONOS-3507 and also on the advertisement of VLAN resource
  for different devices.

Change-Id: Ia852c751135b5ca4a16901c6f3a85ceea11514a3
......@@ -15,42 +15,68 @@
*/
package org.onosproject.net.intent.impl.compiler;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onlab.packet.VlanId;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.EncapsulationType;
import org.onosproject.net.Link;
import org.onosproject.net.LinkKey;
import org.onosproject.net.flow.DefaultFlowRule;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.criteria.Criterion;
import org.onosproject.net.flow.criteria.VlanIdCriterion;
import org.onosproject.net.intent.FlowRuleIntent;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.IntentCompiler;
import org.onosproject.net.intent.IntentExtensionService;
import org.onosproject.net.intent.PathIntent;
import org.onosproject.net.intent.constraint.EncapsulationConstraint;
import org.onosproject.net.intent.impl.IntentCompilationException;
import org.onosproject.net.newresource.ResourcePath;
import org.onosproject.net.newresource.ResourceService;
import org.onosproject.net.resource.link.LinkResourceAllocations;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.onosproject.net.LinkKey.linkKey;
import static org.slf4j.LoggerFactory.getLogger;
@Component(immediate = true)
public class PathIntentCompiler implements IntentCompiler<PathIntent> {
private final Logger log = getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected IntentExtensionService intentManager;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ResourceService resourceService;
private ApplicationId appId;
@Activate
......@@ -71,7 +97,13 @@ public class PathIntentCompiler implements IntentCompiler<PathIntent> {
// TODO: implement recompile behavior
List<Link> links = intent.path().links();
List<FlowRule> rules = new ArrayList<>(links.size() - 1);
List<FlowRule> rules = new LinkedList<>();
Optional<EncapsulationConstraint> enacpConstraint = intent.constraints().stream()
.filter(constraint -> constraint instanceof EncapsulationConstraint)
.map(x -> (EncapsulationConstraint) x).findAny();
//if no encapsulation or is involved only a single switch use the default behaviour
if (!enacpConstraint.isPresent() || links.size() == 1) {
for (int i = 0; i < links.size() - 1; i++) {
ConnectPoint ingress = links.get(i).dst();
......@@ -82,18 +114,30 @@ public class PathIntentCompiler implements IntentCompiler<PathIntent> {
rules.add(rule);
}
return Collections.singletonList(new FlowRuleIntent(appId, null, rules, intent.resources()));
return ImmutableList.of(new FlowRuleIntent(appId, null, rules, intent.resources()));
} else {
if (EncapsulationType.VLAN == enacpConstraint.get().encapType()) {
rules = manageVlanEncap(intent);
}
if (EncapsulationType.MPLS == enacpConstraint.get().encapType()) {
//TODO: to be implemented
rules = Collections.emptyList();
}
return ImmutableList.of(new FlowRuleIntent(appId, null, rules, intent.resources()));
}
}
private FlowRule createFlowRule(TrafficSelector originalSelector, TrafficTreatment originalTreatment,
ConnectPoint ingress, ConnectPoint egress,
int priority, boolean last) {
int priority, boolean applyTreatment) {
TrafficSelector selector = DefaultTrafficSelector.builder(originalSelector)
.matchInPort(ingress.port())
.build();
TrafficTreatment.Builder treatmentBuilder;
if (last) {
if (applyTreatment) {
treatmentBuilder = DefaultTrafficTreatment.builder(originalTreatment);
} else {
treatmentBuilder = DefaultTrafficTreatment.builder();
......@@ -110,6 +154,138 @@ public class PathIntentCompiler implements IntentCompiler<PathIntent> {
.build();
}
private List<FlowRule> manageVlanEncap(PathIntent intent) {
Map<LinkKey, VlanId> vlanIds = assignVlanId(intent);
Iterator<Link> links = intent.path().links().iterator();
Link srcLink = links.next();
Link link = links.next();
// List of flow rules to be installed
List<FlowRule> rules = new LinkedList<>();
// Ingress traffic
VlanId vlanId = vlanIds.get(linkKey(link));
if (vlanId == null) {
throw new IntentCompilationException("No available VLAN ID for " + link);
}
VlanId prevVlanId = vlanId;
//Tag the traffic with the new VLAN
TrafficTreatment treat = DefaultTrafficTreatment.builder()
.setVlanId(vlanId)
.build();
rules.add(createFlowRule(intent.selector(), treat, srcLink.dst(), link.src(), intent.priority(), true));
ConnectPoint prev = link.dst();
while (links.hasNext()) {
link = links.next();
if (links.hasNext()) {
// Transit traffic
VlanId egressVlanId = vlanIds.get(linkKey(link));
if (egressVlanId == null) {
throw new IntentCompilationException("No available VLAN ID for " + link);
}
prevVlanId = egressVlanId;
TrafficSelector transitSelector = DefaultTrafficSelector.builder()
.matchInPort(prev.port())
.matchVlanId(prevVlanId).build();
TrafficTreatment.Builder transitTreat = DefaultTrafficTreatment.builder();
// Set the new vlanId only if the previous one is different
if (!prevVlanId.equals(egressVlanId)) {
transitTreat.setVlanId(egressVlanId);
}
rules.add(createFlowRule(transitSelector,
transitTreat.build(), prev, link.src(), intent.priority(), true));
prev = link.dst();
} else {
// Egress traffic
TrafficSelector egressSelector = DefaultTrafficSelector.builder()
.matchInPort(prev.port())
.matchVlanId(prevVlanId).build();
//TODO: think to other cases for egress packet restoration
Optional<VlanIdCriterion> vlanCriteria = intent.selector().criteria()
.stream().filter(criteria -> criteria.type() == Criterion.Type.VLAN_VID)
.map(criteria -> (VlanIdCriterion) criteria)
.findAny();
TrafficTreatment.Builder egressTreat = DefaultTrafficTreatment.builder(intent.treatment());
if (vlanCriteria.isPresent()) {
egressTreat.setVlanId(vlanCriteria.get().vlanId());
} else {
egressTreat.popVlan();
}
rules.add(createFlowRule(egressSelector,
egressTreat.build(), prev, link.src(), intent.priority(), true));
}
}
return rules;
}
private Map<LinkKey, VlanId> assignVlanId(PathIntent intent) {
Set<LinkKey> linkRequest = Sets.newHashSetWithExpectedSize(intent.path()
.links().size() - 2);
for (int i = 1; i <= intent.path().links().size() - 2; i++) {
LinkKey link = linkKey(intent.path().links().get(i));
linkRequest.add(link);
// add the inverse link. I want that the VLANID is reserved both for
// the direct and inverse link
linkRequest.add(linkKey(link.dst(), link.src()));
}
Map<LinkKey, VlanId> vlanIds = findVlanIds(linkRequest);
if (vlanIds.isEmpty()) {
log.warn("No VLAN IDs available");
return Collections.emptyMap();
}
//same VLANID is used for both directions
Set<ResourcePath> resources = vlanIds.entrySet().stream()
.flatMap(x -> Stream.of(
ResourcePath.discrete(x.getKey().src().deviceId(), x.getKey().src().port(), x.getValue()),
ResourcePath.discrete(x.getKey().dst().deviceId(), x.getKey().dst().port(), x.getValue())
))
.collect(Collectors.toSet());
List<org.onosproject.net.newresource.ResourceAllocation> allocations =
resourceService.allocate(intent.id(), ImmutableList.copyOf(resources));
if (allocations.isEmpty()) {
Collections.emptyMap();
}
return vlanIds;
}
private Map<LinkKey, VlanId> findVlanIds(Set<LinkKey> links) {
Map<LinkKey, VlanId> vlanIds = new HashMap<>();
for (LinkKey link : links) {
Set<VlanId> forward = findVlanId(link.src());
Set<VlanId> backward = findVlanId(link.dst());
Set<VlanId> common = Sets.intersection(forward, backward);
if (common.isEmpty()) {
continue;
}
vlanIds.put(link, common.iterator().next());
}
return vlanIds;
}
private Set<VlanId> findVlanId(ConnectPoint cp) {
return resourceService.getAvailableResources(ResourcePath.discrete(cp.deviceId(), cp.port())).stream()
.filter(x -> x.last() instanceof VlanId)
.map(x -> (VlanId) x.last())
.collect(Collectors.toSet());
}
private boolean isLast(List<Link> links, int i) {
return i == links.size() - 2;
}
......
......@@ -17,6 +17,7 @@ package org.onosproject.net.intent.impl.compiler;
import com.google.common.collect.ImmutableList;
import org.onlab.packet.MplsLabel;
import org.onlab.packet.VlanId;
import org.onosproject.net.newresource.ResourceAllocation;
import org.onosproject.net.newresource.ResourceConsumer;
import org.onosproject.net.newresource.ResourceListener;
......@@ -25,6 +26,7 @@ import org.onosproject.net.newresource.ResourceService;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
......@@ -91,8 +93,11 @@ class MockResourceService implements ResourceService {
@Override
public Collection<ResourcePath> getAvailableResources(ResourcePath parent) {
ResourcePath resource = parent.child(MplsLabel.mplsLabel(10));
return ImmutableList.of(resource);
Collection<ResourcePath> resources = new HashSet<ResourcePath>();
resources.add(parent.child(VlanId.vlanId((short) 10)));
resources.add(parent.child(MplsLabel.mplsLabel(10)));
return ImmutableList.copyOf(resources);
}
@Override
......
......@@ -15,14 +15,11 @@
*/
package org.onosproject.net.intent.impl.compiler;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import com.google.common.collect.ImmutableList;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.onlab.packet.VlanId;
import org.onosproject.TestApplicationId;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
......@@ -30,30 +27,38 @@ import org.onosproject.core.IdGenerator;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DefaultLink;
import org.onosproject.net.DefaultPath;
import org.onosproject.net.EncapsulationType;
import org.onosproject.net.Link;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.instructions.Instructions;
import org.onosproject.net.flow.instructions.L2ModificationInstruction;
import org.onosproject.net.intent.FlowRuleIntent;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.IntentExtensionService;
import org.onosproject.net.intent.MockIdGenerator;
import org.onosproject.net.intent.PathIntent;
import org.onosproject.net.intent.constraint.EncapsulationConstraint;
import org.onosproject.net.provider.ProviderId;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
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.Matchers.is;
import static org.hamcrest.number.OrderingComparison.greaterThan;
import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
import static org.onosproject.net.Link.Type.DIRECT;
import static org.onosproject.net.NetTestTools.APP_ID;
import static org.onosproject.net.NetTestTools.PID;
import static org.onosproject.net.NetTestTools.connectPoint;
import static org.onosproject.net.NetTestTools.*;
/**
* Unit tests for PathIntentCompiler.
......@@ -85,6 +90,7 @@ public class PathIntentCompilerTest {
);
private final int hops = links.size() - 1;
private PathIntent intent;
private PathIntent constraintIntent;
/**
* Configures objects used in all the test cases.
......@@ -96,6 +102,7 @@ public class PathIntentCompilerTest {
expect(coreService.registerApplication("org.onosproject.net.intent"))
.andReturn(appId);
sut.coreService = coreService;
sut.resourceService = new MockResourceService();
Intent.bindIdGenerator(idGenerator);
......@@ -106,6 +113,14 @@ public class PathIntentCompilerTest {
.priority(PRIORITY)
.path(new DefaultPath(pid, links, hops))
.build();
constraintIntent = PathIntent.builder()
.appId(APP_ID)
.selector(selector)
.treatment(treatment)
.priority(PRIORITY)
.constraints(ImmutableList.of(new EncapsulationConstraint(EncapsulationType.VLAN)))
.path(new DefaultPath(pid, links, hops))
.build();
intentExtensionService = createMock(IntentExtensionService.class);
intentExtensionService.registerCompiler(PathIntent.class, sut);
intentExtensionService.unregisterCompiler(PathIntent.class);
......@@ -169,4 +184,91 @@ public class PathIntentCompilerTest {
sut.deactivate();
}
/**
* Tests the compilation behavior of the path intent compiler in case of
* encasulation costraint {@link EncapsulationConstraint}.
*/
@Test
public void testEncapCompile() {
sut.activate();
List<Intent> compiled = sut.compile(constraintIntent, Collections.emptyList(), Collections.emptySet());
assertThat(compiled, hasSize(1));
Collection<FlowRule> rules = ((FlowRuleIntent) compiled.get(0)).flowRules();
assertThat(rules, hasSize(3));
FlowRule rule1 = rules.stream()
.filter(x -> x.deviceId().equals(d1p0.deviceId()))
.findFirst()
.get();
assertThat(rule1.deviceId(), is(d1p0.deviceId()));
assertThat(rule1.priority(), is(intent.priority()));
verifyEncapSelector(rule1.selector(), d1p0, VlanId.NONE);
VlanId vlanToEncap = verifyEncapTreatment(rule1.treatment(), d1p1, true, false);
FlowRule rule2 = rules.stream()
.filter(x -> x.deviceId().equals(d2p0.deviceId()))
.findFirst()
.get();
assertThat(rule2.deviceId(), is(d2p0.deviceId()));
assertThat(rule2.priority(), is(intent.priority()));
verifyEncapSelector(rule2.selector(), d2p0, vlanToEncap);
verifyEncapTreatment(rule2.treatment(), d2p1, false, false);
FlowRule rule3 = rules.stream()
.filter(x -> x.deviceId().equals(d3p0.deviceId()))
.findFirst()
.get();
assertThat(rule3.deviceId(), is(d3p1.deviceId()));
assertThat(rule3.priority(), is(intent.priority()));
verifyEncapSelector(rule3.selector(), d3p1, vlanToEncap);
verifyEncapTreatment(rule3.treatment(), d3p0, false, true);
sut.deactivate();
}
private VlanId verifyEncapTreatment(TrafficTreatment trafficTreatment,
ConnectPoint egress, boolean isIngress, boolean isEgress) {
Set<Instructions.OutputInstruction> ruleOutput = trafficTreatment.allInstructions().stream()
.filter(treat -> treat instanceof Instructions.OutputInstruction)
.map(treat -> (Instructions.OutputInstruction) treat)
.collect(Collectors.toSet());
assertThat(ruleOutput, hasSize(1));
assertThat((ruleOutput.iterator().next()).port(), is(egress.port()));
VlanId vlanToEncap = VlanId.NONE;
if (isIngress && !isEgress) {
Set<L2ModificationInstruction.ModVlanIdInstruction> vlanRules = trafficTreatment.allInstructions().stream()
.filter(treat -> treat instanceof L2ModificationInstruction.ModVlanIdInstruction)
.map(x -> (L2ModificationInstruction.ModVlanIdInstruction) x)
.collect(Collectors.toSet());
assertThat(vlanRules, hasSize(1));
L2ModificationInstruction.ModVlanIdInstruction vlanRule = vlanRules.iterator().next();
assertThat(vlanRule.vlanId().toShort(), greaterThan((short) 0));
vlanToEncap = vlanRule.vlanId();
} else if (!isIngress && !isEgress) {
assertThat(trafficTreatment.allInstructions().stream()
.filter(treat -> treat instanceof L2ModificationInstruction.ModVlanIdInstruction)
.collect(Collectors.toSet()), hasSize(0));
} else {
assertThat(trafficTreatment.allInstructions().stream()
.filter(treat -> treat instanceof L2ModificationInstruction.ModVlanIdInstruction)
.collect(Collectors.toSet()), hasSize(0));
assertThat(trafficTreatment.allInstructions().stream()
.filter(treat -> treat instanceof L2ModificationInstruction.PopVlanInstruction)
.collect(Collectors.toSet()), hasSize(1));
}
return vlanToEncap;
}
private void verifyEncapSelector(TrafficSelector trafficSelector, ConnectPoint ingress, VlanId vlanToMatch) {
is(DefaultTrafficSelector.builder(selector).matchInPort(ingress.port())
.matchVlanId(vlanToMatch).build());
}
}
......