Initial development of Carrier Ethernet E-CORD

Change-Id: I57ca86dc1dd6a19636f3778d6f5030df33169144
diff --git a/local/ce-vee/pom.xml b/local/ce-vee/pom.xml
new file mode 100644
index 0000000..8a416d4
--- /dev/null
+++ b/local/ce-vee/pom.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2015-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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.opencord.ce</groupId>
+        <artifactId>local</artifactId>
+    	<version>1.0.0</version>
+    </parent>
+
+    <artifactId>vee</artifactId>
+    <packaging>bundle</packaging>
+
+    <description>Virtual ethernet edge service for CORD</description>
+
+    <properties>
+        <onos.app.name>org.opencord.ce.local.vee</onos.app.name>
+        <onos.app.title>ECORD-CE App</onos.app.title>
+        <onos.app.url>http://opencord.org</onos.app.url>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.opencord.ce</groupId>
+            <artifactId>bigswitch</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.onosproject</groupId>
+                <artifactId>onos-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+    <repositories>
+        <repository>
+            <id>snapshots</id>
+            <url>https://oss.sonatype.org/content/repositories/snapshots</url>
+            <releases><enabled>false</enabled></releases>
+        </repository>
+    </repositories>
+
+    <pluginRepositories>
+        <pluginRepository>
+            <id>snapshots</id>
+            <url>https://oss.sonatype.org/content/repositories/snapshots</url>
+            <releases><enabled>false</enabled></releases>
+        </pluginRepository>
+    </pluginRepositories>
+
+</project>
diff --git a/local/ce-vee/src/main/java/org/opencord/ce/local/vee/VeeManager.java b/local/ce-vee/src/main/java/org/opencord/ce/local/vee/VeeManager.java
new file mode 100644
index 0000000..daf5614
--- /dev/null
+++ b/local/ce-vee/src/main/java/org/opencord/ce/local/vee/VeeManager.java
@@ -0,0 +1,730 @@
+/*
+ * Copyright 2017-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.opencord.ce.local.vee;
+
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+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.apache.felix.scr.annotations.Service;
+import org.onlab.packet.EthType;
+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.Link;
+import org.onosproject.net.Path;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.driver.Driver;
+import org.onosproject.net.driver.DriverService;
+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.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.flow.instructions.L2ModificationInstruction;
+import org.onosproject.net.flowobjective.DefaultFilteringObjective;
+import org.onosproject.net.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.DefaultNextObjective;
+import org.onosproject.net.flowobjective.FilteringObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.NextObjective;
+import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.net.meter.Band;
+import org.onosproject.net.meter.DefaultBand;
+import org.onosproject.net.meter.DefaultMeterRequest;
+import org.onosproject.net.meter.Meter;
+import org.onosproject.net.meter.MeterId;
+import org.onosproject.net.meter.MeterRequest;
+import org.onosproject.net.meter.MeterService;
+import org.onosproject.net.topology.PathService;
+import org.opencord.ce.api.models.CarrierEthernetEnni;
+import org.opencord.ce.api.models.CarrierEthernetForwardingConstruct;
+import org.opencord.ce.api.models.CarrierEthernetGenericNi;
+import org.opencord.ce.api.models.CarrierEthernetInni;
+import org.opencord.ce.api.models.CarrierEthernetNetworkInterface;
+import org.opencord.ce.api.models.CarrierEthernetUni;
+import org.opencord.ce.api.models.EvcConnId;
+import org.opencord.ce.api.services.MetroNetworkVirtualNodeService;
+
+import org.opencord.ce.local.bigswitch.BigSwitchService;
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import static org.slf4j.LoggerFactory.getLogger;
+import static org.opencord.ce.api.models.CarrierEthernetNetworkInterface.Type;
+import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
+
+/**
+ * Class used to control Ethernet Edge nodes according to the OpenFlow (1.3 and above) protocol.
+ */
+@Component(immediate = true)
+@Service(value = MetroNetworkVirtualNodeService.class)
+public class VeeManager implements MetroNetworkVirtualNodeService {
+    private static final int PRIORITY = 50000;
+    public static final String APP_NAME = "org.opencord.ce.local.vee";
+
+    @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 LinkService linkService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected BigSwitchService bigSwitchService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PathService pathService;
+
+    // FIXME slightly better way to detect OF-DPA issues
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DriverService drivers;
+
+    private final Logger log = getLogger(getClass());
+
+    private ApplicationId appId;
+
+    // FIXME: We don't need to store the submitted meters as we can get them from the service
+    private Map<EvcConnId, Set<Pair<DeviceId, MeterId>>> fcMeterMap =
+            new ConcurrentHashMap<>();
+    // Store submitted flow objects as the service does not allow query operations
+    private final Map<EvcConnId, LinkedList<Pair<DeviceId, Objective>>> flowObjectiveMap =
+            new ConcurrentHashMap<>();
+
+    private final Map<EvcConnId, DeviceId> eeDeviceMap = new ConcurrentHashMap<>();
+
+    @Activate
+    protected void activate() {
+        appId = coreService.registerApplication(APP_NAME);
+
+        // TODO: start packet intercept for untagged traffic?
+        log.info("Started");
+
+       // testCentec();
+    }
+
+    @Deactivate
+    protected void deactivate() {
+
+        log.info("Stopped");
+    }
+
+
+    @Override
+    public void setNodeForwarding(CarrierEthernetForwardingConstruct fc, CarrierEthernetNetworkInterface ingressNi,
+                                  Set<CarrierEthernetNetworkInterface> egressNiSet) {
+
+        log.info("DEBUG: setForwarding method called..");
+        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<>());
+
+        CarrierEthernetNetworkInterface realIngressNi;
+        Optional<ConnectPoint> optCp =
+                bigSwitchService.connectPointFromVirtPort(ingressNi.cp().port());
+        if (optCp.isPresent()) {
+            realIngressNi = buildLocalNi(optCp.get(), ingressNi);
+        } else {
+            log.warn("Virtual ingress interface does not map to a local connect point");
+            return;
+        }
+
+        // real <-> virtual
+        final Map<ConnectPoint, CarrierEthernetNetworkInterface> realEgressNi = new HashMap<>();
+        egressNiSet.forEach(egressNi -> {
+            Optional<ConnectPoint> opt =
+                    bigSwitchService.connectPointFromVirtPort(egressNi.cp().port());
+            opt.ifPresent(connectPoint -> realEgressNi.put(connectPoint, egressNi));
+        });
+
+        // it is necessary to identify the number of egress devices in order to build the
+        // flow rules
+        Set<DeviceId> egressDevices = new HashSet<>();
+        realEgressNi.keySet().forEach(cp -> egressDevices.add(cp.deviceId()));
+
+        //for how it is made the CE app, egressNiSet contains only one item, but...
+        egressDevices.forEach(deviceId -> {
+            // group the egress NI per device
+            Set<CarrierEthernetNetworkInterface> perDeviceEgressNiSet = new HashSet<>();
+            realEgressNi.keySet().forEach(cp -> {
+                if (cp.deviceId().equals(deviceId)) {
+                    perDeviceEgressNiSet.add(buildLocalNi(cp, realEgressNi.get(cp)));
+                }
+            });
+            // if the egress device ID is equal to the ingress --> install rules
+            if (deviceId.equals(realIngressNi.cp().deviceId())) {
+                createFlowObjectives(fc, realIngressNi,
+                        perDeviceEgressNiSet);
+            } else {
+                Set<Path> paths = pathService.getPaths(realIngressNi.cp().deviceId(),
+                        deviceId);
+                Path path;
+                // TODO: Select path in more sophisticated way and return null if any of the constraints cannot be met
+                path = paths.iterator().hasNext() ? paths.iterator().next() : null;
+                if (path == null) {
+                    return;
+                }
+                // for each NI, add edge links ()
+                perDeviceEgressNiSet.forEach(egressNi -> {
+                    List<Link> links = new ArrayList<>();
+                    links.add(createEdgeLink(realIngressNi.cp(), true));
+                    links.addAll(path.links());
+                    links.add(createEdgeLink(egressNi.cp(), false));
+
+                    HashMap<CarrierEthernetNetworkInterface, HashSet<CarrierEthernetNetworkInterface>>
+                            ingressEgressNiMap = new HashMap<>();
+                    populateIngressEgressNiMap(realIngressNi, egressNi, links, ingressEgressNiMap);
+                    // Establish connectivity using the ingressEgressNiMap
+                    ingressEgressNiMap.keySet().forEach(srcNi ->
+                        createFlowObjectives(fc, srcNi, ingressEgressNiMap.get(srcNi)));
+                });
+            }
+        });
+    }
+
+    private void populateIngressEgressNiMap(CarrierEthernetNetworkInterface srcNi,
+                                            CarrierEthernetNetworkInterface dstNi,
+                                            List<Link> linkList,
+                                            HashMap<CarrierEthernetNetworkInterface,
+                                                    HashSet<CarrierEthernetNetworkInterface>> ingressEgressNiMap
+    ) {
+        // FIXME: Fix the method - avoid generating GENERIC NIs if not needed
+        // Add the src and destination NIs as well as the associated Generic NIs
+        ingressEgressNiMap.putIfAbsent(srcNi, new HashSet<>());
+        // Add last hop entry only if srcNi, dstNi aren't on same device (in which case srcNi, ingressNi would coincide)
+        if (!srcNi.cp().deviceId().equals(dstNi.cp().deviceId())) {
+            // If srcNi, dstNi are not on the same device, create mappings to/from new GENERIC NIs
+            ingressEgressNiMap.get(srcNi).add(new CarrierEthernetGenericNi(linkList.get(1).src(), null));
+            CarrierEthernetGenericNi ingressNi =
+                    new CarrierEthernetGenericNi(linkList.get(linkList.size() - 2).dst(), null);
+            ingressEgressNiMap.putIfAbsent(ingressNi, new HashSet<>());
+            ingressEgressNiMap.get(ingressNi).add(dstNi);
+        } else {
+            // If srcNi, dstNi are on the same device, this is the only mapping that will be created
+            ingressEgressNiMap.get(srcNi).add(dstNi);
+        }
+
+        // Go through the links and create/add the intermediate NIs
+        for (int i = 1; i < linkList.size() - 2; i++) {
+            CarrierEthernetGenericNi ingressNi = new CarrierEthernetGenericNi(linkList.get(i).dst(), null);
+            ingressEgressNiMap.putIfAbsent(ingressNi, new HashSet<>());
+            ingressEgressNiMap.get(ingressNi).add(new CarrierEthernetGenericNi(linkList.get(i + 1).src(), null));
+        }
+    }
+
+    private CarrierEthernetNetworkInterface buildLocalNi(ConnectPoint localCp, CarrierEthernetNetworkInterface
+                                                         virtualNi) {
+        CarrierEthernetNetworkInterface ni;
+        switch (virtualNi.type()) {
+            case UNI:
+                CarrierEthernetUni tmpUni = (CarrierEthernetUni) virtualNi;
+                ni = CarrierEthernetUni.builder()
+                        .cp(localCp)
+                        .role(tmpUni.role())
+                        .ceVlanId(tmpUni.ceVlanId())
+                        .bwp(tmpUni.bwp())
+                        .build();
+                break;
+            case ENNI:
+                CarrierEthernetEnni tmpEnni = (CarrierEthernetEnni) virtualNi;
+                ni = CarrierEthernetEnni.builder()
+                        .cp(localCp)
+                        .role(tmpEnni.role())
+                        .sVlanId(tmpEnni.sVlanId())
+                        .build();
+                break;
+            case INNI:
+                CarrierEthernetInni tmpInni = (CarrierEthernetInni) virtualNi;
+                ni = CarrierEthernetInni.builder()
+                        .cp(localCp)
+                        .role(tmpInni.role())
+                        .sVlanId(tmpInni.sVlanId())
+                        .build();
+                break;
+            case GENERIC:
+                ni = new CarrierEthernetGenericNi(localCp, null);
+                break;
+            default:
+                return null;
+        }
+        return ni;
+
+    }
+
+    /**
+     * Creates and submits FlowObjectives into the device vesting the role of Ethernet Edge.
+     *
+     * @param fc the FC representation
+     * @param ingressNi the ingress network interface
+     * @param  egressNiSet the set of egress NIs
+     */
+    private void createFlowObjectives(CarrierEthernetForwardingConstruct fc, CarrierEthernetNetworkInterface ingressNi,
+                                      Set<CarrierEthernetNetworkInterface> egressNiSet) {
+        DeviceId deviceId = ingressNi.cp().deviceId();
+        PortNumber portNumber = ingressNi.cp().port();
+
+        /////////////////////////////////////////
+        // Prepare and submit filtering objective
+        /////////////////////////////////////////
+
+        FilteringObjective.Builder filterObjBuilder = DefaultFilteringObjective.builder()
+                .permit().fromApp(appId)
+                .withPriority(PRIORITY)
+                .withKey(Criteria.matchInPort(portNumber));
+
+        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
+            if (fcMeterMap.get(fc.id()) != null && !fcMeterMap.get(fc.id()).isEmpty()) {
+                fcMeterMap.get(fc.id()).forEach(devMeterPair -> {
+                    if (devMeterPair.getLeft().equals(deviceId)) {
+                        filterTreatmentBuilder.meter(devMeterPair.getRight());
+                    }
+                });
+            }
+            // If a CE-VLAN-ID exists on the incoming packet then push an S-TAG of current FC on top
+            // otherwise push it on as a C-tag
+            if (ingressNi.ceVlanId() != null && ingressNi.ceVlanId() != VlanId.NONE) {
+                filterTreatmentBuilder.pushVlan(EthType.EtherType.QINQ.ethType()).setVlanId(fc.vlanId());
+            } else {
+                filterTreatmentBuilder.pushVlan().setVlanId(fc.vlanId());
+            }
+        }
+
+        filterObjBuilder.addCondition(filterVlanIdCriterion);
+
+
+
+        // Do not add meta if there are no instructions (i.e. if not first)
+        if (!(ingressNi.type().equals(Type.GENERIC))) {
+            filterObjBuilder.withMeta(filterTreatmentBuilder.build());
+        }
+
+        flowObjectiveService.filter(deviceId, filterObjBuilder.add());
+        flowObjectiveMap.get(fc.id()).addFirst(Pair.of(deviceId, filterObjBuilder.add()));
+
+        ////////////////////////////////////////////////////
+        // Prepare and submit next and forwarding objectives
+        ////////////////////////////////////////////////////
+
+        TrafficSelector.Builder fwdSelectorBuilder = DefaultTrafficSelector.builder()
+                .matchVlanId(fc.vlanId())
+                .matchInPort(portNumber);
+
+        if (isOfDpa(deviceId)) {
+            // workaround for OF-DPA
+            fwdSelectorBuilder.matchEthType(Ethernet.TYPE_IPV4);
+        }
+
+        TrafficSelector fwdSelector = fwdSelectorBuilder.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) {
+        log.info("Creating BW profile...{}", uni.toString());
+        // Create meters and add them to global MeterId map
+        Set<Pair<DeviceId, MeterId>> meters;
+
+        DeviceId deviceId = getEEDevice(fc, uni);
+
+        if (fcMeterMap.containsKey(fc.id())) {
+            meters = fcMeterMap.get(fc.id());
+        } else {
+            meters = new HashSet<>();
+            if (deviceId == null) {
+                // no meters
+                return;
+            }
+        }
+        meters.addAll(submitMeters(fc, uni, deviceId));
+        fcMeterMap.put(fc.id(), meters);
+    }
+
+    private boolean isOfDpa(DeviceId deviceId) {
+        Driver driver = drivers.getDriver(deviceId);
+        return driver != null &&
+                driver.swVersion().contains("OF-DPA");
+    }
+
+    // Maybe we still need this method to later modify the QoS profile of an FC
+    @Override
+    public void applyBandwidthProfileResources(CarrierEthernetForwardingConstruct fc, CarrierEthernetUni uni) {
+        log.info("Applying BW profile...");
+
+        DeviceId deviceId = eeDeviceMap.get(fc.id());
+        if (deviceId == null) {
+            log.warn("Trying to apply bandwidth profile rules" +
+                    "to not existing device/meters map");
+            return;
+        }
+
+        // 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;
+        }
+
+        // Do not apply meters to OFDPA 2.0 switches since they are not currently supported
+        if (isOfDpa(deviceId)) {
+            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())
+                        .collect(Collectors.toList());
+                                //&& getPushedVlanFromTreatment(flowRule.treatment()).equals(fc.vlanId()))
+
+
+        // Apply meters to flows
+        for (FlowRule flowRule : flowRuleList) {
+            // Need to add to the flow the meters associated with the same device
+            Set<Pair<DeviceId, MeterId>> deviceMeterIdSet = new HashSet<>();
+
+            if (fcMeterMap.get(fc.id()) != null && !fcMeterMap.get(fc.id()).isEmpty()) {
+                fcMeterMap.get(fc.id()).forEach(devMeterPair -> {
+                    if (devMeterPair.getLeft().equals(flowRule.deviceId())) {
+                        deviceMeterIdSet.add(devMeterPair);
+                    }
+                });
+            }
+            // Modify and submit flow rule only if there are meters to add
+            if (!deviceMeterIdSet.isEmpty()) {
+                log.info("Applying metered flow rules...");
+                FlowRule newFlowRule = addMetersToFlowRule(flowRule, deviceMeterIdSet);
+                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;
+    }
+
+
+    /**
+     * 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<Pair<DeviceId, MeterId>> submitMeters(CarrierEthernetForwardingConstruct fc,
+                                                      CarrierEthernetUni uni, DeviceId deviceId) {
+        Set<Pair<DeviceId, MeterId>> meters = new HashSet<>();
+
+        uni.bwps().forEach(bwp -> {
+            log.info("bwp: {}", bwp.toString());
+
+            // KB_PER_SECOND
+            long longCir = (long) (bwp.cir().bps() / 8000);
+            long longEir = (long) (bwp.eir().bps() / 8000);
+            log.info("longCir: {}", longCir);
+            log.info("longEir: {}", longEir);
+
+            MeterRequest.Builder meterRequestBuilder;
+            Meter meter;
+            Band.Builder bandBuilder;
+
+            Set<Band> bandSet = new HashSet<>();
+
+            // If EIR is zero do not create the REMARK meter
+            /* === Centec v350 supports only DROP type! ===
+            if (longEir != 0) {
+                log.info("Enter here..");
+                // Mark frames that exceed CIR as Best Effort
+                bandBuilder = DefaultBand.builder()
+                        .ofType(Band.Type.REMARK)
+                        .withRate(longCir)
+                        .dropPrecedence((short) 0);
+
+                if (bwp.cbs() != 0) {
+                    bandBuilder.burstSize(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 (bwp.cbs() != 0 || bwp.ebs() != 0) {
+                    // FIXME: Use CBS and EBS correctly according to MEF specs
+                    bandBuilder.burstSize(bwp.cbs() + bwp.ebs());
+                }
+
+                bandSet.add(bandBuilder.build());
+            }
+
+            // Create meter only if at least one band was created
+            if (!bandSet.isEmpty()) {
+                meterRequestBuilder = DefaultMeterRequest.builder()
+                        .forDevice(deviceId)
+                        .fromApp(appId)
+                        .withUnit(Meter.Unit.KB_PER_SEC)
+                        .withBands(bandSet);
+
+                if (bwp.cbs() != 0 || bwp.ebs() != 0) {
+                    meterRequestBuilder.burst();
+                }
+
+                // Submit meter request and store
+                meter = meterService.submit(meterRequestBuilder.add());
+                // FIXME: use correct device id
+                log.info("Adding meter...{}", meter.toString());
+
+                meters.add(new ImmutablePair<>(deviceId, meter.id()));
+            }
+        });
+
+        return meters;
+    }
+
+    private FlowRule addMetersToFlowRule(FlowRule flowRule,  Set<Pair<DeviceId, MeterId>> deviceMeterIdSet) {
+
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment
+                .builder(flowRule.treatment());
+
+        deviceMeterIdSet.forEach(deviceMeterPair ->
+            tBuilder.meter(deviceMeterPair.getRight()));
+
+        return createFlowRule(flowRule.deviceId(), flowRule.priority(),
+                flowRule.selector(), tBuilder.build(), flowRule.tableId());
+    }
+
+    @Override
+    public void removeBandwidthProfileResources(CarrierEthernetForwardingConstruct fc, CarrierEthernetUni uni) {
+        removeMeters(fc, uni);
+    }
+
+    /**
+     * Removes the meters associated with a specific UNI of an FC.
+     *
+     * @param fc the forwarding construct
+     * @param uni the UNI descriptor
+     * */
+    private void removeMeters(CarrierEthernetForwardingConstruct fc, CarrierEthernetUni uni) {
+
+        if (!fcMeterMap.containsKey(fc.id())) {
+            return;
+        }
+
+        Set<Pair<DeviceId, MeterId>> ids = fcMeterMap.get(fc.id());
+
+        // Rebuild meter request based on existing meter, withdraw the request, and remove it from our internal storage
+        ids.stream()
+                .map(id -> meterService.getMeter(id.getLeft(), id.getRight()))
+                .filter(Objects::nonNull)
+                .forEach(meter -> {
+                    MeterRequest.Builder request = DefaultMeterRequest.builder()
+                            .fromApp(meter.appId())
+                            .forDevice(meter.deviceId())
+                            .withUnit(meter.unit())
+                            .withBands(meter.bands());
+                    if (uni.bwp().cbs() != 0 || uni.bwp().ebs() != 0) {
+                        request.burst();
+                    }
+                    meterService.withdraw(request.remove(), meter.id());
+
+                    ids.remove(new ImmutablePair<>(meter.deviceId(), meter.id()));
+                });
+    }
+
+    /**
+     * Removes all flow objectives installed by the application which are associated with a specific FC.
+     *
+     * @param fcId ForwardingConcrtuct object
+     */
+    @Override
+    public void removeAllForwardingResources(EvcConnId fcId) {
+        // Note: A Flow Rule cannot be shared by multiple FCs due to different VLAN or CE-VLAN ID match.
+        List<Pair<DeviceId, Objective>> flowObjectives = flowObjectiveMap.remove(fcId);
+        // NextObjectives will be removed after all other Objectives
+        // TODO: filter also on elements of pair
+        flowObjectives.stream()
+                .filter(Objects::nonNull)
+                .forEach(pair ->
+                        flowObjectiveService.apply(pair.getLeft(), pair.getRight().copy().remove()));
+    }
+
+    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();
+    }
+
+    /**
+     * !! Caution !! This method is strictly tied to the expected topology
+     * view of this {@link MetroNetworkVirtualNodeService} implementation.
+     * The UNI is expected to be in the CPE and meters implementation in the EE.
+     * The CPE node shall be connected only
+     * with the EE device (exception are multi path home gateway).
+     *
+     * @param fc forwarding construct
+     * @param uni User to Network Interface
+     * @return the EE device ID
+     */
+    private DeviceId getEEDevice(CarrierEthernetForwardingConstruct fc, CarrierEthernetUni uni)
+            throws IllegalStateException {
+        CarrierEthernetNetworkInterface realUni;
+        Optional<ConnectPoint> optCp =
+                bigSwitchService.connectPointFromVirtPort(uni.cp().port());
+        if (optCp.isPresent()) {
+            realUni = buildLocalNi(optCp.get(), uni);
+        } else {
+            log.info("Virtual ingress interface does not map to a local connect point");
+            throw new IllegalStateException("Virtual UNI interface does not map to a local connect point");
+        }
+        if (eeDeviceMap.get(fc.id()) != null) {
+            return eeDeviceMap.get(fc.id());
+        }
+        // find the (only) egress link of the UNI device
+        Optional<Link> optLink = linkService.getDeviceEgressLinks(realUni.cp().deviceId())
+                .stream().findFirst();
+        if (optLink.isPresent()) {
+
+            // the link destination should be our target EE device
+            DeviceId deviceId = optLink.get().dst().deviceId();
+            log.info("Adding EE device {} in memory...", deviceId.toString());
+            eeDeviceMap.put(fc.id(), deviceId);
+            return deviceId;
+        } else {
+            log.info("No EE device found for meters...uni: {}", realUni.cp().toString());
+            // in this case there is no EE upstream and so no meter will be installed
+            return null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/local/ce-vee/src/main/java/org/opencord/ce/local/vee/package-info.java b/local/ce-vee/src/main/java/org/opencord/ce/local/vee/package-info.java
new file mode 100644
index 0000000..184f05a
--- /dev/null
+++ b/local/ce-vee/src/main/java/org/opencord/ce/local/vee/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-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.
+ */
+
+/**
+ * Virtual Ethernet Edge implementation.
+ */
+package org.opencord.ce.local.vee;
\ No newline at end of file