org.onosproject.ecord.carrierethernet.app.CarrierEthernetPacketNodeManager.java Source code

Java tutorial

Introduction

Here is the source code for org.onosproject.ecord.carrierethernet.app.CarrierEthernetPacketNodeManager.java

Source

/*
 * 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.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.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.instructions.Instruction;
import org.onosproject.net.flow.instructions.Instructions;
import org.onosproject.net.flow.instructions.L2ModificationInstruction;
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 java.util.stream.Collectors;
import java.util.stream.StreamSupport;

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 = CarrierEthernetPacketNodeService.class)
public class CarrierEthernetPacketNodeManager implements CarrierEthernetPacketNodeService {

    @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;

    // FIXME App directly depending on SB details is probably not correct
    @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(CarrierEthernetForwardingConstruct fc, CarrierEthernetNetworkInterface ingressNi,
            Set<CarrierEthernetNetworkInterface> egressNiSet) {

        if (ingressNi == null || egressNiSet.isEmpty()) {
            log.error("There needs to be at least one ingress and one egress NI to set forwarding.");
            return;
        }

        flowObjectiveMap.putIfAbsent(fc.id(), new LinkedList<>());

        // TODO: Get created FlowObjectives from this method
        createFlowObjectives(fc, ingressNi, egressNiSet);
    }

    /**
     * Creates and submits FlowObjectives depending on role of the device in the FC and ingress/egress NI types.
     *
     * @param fc the FC representation
     * @param ingressNi the ingress NI (UNI, INNI, ENNI or GENERIC) of the EVC for this forwarding segment
     * @param  egressNiSet the set of egress NIs (UNI, INNI, ENNI or GENERIC) of the EVC for this forwarding segment
     */
    private void createFlowObjectives(CarrierEthernetForwardingConstruct fc,
            CarrierEthernetNetworkInterface ingressNi, Set<CarrierEthernetNetworkInterface> egressNiSet) {

        /////////////////////////////////////////
        // Prepare and submit filtering objective
        /////////////////////////////////////////

        FilteringObjective.Builder filteringObjectiveBuilder = DefaultFilteringObjective.builder().permit()
                .fromApp(appId).withPriority(PRIORITY).withKey(Criteria.matchInPort(ingressNi.cp().port()));

        TrafficTreatment.Builder filterTreatmentBuilder = DefaultTrafficTreatment.builder();

        // In general, nodes would match on the VLAN tag assigned to the EVC/FC
        Criterion filterVlanIdCriterion = Criteria.matchVlanId(fc.vlanId());

        if ((ingressNi.type().equals(CarrierEthernetNetworkInterface.Type.INNI))
                || (ingressNi.type().equals(CarrierEthernetNetworkInterface.Type.ENNI))) {
            // TODO: Check TPID? Also: Is is possible to receive untagged pkts at an INNI/ENNI?
            // Source node of an FC should match on S-TAG if it's an INNI/ENNI
            filterVlanIdCriterion = Criteria.matchVlanId(ingressNi.sVlanId());
            // Translate S-TAG to the one used in the current FC
            filterTreatmentBuilder.setVlanId(fc.vlanId());
        } else if (ingressNi.type().equals(CarrierEthernetNetworkInterface.Type.UNI)) {
            // Source node of an FC should match on CE-VLAN ID (if present) if it's a UNI
            filterVlanIdCriterion = Criteria.matchVlanId(ingressNi.ceVlanId());
            // Obtain related Meter (if it exists) and add it in the treatment in case it may be used
            deviceMeterIdMap.get(fc.id()).forEach(deviceMeterId -> {
                if (deviceMeterId.deviceId().equals(ingressNi.cp().deviceId())) {
                    filterTreatmentBuilder.meter(deviceMeterId.meterId());
                }
            });
            // Push S-TAG of current FC on top of existing CE-VLAN ID
            filterTreatmentBuilder.pushVlan().setVlanId(fc.vlanId());
        }

        filteringObjectiveBuilder.addCondition(filterVlanIdCriterion);

        // Do not add meta if there are no instructions (i.e. if not first)
        if (!(ingressNi.type().equals(CarrierEthernetNetworkInterface.Type.GENERIC))) {
            filteringObjectiveBuilder.withMeta(filterTreatmentBuilder.build());
        }

        flowObjectiveService.filter(ingressNi.cp().deviceId(), filteringObjectiveBuilder.add());
        flowObjectiveMap.get(fc.id()).addFirst(Pair.of(ingressNi.cp().deviceId(), filteringObjectiveBuilder.add()));

        ////////////////////////////////////////////////////
        // Prepare and submit next and forwarding objectives
        ////////////////////////////////////////////////////

        TrafficSelector fwdSelector = DefaultTrafficSelector.builder().matchVlanId(fc.vlanId())
                .matchInPort(ingressNi.cp().port()).matchEthType(Ethernet.TYPE_IPV4).build();

        Integer nextId = flowObjectiveService.allocateNextId();

        NextObjective.Type nextType = egressNiSet.size() == 1 ? NextObjective.Type.SIMPLE
                : NextObjective.Type.BROADCAST;

        // Setting higher priority to fwd/next objectives to bypass filter in case of match conflict in OVS switches
        NextObjective.Builder nextObjectiveBuider = DefaultNextObjective.builder().fromApp(appId).makePermanent()
                .withType(nextType).withPriority(PRIORITY + 1).withMeta(fwdSelector).withId(nextId);

        egressNiSet.forEach(egressNi -> {
            // TODO: Check if ingressNi and egressNi are on the same device?
            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 (egressNi.type().equals(CarrierEthernetNetworkInterface.Type.UNI)) {
                nextTreatmentBuilder.popVlan();
            }
            Instruction outInstruction = Instructions.createOutput(egressNi.cp().port());
            nextTreatmentBuilder.add(outInstruction);
            nextObjectiveBuider.addTreatment(nextTreatmentBuilder.build());
        });

        NextObjective nextObjective = nextObjectiveBuider.add();

        // Setting higher priority to fwd/next objectives to bypass filter in case of match conflict in OVS switches
        ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder().fromApp(appId)
                .makePermanent().withFlag(ForwardingObjective.Flag.VERSATILE).withPriority(PRIORITY + 1)
                .withSelector(fwdSelector).nextStep(nextId).add();

        flowObjectiveService.next(ingressNi.cp().deviceId(), nextObjective);
        // Add all NextObjectives at the end of the list so that they will be removed last
        flowObjectiveMap.get(fc.id()).addLast(Pair.of(ingressNi.cp().deviceId(), nextObjective));

        flowObjectiveService.forward(ingressNi.cp().deviceId(), forwardingObjective);
        flowObjectiveMap.get(fc.id()).addFirst(Pair.of(ingressNi.cp().deviceId(), forwardingObjective));
    }

    @Override
    public void createBandwidthProfileResources(CarrierEthernetForwardingConstruct fc, CarrierEthernetUni uni) {
        // Create meters and add them to global MeterId map
        Set<DeviceMeterId> deviceMeterIdSet = deviceMeterIdMap.get(fc.id());
        if (deviceMeterIdSet == null) {
            deviceMeterIdSet = new HashSet<>();
        }
        deviceMeterIdSet.addAll(createMeters(uni));
        deviceMeterIdMap.put(fc.id(), deviceMeterIdSet);
    }

    @Override
    public void applyBandwidthProfileResources(CarrierEthernetForwardingConstruct fc, CarrierEthernetUni uni) {

        DeviceId deviceId = uni.cp().deviceId();

        // Do not apply meters to NETCONF-controlled switches here since they should have been applied in the pipeline
        // FIXME: Is there a better way to check this?
        if (deviceId.uri().getScheme().equals("netconf")) {
            return;
        }

        Dpid dpid = Dpid.dpid(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;
        }

        // Get installed flows with the same appId/deviceId with IN_PORT = UNI port which push the FC vlanId
        List<FlowRule> flowRuleList = StreamSupport
                .stream(flowRuleService.getFlowEntries(deviceId).spliterator(), false)
                .filter(flowRule -> flowRule.appId() == appId.id()
                        && getPushedVlanFromTreatment(flowRule.treatment()).equals(fc.vlanId())
                        && getInPortNumberFromSelector(flowRule.selector()).equals(uni.cp().port()))
                .collect(Collectors.toList());

        // Apply meters to flows
        for (FlowRule flowRule : flowRuleList) {
            // Need to add to the flow the meters associated with the same device
            Set<DeviceMeterId> tmpDeviceMeterIdSet = new HashSet<>();
            deviceMeterIdMap.get(fc.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);
            }
        }
    }

    private VlanId getPushedVlanFromTreatment(TrafficTreatment treatment) {
        boolean pushVlan = false;
        VlanId pushedVlan = null;
        for (Instruction instruction : treatment.allInstructions()) {
            if (instruction.type().equals(Instruction.Type.L2MODIFICATION)) {
                L2ModificationInstruction l2ModInstr = (L2ModificationInstruction) instruction;
                if (l2ModInstr.subtype().equals(L2ModificationInstruction.L2SubType.VLAN_PUSH)) {
                    pushVlan = true;
                } else if (l2ModInstr.subtype().equals(L2ModificationInstruction.L2SubType.VLAN_ID) && pushVlan) {
                    pushedVlan = ((L2ModificationInstruction.ModVlanIdInstruction) instruction).vlanId();
                }
            }
        }
        return pushedVlan != null ? pushedVlan : VlanId.NONE;
    }

    private PortNumber getInPortNumberFromSelector(TrafficSelector selector) {
        for (Criterion criterion : selector.criteria()) {
            if (criterion.type().equals(Criterion.Type.IN_PORT)) {
                return ((PortCriterion) criterion).port();
            }
        }
        return PortNumber.portNumber("-1");
    }

    /**
     * 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()).transition(flowRule.treatment().tableTransition().tableId());
        });

        return createFlowRule(flowRule.deviceId(), flowRule.priority(), flowRule.selector(), tBuilder.build(),
                flowRule.tableId());
    }

    @Override
    public void removeBandwidthProfileResources(String fcId, CarrierEthernetUni uni) {
        removeMeters(fcId, uni);
    }

    /**
     * Removes the meters associated with a specific UNI of an FC.
     *
     * @param fcId the EVC ID
     * @param uni the UNI descriptor
     * */
    private void removeMeters(String fcId, CarrierEthernetUni uni) {

        Set<DeviceMeterId> newDeviceMeterIdSet = deviceMeterIdMap.get(fcId);
        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(fcId).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(fcId, newDeviceMeterIdSet);
    }

    @Override
    public void removeAllForwardingResources(CarrierEthernetForwardingConstruct fc) {
        removeFlowObjectives(fc.id());
    }

    /**
     * Removes all flow objectives installed by the application which are associated with a specific FC.
     *
     * @param fcId the FC id
     * */
    private void removeFlowObjectives(String fcId) {
        // Note: A Flow Rule cannot be shared by multiple FCs due to different VLAN or CE-VLAN ID match.
        List<Pair<DeviceId, Objective>> flowObjectiveList = flowObjectiveMap.remove(fcId);
        // 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();
    }

}