blob: 73368a2ce02a1b85ef8406a465b6c152ac169243 [file] [log] [blame]
package org.onosproject.ecord.carrierethernet.app;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onlab.packet.Ethernet;
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.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.FlowRuleService;
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.PortCriterion;
import org.onosproject.net.flow.criteria.VlanIdCriterion;
import org.onosproject.net.flow.instructions.Instruction;
import org.onosproject.net.flow.instructions.Instructions;
import org.onosproject.net.flowobjective.*;
import org.onosproject.net.meter.Meter;
import org.onosproject.net.meter.MeterId;
import org.onosproject.net.meter.MeterRequest;
import org.onosproject.net.meter.Band;
import org.onosproject.net.meter.DefaultBand;
import org.onosproject.net.meter.MeterService;
import org.onosproject.net.meter.DefaultMeterRequest;
import org.onosproject.openflow.controller.Dpid;
import org.onosproject.openflow.controller.OpenFlowController;
import org.onosproject.openflow.controller.OpenFlowSwitch;
import org.slf4j.Logger;
import java.util.Collection;
import java.util.Objects;
import java.util.Set;
import java.util.HashSet;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.LinkedList;
import java.util.Iterator;
import java.util.ListIterator;
import org.apache.commons.lang3.tuple.Pair;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Class used to control Carrier Ethernet nodes according to the OpenFlow (1.3 and above) protocol.
*/
@Component(immediate = true)
@Service (value = CarrierEthernetOpenFlowPacketNodeManager.class)
public class CarrierEthernetOpenFlowPacketNodeManager extends CarrierEthernetPacketNodeManager {
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected FlowRuleService flowRuleService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected MeterService meterService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected FlowObjectiveService flowObjectiveService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected OpenFlowController controller;
private final Logger log = getLogger(getClass());
private static ApplicationId appId;
private static final int PRIORITY = 50000;
// TODO: Below maps to be replaced by the meter ids and flow objectives associated with each CE Intent
// FIXME: Replace with Pair<DeviceId, MeterId>
private final Map<String, Set<DeviceMeterId>> deviceMeterIdMap = new HashMap<>();
private final Map<String, LinkedList<Pair<DeviceId, Objective>>> flowObjectiveMap = new HashMap();
@Activate
protected void activate() {
appId = coreService.registerApplication("org.onosproject.ecord.carrierethernet");
}
@Deactivate
protected void deactivate() {}
@Override
public void setNodeForwarding(CarrierEthernetVirtualConnection evc, CarrierEthernetNetworkInterface srcNi,
CarrierEthernetNetworkInterface dstNi, ConnectPoint ingress, ConnectPoint egress,
boolean first, boolean last) {
// FIXME: Is this necessary? Can first and last be true at the same time?
if(srcNi.cp().deviceId().equals(dstNi.cp().deviceId())) {
log.error("Source and destination NI must belong to different devices.");
return;
}
flowObjectiveMap.putIfAbsent(evc.id(), new LinkedList<>());
// TODO: Get created FlowObjectives from this method
createFlowObjectives(evc, srcNi, dstNi, ingress, egress, first, last);
}
/**
* Creates and submits FlowObjectives depending on the role of the device in the EVC and the types of srcNi/dstNi.
*
* @param evc the EVC representation
* @param srcNi the source network interface (UNI, INNI or ENNI) of the EVC for this forwarding segment
* @param dstNi the destination network interface (UNI, INNI or ENNI) of the EVC for this forwarding segment
* @param ingress the ingress connect point at the current device
* @param egress the egress connect point at the current device
* @param first is true if the current device is the first node in the end-to-end path
* @param last is true if the current device is the last node in the end-to-end path
*/
private void createFlowObjectives(CarrierEthernetVirtualConnection evc, CarrierEthernetNetworkInterface srcNi,
CarrierEthernetNetworkInterface dstNi, ConnectPoint ingress, ConnectPoint egress,
boolean first, boolean last) {
/////////////////////////////////////////
// Prepare and submit filtering objective
/////////////////////////////////////////
FilteringObjective.Builder filteringObjectiveBuilder = DefaultFilteringObjective.builder()
.permit().fromApp(appId)
.withPriority(PRIORITY)
.withKey(Criteria.matchInPort(ingress.port()));
TrafficTreatment.Builder filterTreatmentBuilder = DefaultTrafficTreatment.builder();
// In general, nodes would match on the VLAN tag assigned to the EVC/FC
Criterion filterVlanIdCriterion = Criteria.matchVlanId(evc.vlanId());
if (first) {
if ((srcNi instanceof CarrierEthernetInni) || (srcNi instanceof CarrierEthernetEnni) ) {
// TODO: Check TPID? Also: Is is possible to receive untagged pkts at an INNI/ENNI?
// First node of an FC should match on S-TAG if it's an INNI/ENNI
filterVlanIdCriterion = Criteria.matchVlanId(srcNi.sVlanId());
// Translate S-TAG to the one used in the current FC
filterTreatmentBuilder.setVlanId(evc.vlanId());
} else {
// First node of an FC should match on CE-VLAN ID (if present) if it's a UNI
filterVlanIdCriterion = Criteria.matchVlanId(srcNi.ceVlanId());
// Push S-TAG of current FC on top of existing CE-VLAN ID
filterTreatmentBuilder.pushVlan().setVlanId(evc.vlanId());
}
}
filteringObjectiveBuilder.addCondition(filterVlanIdCriterion);
// Do not add meta if there are no instructions (i.e. if not first)
if (first) {
filteringObjectiveBuilder.withMeta(filterTreatmentBuilder.build());
}
flowObjectiveService.filter(ingress.deviceId(), filteringObjectiveBuilder.add());
flowObjectiveMap.get(evc.id()).addFirst(Pair.of(ingress.deviceId(), filteringObjectiveBuilder.add()));
////////////////////////////////////////////////////
// Prepare and submit next and forwarding objectives
////////////////////////////////////////////////////
TrafficSelector fwdSelector = DefaultTrafficSelector.builder()
.matchVlanId(evc.vlanId())
.matchInPort(ingress.port())
.matchEthType(Ethernet.TYPE_IPV4)
.build();
TrafficTreatment.Builder nextTreatmentBuilder = DefaultTrafficTreatment.builder();
// If last NI in FC is not UNI, keep the existing S-TAG - it will be translated at the entrance of the next FC
if (last && (dstNi instanceof CarrierEthernetUni)) {
nextTreatmentBuilder.popVlan();
}
Instruction outInstruction = Instructions.createOutput(egress.port());
nextTreatmentBuilder.add(outInstruction);
// Check if flow rule with same selector already exists (e.g. when branching in an E-LAN or E-Tree).
// If yes, it will be replaced, so we need to include its output in the currently prepared NextObjective
Iterator<FlowRule> flowRuleIt = flowRuleService.getFlowRulesById(appId).iterator();
while (flowRuleIt.hasNext()) {
FlowRule flowRule = flowRuleIt.next();
if (flowRule.deviceId().equals(egress.deviceId()) && flowRule.selector().equals(fwdSelector)) {
Iterator<Instruction> instructionIt = flowRule.treatment().allInstructions().iterator();
while (instructionIt.hasNext()) {
Instruction instruction = instructionIt.next();
// If this is an GROUP instruction and it's different than existing, add it to FlowObjective
if (instruction.type().equals(outInstruction.type()) &&
!(instruction.equals(outInstruction))) {
nextTreatmentBuilder.add(instruction);
}
}
}
}
// Setting higher priority to fwd/next objectives to bypass filter in case of match conflict in OVS switches
Integer nextId = flowObjectiveService.allocateNextId();
NextObjective nextObjective = DefaultNextObjective.builder()
.fromApp(appId)
.makePermanent()
.withType(NextObjective.Type.SIMPLE)
.withPriority(PRIORITY + 1)
.withMeta(fwdSelector)
.addTreatment(nextTreatmentBuilder.build())
.withId(nextId)
.add();
ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
.fromApp(appId)
.makePermanent()
.withFlag(ForwardingObjective.Flag.VERSATILE)
.withPriority(PRIORITY + 1)
.withSelector(fwdSelector)
.nextStep(nextId)
.add();
flowObjectiveService.next(egress.deviceId(), nextObjective);
// Add all NextObjectives at the end of the list so that they will be removed last
flowObjectiveMap.get(evc.id()).addLast(Pair.of(ingress.deviceId(), nextObjective));
flowObjectiveService.forward(egress.deviceId(), forwardingObjective);
flowObjectiveMap.get(evc.id()).addFirst(Pair.of(ingress.deviceId(), forwardingObjective));
// FIXME: For efficiency do not send FlowObjective again if new treatment is exactly the same as existing one
}
@Override
void applyBandwidthProfileResources(CarrierEthernetVirtualConnection evc, CarrierEthernetUni uni) {
Dpid dpid = Dpid.dpid(uni.cp().deviceId().uri());
OpenFlowSwitch sw = controller.getSwitch(dpid);
// Do not apply meters to OFDPA 2.0 switches since they are not currently supported
if (sw.softwareDescription().equals("OF-DPA 2.0")) {
return;
}
// Create meters and add them to global MeterId map
Set<DeviceMeterId> deviceMeterIdSet = deviceMeterIdMap.get(evc.id());
if (deviceMeterIdSet == null) {
deviceMeterIdSet = new HashSet<>();
}
deviceMeterIdSet.addAll(createMeters(uni));
deviceMeterIdMap.put(evc.id(), deviceMeterIdSet);
// Apply meters to already installed flows
Set<FlowRule> newFlowRuleSet = new HashSet<>();
// Get flow rules belonging to service and having as in_port the UNI connect point
// FIXME: Check for flow rules associated with evcId
for (FlowRule flowRule : flowRuleService.getFlowRulesById(appId)) {
PortCriterion portCriterion = (PortCriterion) flowRule.selector().getCriterion(Criterion.Type.IN_PORT);
VlanIdCriterion vlanIdCriterion = (VlanIdCriterion) flowRule.selector()
.getCriterion(Criterion.Type.VLAN_VID);
if (portCriterion == null || vlanIdCriterion == null) {
continue;
}
PortNumber inPort = portCriterion.port();
VlanId flowInVlanId = vlanIdCriterion.vlanId();
if (inPort == null || flowInVlanId == null) {
continue;
}
ConnectPoint flowInCp = new ConnectPoint(flowRule.deviceId(), inPort);
// FIXME: Maybe check also if there is a group action?
if (uni.cp().equals(flowInCp) && evc.vlanId().equals(flowInVlanId)) {
// Need to add to the flow the meters associated with the same device
Set<DeviceMeterId> tmpDeviceMeterIdSet = new HashSet<>();
deviceMeterIdMap.get(evc.id()).forEach(deviceMeterId -> {
if (deviceMeterId.deviceId().equals(flowRule.deviceId())) {
tmpDeviceMeterIdSet.add(deviceMeterId);
}
});
// Modify and submit flow rule only if there are meters to add
if (!tmpDeviceMeterIdSet.isEmpty()) {
FlowRule newFlowRule = addMetersToFlowRule(flowRule, tmpDeviceMeterIdSet);
flowRuleService.applyFlowRules(newFlowRule);
newFlowRuleSet.add(newFlowRule);
} else {
newFlowRuleSet.add(flowRule);
}
} else {
newFlowRuleSet.add(flowRule);
}
}
}
/**
* Creates and submits a meter with the required bands for a UNI.
*
* @param uni the UNI descriptor
* @return set of meter ids of the meters created
*/
private Set<DeviceMeterId> createMeters(CarrierEthernetUni uni) {
// TODO: Check if meter already exists before adding it?
Set<DeviceMeterId> deviceMeterIdSet = new HashSet<>();
long longCir = (long) (uni.bwp().cir().bps() / 8000);
long longEir = (long) (uni.bwp().eir().bps() / 8000);
MeterRequest.Builder meterRequestBuilder;
Meter meter;
Band.Builder bandBuilder;
Set<Band> bandSet = new HashSet<>();
// If EIR is zero do not create the REMARK meter
if (longEir != 0) {
// Mark frames that exceed CIR as Best Effort
bandBuilder = DefaultBand.builder()
.ofType(Band.Type.REMARK)
.withRate(longCir)
.dropPrecedence((short) 0);
if (uni.bwp().cbs() != 0) {
bandBuilder.burstSize(uni.bwp().cbs());
}
bandSet.add(bandBuilder.build());
}
// If CIR is zero do not create the DROP meter
if (longCir != 0) {
// Drop all frames that exceed CIR + EIR
bandBuilder = DefaultBand.builder()
.ofType(Band.Type.DROP)
.withRate(longCir + longEir);
if (uni.bwp().cbs() != 0 || uni.bwp().ebs() != 0) {
// FIXME: Use CBS and EBS correctly according to MEF specs
bandBuilder.burstSize(uni.bwp().cbs() + uni.bwp().ebs());
}
bandSet.add(bandBuilder.build());
}
// Create meter only if at least one band was created
if (!bandSet.isEmpty()) {
meterRequestBuilder = DefaultMeterRequest.builder()
.forDevice(uni.cp().deviceId())
.fromApp(appId)
.withUnit(Meter.Unit.KB_PER_SEC)
.withBands(bandSet);
if (uni.bwp().cbs() != 0 || uni.bwp().ebs() != 0) {
meterRequestBuilder.burst();
}
meter = meterService.submit(meterRequestBuilder.add());
deviceMeterIdSet.add(new DeviceMeterId(uni.cp().deviceId(), meter.id()));
}
return deviceMeterIdSet;
}
private FlowRule addMetersToFlowRule(FlowRule flowRule, Set<DeviceMeterId> deviceMeterIdSet) {
// FIXME: Refactor to use only single meter
TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment
.builder(flowRule.treatment());
deviceMeterIdSet.forEach(deviceMeterId -> {
tBuilder.meter(deviceMeterId.meterId());
});
return createFlowRule(flowRule.deviceId(), flowRule.priority(),
flowRule.selector(), tBuilder.build(), flowRule.tableId());
}
@Override
void removeBandwidthProfileResources(String serviceId, CarrierEthernetUni uni) {
removeMeters(serviceId, uni);
}
/**
* Removes the meters associated with a specific UNI of a service.
*
* @param serviceId the CE service ID
* @param uni the UNI descriptor
* */
private void removeMeters(String serviceId, CarrierEthernetUni uni) {
Set<DeviceMeterId> newDeviceMeterIdSet = deviceMeterIdMap.get(serviceId);
DeviceMeterId tmpDeviceMeterId;
Collection<Meter> meters = meterService.getMeters(uni.cp().deviceId());
Iterator<Meter> it = meters.iterator();
while (it.hasNext()) {
Meter meter = it.next();
tmpDeviceMeterId = new DeviceMeterId(uni.cp().deviceId(), meter.id());
if (meter.appId().equals(appId) &&
deviceMeterIdMap.get(serviceId).contains(tmpDeviceMeterId)) {
MeterRequest.Builder mBuilder;
mBuilder = DefaultMeterRequest.builder()
.fromApp(meter.appId())
.forDevice(meter.deviceId())
.withUnit(meter.unit())
.withBands(meter.bands());
if (uni.bwp().cbs() != 0 || uni.bwp().ebs() != 0) {
mBuilder.burst();
}
meterService.withdraw(mBuilder.remove(), meter.id());
newDeviceMeterIdSet.remove(tmpDeviceMeterId);
}
}
deviceMeterIdMap.put(serviceId, newDeviceMeterIdSet);
}
@Override
void removeAllForwardingResources(CarrierEthernetVirtualConnection evc) {
removeFlowObjectives(evc.id());
}
/**
* Removes all flow objectives installed by the application which are associated with a specific EVC.
*
* @param evcId the EVC id
* */
private void removeFlowObjectives(String evcId) {
// Note: A Flow Rule cannot be shared by multiple services due to different VLAN or CE-VLAN ID match.
List<Pair<DeviceId, Objective>> flowObjectiveList = flowObjectiveMap.remove(evcId);
// NextObjectives will be removed after all other Objectives
ListIterator<Pair<DeviceId, Objective>> objIter = flowObjectiveList.listIterator();
while (objIter.hasNext()) {
Pair<DeviceId, Objective> deviceObjectivePair = objIter.next();
flowObjectiveService.apply(deviceObjectivePair.getLeft(), deviceObjectivePair.getRight().copy().remove());
}
}
// FIXME: Replace with Pair<DeviceId, MeterId>
/**
* Utility class to compensate for the fact that MeterIds are not unique system-wide.
* */
class DeviceMeterId {
private DeviceId deviceId;
private MeterId meterId;
DeviceMeterId(DeviceId deviceId, MeterId meterId) {
this.deviceId = deviceId;
this.meterId = meterId;
}
public DeviceId deviceId() {
return deviceId;
}
public MeterId meterId() {
return meterId;
}
@Override
public int hashCode() {
return Objects.hash(deviceId, meterId);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof DeviceMeterId) {
DeviceMeterId other = (DeviceMeterId) obj;
if (this.deviceId().equals(other.deviceId()) && this.meterId().equals(other.meterId())) {
return true;
}
}
return false;
}
}
private FlowRule createFlowRule(DeviceId deviceId, int priority,
TrafficSelector selector, TrafficTreatment treatment, int tableId) {
return DefaultFlowRule.builder()
.fromApp(appId)
.forDevice(deviceId)
.makePermanent()
.withPriority(priority)
.withSelector(selector)
.withTreatment(treatment)
.forTable(tableId)
.build();
}
}