blob: a9bf1b7241b1edbdde9be7456681b07cdc95aed2 [file] [log] [blame]
/*
* Copyright 2015 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.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.EthType;
import org.onlab.packet.Ethernet;
import org.onlab.packet.MplsLabel;
import org.onlab.packet.VlanId;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Link;
import org.onosproject.net.LinkKey;
import org.onosproject.net.PortNumber;
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.EthTypeCriterion;
import org.onosproject.net.flow.instructions.Instruction;
import org.onosproject.net.flow.instructions.L2ModificationInstruction;
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.MplsPathIntent;
import org.onosproject.net.newresource.Resource;
import org.onosproject.net.newresource.ResourceService;
import org.onosproject.net.resource.link.LinkResourceAllocations;
import org.slf4j.Logger;
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.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onosproject.net.LinkKey.linkKey;
import static org.slf4j.LoggerFactory.getLogger;
@Component(immediate = true)
public class MplsPathIntentCompiler implements IntentCompiler<MplsPathIntent> {
private final Logger log = getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected IntentExtensionService intentExtensionService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ResourceService resourceService;
protected ApplicationId appId;
@Override
public List<Intent> compile(MplsPathIntent intent, List<Intent> installable,
Set<LinkResourceAllocations> resources) {
Map<LinkKey, MplsLabel> labels = assignMplsLabel(intent);
List<FlowRule> rules = generateRules(intent, labels);
return Collections.singletonList(new FlowRuleIntent(appId, rules, intent.resources()));
}
@Activate
public void activate() {
appId = coreService.registerApplication("org.onosproject.net.intent");
intentExtensionService.registerCompiler(MplsPathIntent.class, this);
}
@Deactivate
public void deactivate() {
intentExtensionService.unregisterCompiler(MplsPathIntent.class);
}
private Map<LinkKey, MplsLabel> assignMplsLabel(MplsPathIntent intent) {
// TODO: do it better... Suggestions?
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 label is reserved both for
// the direct and inverse link
linkRequest.add(linkKey(link.dst(), link.src()));
}
Map<LinkKey, MplsLabel> labels = findMplsLabels(linkRequest);
if (labels.isEmpty()) {
return Collections.emptyMap();
}
// for short term solution: same label is used for both directions
// TODO: introduce the concept of Tx and Rx resources of a port
Set<Resource> resources = labels.entrySet().stream()
.flatMap(x -> Stream.of(
Resource.discrete(x.getKey().src().deviceId(), x.getKey().src().port(), x.getValue()),
Resource.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 labels;
}
private Map<LinkKey, MplsLabel> findMplsLabels(Set<LinkKey> links) {
Map<LinkKey, MplsLabel> labels = new HashMap<>();
for (LinkKey link : links) {
Set<MplsLabel> forward = findMplsLabel(link.src());
Set<MplsLabel> backward = findMplsLabel(link.dst());
Set<MplsLabel> common = Sets.intersection(forward, backward);
if (common.isEmpty()) {
continue;
}
labels.put(link, common.iterator().next());
}
return labels;
}
private Set<MplsLabel> findMplsLabel(ConnectPoint cp) {
return resourceService.getAvailableResources(Resource.discrete(cp.deviceId(), cp.port())).stream()
.filter(x -> x.last() instanceof MplsLabel)
.map(x -> (MplsLabel) x.last())
.collect(Collectors.toSet());
}
private MplsLabel getMplsLabel(Map<LinkKey, MplsLabel> labels, LinkKey link) {
return labels.get(link);
}
private List<FlowRule> generateRules(MplsPathIntent intent,
Map<LinkKey, MplsLabel> labels) {
Iterator<Link> links = intent.path().links().iterator();
Link srcLink = links.next();
ConnectPoint prev = srcLink.dst();
Link link = links.next();
// List of flow rules to be installed
List<FlowRule> rules = new LinkedList<>();
// Ingress traffic
// Get the new MPLS label
MplsLabel mpls = getMplsLabel(labels, linkKey(link));
checkNotNull(mpls);
MplsLabel prevLabel = mpls;
rules.add(ingressFlow(prev.port(), link, intent, mpls));
prev = link.dst();
while (links.hasNext()) {
link = links.next();
if (links.hasNext()) {
// Transit traffic
// Get the new MPLS label
mpls = getMplsLabel(labels, linkKey(link));
checkNotNull(mpls);
rules.add(transitFlow(prev.port(), link, intent,
prevLabel, mpls));
prevLabel = mpls;
} else {
// Egress traffic
rules.add(egressFlow(prev.port(), link, intent,
prevLabel));
}
prev = link.dst();
}
return rules;
}
private FlowRule ingressFlow(PortNumber inPort, Link link,
MplsPathIntent intent,
MplsLabel label) {
TrafficSelector.Builder ingressSelector = DefaultTrafficSelector
.builder(intent.selector());
TrafficTreatment.Builder treat = DefaultTrafficTreatment.builder();
ingressSelector.matchInPort(inPort);
if (intent.ingressLabel().isPresent()) {
ingressSelector.matchEthType(Ethernet.MPLS_UNICAST)
.matchMplsLabel(intent.ingressLabel().get());
// Swap the MPLS label
treat.setMpls(label);
} else {
// Push and set the MPLS label
treat.pushMpls().setMpls(label);
}
// Add the output action
treat.setOutput(link.src().port());
return createFlowRule(intent, link.src().deviceId(), ingressSelector.build(), treat.build());
}
private FlowRule transitFlow(PortNumber inPort, Link link,
MplsPathIntent intent,
MplsLabel prevLabel,
MplsLabel outLabel) {
// Ignore the ingress Traffic Selector and use only the MPLS label
// assigned in the previous link
TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
selector.matchInPort(inPort).matchEthType(Ethernet.MPLS_UNICAST)
.matchMplsLabel(prevLabel);
TrafficTreatment.Builder treat = DefaultTrafficTreatment.builder();
// Set the new label only if the label on the packet is
// different
if (!prevLabel.equals(outLabel)) {
treat.setMpls(outLabel);
}
treat.setOutput(link.src().port());
return createFlowRule(intent, link.src().deviceId(), selector.build(), treat.build());
}
private FlowRule egressFlow(PortNumber inPort, Link link,
MplsPathIntent intent,
MplsLabel prevLabel) {
// egress point: either set the egress MPLS label or pop the
// MPLS label based on the intent annotations
TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
selector.matchInPort(inPort).matchEthType(Ethernet.MPLS_UNICAST)
.matchMplsLabel(prevLabel);
// apply the intent's treatments
TrafficTreatment.Builder treat = DefaultTrafficTreatment.builder(intent
.treatment());
// check if the treatement is popVlan or setVlan (rewrite),
// than selector needs to match any VlanId
for (Instruction instruct : intent.treatment().allInstructions()) {
if (instruct instanceof L2ModificationInstruction) {
L2ModificationInstruction l2Mod = (L2ModificationInstruction) instruct;
if (l2Mod.subtype() == L2ModificationInstruction.L2SubType.VLAN_PUSH) {
break;
}
if (l2Mod.subtype() == L2ModificationInstruction.L2SubType.VLAN_POP ||
l2Mod.subtype() == L2ModificationInstruction.L2SubType.VLAN_ID) {
selector.matchVlanId(VlanId.ANY);
}
}
}
if (intent.egressLabel().isPresent()) {
treat.setMpls(intent.egressLabel().get());
} else {
treat.popMpls(outputEthType(intent.selector()));
}
treat.setOutput(link.src().port());
return createFlowRule(intent, link.src().deviceId(),
selector.build(), treat.build());
}
protected FlowRule createFlowRule(MplsPathIntent intent, DeviceId deviceId,
TrafficSelector selector, TrafficTreatment treat) {
return DefaultFlowRule.builder()
.forDevice(deviceId)
.withSelector(selector)
.withTreatment(treat)
.withPriority(intent.priority())
.fromApp(appId)
.makePermanent()
.build();
}
// if the ingress ethertype is defined, the egress traffic
// will be use that value, otherwise the IPv4 ethertype is used.
private EthType outputEthType(TrafficSelector selector) {
Criterion c = selector.getCriterion(Criterion.Type.ETH_TYPE);
if (c != null && c instanceof EthTypeCriterion) {
EthTypeCriterion ethertype = (EthTypeCriterion) c;
return ethertype.ethType();
} else {
return EthType.EtherType.IPV4.ethType();
}
}
}