SEBA-815 Multi Tcont support by OLT app.

Change-Id: I024ef2fcb3d3e59cc86bd2088726ae513fcff796
diff --git a/api/pom.xml b/api/pom.xml
index 5915e72..30f6a3a 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -33,7 +33,7 @@
 
     <description>OLT application API</description>
     <properties>
-        <sadis.api.version>3.1.0</sadis.api.version>
+        <sadis.api.version>3.1.1-SNAPSHOT</sadis.api.version>
     </properties>
 
     <dependencies>
diff --git a/api/src/main/java/org/opencord/olt/AccessDeviceEvent.java b/api/src/main/java/org/opencord/olt/AccessDeviceEvent.java
index 3c189dd..3a4146c 100644
--- a/api/src/main/java/org/opencord/olt/AccessDeviceEvent.java
+++ b/api/src/main/java/org/opencord/olt/AccessDeviceEvent.java
@@ -29,21 +29,12 @@
 
     private final Optional<VlanId> sVlan;
     private final Optional<VlanId> cVlan;
+    private final Optional<Integer> tpId;
 
     private final Optional<Port> port;
 
     public enum Type {
         /**
-         * A subscriber was registered and provisioned.
-         */
-        SUBSCRIBER_REGISTERED,
-
-        /**
-         * A subscriber was unregistered and deprovisioned.
-         */
-        SUBSCRIBER_UNREGISTERED,
-
-        /**
          * An access device connected.
          */
         DEVICE_CONNECTED,
@@ -61,55 +52,87 @@
         /**
          * An existing UNI port was removed.
          */
-        UNI_REMOVED
+        UNI_REMOVED,
+
+        /**
+         * A uniTag (one service) was registered and provisioned.
+         */
+        SUBSCRIBER_UNI_TAG_REGISTERED,
+
+        /**
+         * A uniTag (one service) was unregistered and deprovisioned.
+         */
+        SUBSCRIBER_UNI_TAG_UNREGISTERED,
+
+        /**
+         * A uniTag (one service) was failed while registration.
+         */
+        SUBSCRIBER_UNI_TAG_REGISTRATION_FAILED,
+
+        /**
+         * A uniTag (one service) was failed while unregistration.
+         */
+        SUBSCRIBER_UNI_TAG_UNREGISTRATION_FAILED
     }
 
     /**
+     * Creates an event of a given type and for the specified device, port,
+     * along with the cVlanId, sVlanId, and tpId. The vlan fields may not be provisioned
+     * if the event is related to the access device (dis)connection.
      *
+     * @param type     the event type
+     * @param deviceId the device id
+     * @param port     the device port
+     * @param sVlanId  the service vlan
+     * @param cVlanId  the customer vlan
+     * @param tpId     the technology profile
+     */
+    public AccessDeviceEvent(Type type, DeviceId deviceId,
+                             Port port,
+                             VlanId sVlanId,
+                             VlanId cVlanId,
+                             Integer tpId) {
+        super(type, deviceId);
+        this.port = Optional.ofNullable(port);
+        this.sVlan = Optional.ofNullable(sVlanId);
+        this.cVlan = Optional.ofNullable(cVlanId);
+        this.tpId = Optional.ofNullable(tpId);
+    }
+
+    /**
      * Creates an event of a given type and for the specified device,
      * along with the cVlanId and sVlanId. The vlan fields may not be provisioned
      * if the event is related to the access device (dis)connection.
      *
-     * @param type the event type
+     * @param type     the event type
      * @param deviceId the device id
-     * @param sVlanId the service vlan
-     * @param cVlanId the customer vlan
+     * @param sVlanId  the service vlan
+     * @param cVlanId  the customer vlan
+     * @param tpId     the technology profile id
      */
     public AccessDeviceEvent(Type type, DeviceId deviceId,
                              VlanId sVlanId,
-                             VlanId cVlanId) {
+                             VlanId cVlanId,
+                             Integer tpId) {
         super(type, deviceId);
         this.sVlan = Optional.ofNullable(sVlanId);
         this.cVlan = Optional.ofNullable(cVlanId);
+        this.tpId = Optional.ofNullable(tpId);
         this.port = Optional.empty();
     }
 
     /**
+     * Creates an event of a given type and for the specified device and port.
      *
-     * Creates an event of a given type and for the specified device, and timestamp
-     * along with the cVlanId and sVlanId. The vlan fields may not be provisioned
-     * if the event is related to the access device (dis)connection.
-     *
-     * @param type the event type
+     * @param type     the event type
      * @param deviceId the device id
-     * @param time a timestamp
-     * @param sVlanId the service vlan
-     * @param cVlanId the customer vlan
+     * @param port     the device port
      */
-    protected AccessDeviceEvent(Type type, DeviceId deviceId, long time,
-                                VlanId sVlanId,
-                                VlanId cVlanId) {
-        super(type, deviceId, time);
-        this.sVlan = Optional.ofNullable(sVlanId);
-        this.cVlan = Optional.ofNullable(cVlanId);
-        this.port = Optional.empty();
-
-    }
-
     public AccessDeviceEvent(Type type, DeviceId deviceId, Port port) {
         super(type, deviceId);
         this.sVlan = Optional.empty();
         this.cVlan = Optional.empty();
+        this.tpId = Optional.empty();
         this.port = Optional.ofNullable(port);
     }
 
@@ -125,4 +148,8 @@
         return port;
     }
 
+    public Optional<Integer> tpId() {
+        return tpId;
+    }
+
 }
diff --git a/api/src/main/java/org/opencord/olt/AccessDeviceService.java b/api/src/main/java/org/opencord/olt/AccessDeviceService.java
index 32c2ed0..06c5188 100644
--- a/api/src/main/java/org/opencord/olt/AccessDeviceService.java
+++ b/api/src/main/java/org/opencord/olt/AccessDeviceService.java
@@ -16,20 +16,17 @@
 
 package org.opencord.olt;
 
-import java.util.Collection;
 import java.util.List;
-import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
 
-import com.google.common.collect.ImmutableSet;
 import org.onlab.packet.VlanId;
 import org.onosproject.event.ListenerService;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DeviceId;
-import org.onosproject.net.meter.MeterKey;
-import org.opencord.sadis.SubscriberAndDeviceInformation;
 
 import com.google.common.collect.ImmutableMap;
+import org.opencord.sadis.UniTagInformation;
 
 /**
  * Service for interacting with an access device (OLT).
@@ -39,6 +36,7 @@
 
     /**
      * Provisions connectivity for a subscriber on an access device.
+     * Installs flows for all uni tag information
      *
      * @param port subscriber's connection point
      * @return true if successful false otherwise
@@ -47,6 +45,7 @@
 
     /**
      * Removes provisioned connectivity for a subscriber from an access device.
+     * Removes flows for all uni tag information
      *
      * @param port subscriber's connection point
      * @return true if successful false otherwise
@@ -54,31 +53,32 @@
     boolean removeSubscriber(ConnectPoint port);
 
     /**
-     * Provisions flows for the specific subscriber.
+     * Provisions a uni tag information for the specific subscriber.
+     * It finds the related uni tag information from the subscriber uni tag list
+     * and installs it
      *
      * @param subscriberId Identification of the subscriber
-     * @param sTag additional outer tag on this port
-     * @param cTag additional inner tag on this port
+     * @param sTag         additional outer tag on this port
+     * @param cTag         additional inner tag on this port
+     * @param tpId         additional technology profile id
      * @return true if successful false otherwise
      */
-    boolean provisionSubscriber(AccessSubscriberId subscriberId, Optional<VlanId> sTag, Optional<VlanId> cTag);
+    boolean provisionSubscriber(AccessSubscriberId subscriberId, Optional<VlanId> sTag,
+                                Optional<VlanId> cTag, Optional<Integer> tpId);
 
     /**
-     * Removes flows for the specific subscriber.
+     * Removes a uni tag information for the specific subscriber.
+     * It finds the related uni tag information from the subscriber uni tag list
+     * and remove it
      *
      * @param subscriberId Identification of the subscriber
-     * @param sTag additional outer tag on this port
-     * @param cTag additional inner tag on this port
+     * @param sTag         additional outer tag on this port
+     * @param cTag         additional inner tag on this port
+     * @param tpId         additional technology profile id
      * @return true if successful false otherwise
      */
-    boolean removeSubscriber(AccessSubscriberId subscriberId, Optional<VlanId> sTag, Optional<VlanId> cTag);
-
-    /**
-     * Returns information about the provisioned subscribers.
-     *
-     * @return subscribers
-     */
-    Collection<Map.Entry<ConnectPoint, Map.Entry<VlanId, VlanId>>> getSubscribers();
+    boolean removeSubscriber(AccessSubscriberId subscriberId, Optional<VlanId> sTag,
+                             Optional<VlanId> cTag, Optional<Integer> tpId);
 
     /**
      * Returns the list of active OLTs.
@@ -89,26 +89,11 @@
 
     /**
      * Returns information about subscribers that have been programmed in the
-     * data-plane.
+     * data-plane. It shows all uni tag information list of the subscribers even if
+     * these have not been programmed.
      *
      * @return an immutable map of locations and subscriber information
      */
-    ImmutableMap<ConnectPoint, SubscriberAndDeviceInformation> getProgSubs();
-
-    /**
-     * Returns information about device-meter mappings that have been programmed in the
-     * data-plane.
-     *
-     * @return an immutable set of device-meter mappings
-     */
-    ImmutableSet<MeterKey> getProgMeters();
-
-    /**
-     * Returns information about bandwidthProfile-meterKey (device / meter) mappings
-     * that have been programmed in the data-plane.
-     *
-     * @return an immutable map of bandwidthProfile-meterKey (device / meter) mappings
-     */
-    ImmutableMap<String, List<MeterKey>> getBpMeterMappings();
+    ImmutableMap<ConnectPoint, Set<UniTagInformation>> getProgSubs();
 
 }
diff --git a/app/pom.xml b/app/pom.xml
index a15db81..b50a4d6 100644
--- a/app/pom.xml
+++ b/app/pom.xml
@@ -45,7 +45,7 @@
         <onos.app.readme>CORD OLT Access management application</onos.app.readme>
         <api.package>org.opencord.olt.rest</api.package>
         <olt.api.version>${project.version}</olt.api.version>
-        <sadis.api.version>3.1.0</sadis.api.version>
+        <sadis.api.version>3.1.1-SNAPSHOT</sadis.api.version>
     </properties>
 
     <dependencies>
@@ -97,7 +97,6 @@
             <plugin>
                 <groupId>org.onosproject</groupId>
                 <artifactId>onos-maven-plugin</artifactId>
-                <version>1.11</version>
             </plugin>
             <plugin>
                 <groupId>org.apache.felix</groupId>
diff --git a/app/src/main/java/org/opencord/olt/cli/ShowBpMeterMappingsCommand.java b/app/src/main/java/org/opencord/olt/cli/ShowBpMeterMappingsCommand.java
index 83b518f..630ae4a 100644
--- a/app/src/main/java/org/opencord/olt/cli/ShowBpMeterMappingsCommand.java
+++ b/app/src/main/java/org/opencord/olt/cli/ShowBpMeterMappingsCommand.java
@@ -19,10 +19,10 @@
 import org.apache.karaf.shell.commands.Command;
 import org.onosproject.cli.AbstractShellCommand;
 import org.onosproject.net.meter.MeterKey;
-import org.opencord.olt.AccessDeviceService;
+import org.opencord.olt.internalapi.AccessDeviceMeterService;
 
-import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 @Command(scope = "onos", name = "volt-bpmeter-mappings",
         description = "Shows information about bandwidthProfile-meterKey (device / meter) mappings")
@@ -30,12 +30,12 @@
 
     @Override
     protected void execute() {
-        AccessDeviceService service = AbstractShellCommand.get(AccessDeviceService.class);
-        Map<String, List<MeterKey>> bpMeterMappings = service.getBpMeterMappings();
+        AccessDeviceMeterService service = AbstractShellCommand.get(AccessDeviceMeterService.class);
+        Map<String, Set<MeterKey>> bpMeterMappings = service.getBpMeterMappings();
         bpMeterMappings.forEach(this::display);
     }
 
-    private void display(String bpInfo, List<MeterKey> meterKeyList) {
+    private void display(String bpInfo, Set<MeterKey> meterKeyList) {
         meterKeyList.forEach(meterKey ->
                 print("bpInfo=%s deviceId=%s meterId=%s",
                         bpInfo, meterKey.deviceId(), meterKey.meterId()));
diff --git a/app/src/main/java/org/opencord/olt/cli/ShowOltCommand.java b/app/src/main/java/org/opencord/olt/cli/ShowOltCommand.java
index d01e8e2..0c0441f 100644
--- a/app/src/main/java/org/opencord/olt/cli/ShowOltCommand.java
+++ b/app/src/main/java/org/opencord/olt/cli/ShowOltCommand.java
@@ -30,8 +30,6 @@
     @Override
     protected void execute() {
         AccessDeviceService service = AbstractShellCommand.get(AccessDeviceService.class);
-        service.fetchOlts().forEach(did -> {
-            print("OLT %s", did);
-        });
+        service.fetchOlts().forEach(did -> print("OLT %s", did));
     }
 }
diff --git a/app/src/main/java/org/opencord/olt/cli/ShowProgrammedMetersCommand.java b/app/src/main/java/org/opencord/olt/cli/ShowProgrammedMetersCommand.java
index 6fa8c9d..6615002 100644
--- a/app/src/main/java/org/opencord/olt/cli/ShowProgrammedMetersCommand.java
+++ b/app/src/main/java/org/opencord/olt/cli/ShowProgrammedMetersCommand.java
@@ -19,7 +19,7 @@
 import org.apache.karaf.shell.commands.Command;
 import org.onosproject.cli.AbstractShellCommand;
 import org.onosproject.net.meter.MeterKey;
-import org.opencord.olt.AccessDeviceService;
+import org.opencord.olt.internalapi.AccessDeviceMeterService;
 
 import java.util.Set;
 
@@ -33,7 +33,7 @@
 
     @Override
     protected void execute() {
-        AccessDeviceService service = AbstractShellCommand.get(AccessDeviceService.class);
+        AccessDeviceMeterService service = AbstractShellCommand.get(AccessDeviceMeterService.class);
         Set<MeterKey> programmedMeters = service.getProgMeters();
         programmedMeters.forEach(this::display);
     }
diff --git a/app/src/main/java/org/opencord/olt/cli/ShowProgrammedSubscribersCommand.java b/app/src/main/java/org/opencord/olt/cli/ShowProgrammedSubscribersCommand.java
index 2a4409e..be2b6dd 100644
--- a/app/src/main/java/org/opencord/olt/cli/ShowProgrammedSubscribersCommand.java
+++ b/app/src/main/java/org/opencord/olt/cli/ShowProgrammedSubscribersCommand.java
@@ -17,12 +17,13 @@
 package org.opencord.olt.cli;
 
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.karaf.shell.commands.Command;
 import org.onosproject.cli.AbstractShellCommand;
 import org.onosproject.net.ConnectPoint;
 import org.opencord.olt.AccessDeviceService;
-import org.opencord.sadis.SubscriberAndDeviceInformation;
+import org.opencord.sadis.UniTagInformation;
 
 /**
  * Shows subscriber information for those subscriber which have been programmed
@@ -35,11 +36,12 @@
     @Override
     protected void execute() {
         AccessDeviceService service = AbstractShellCommand.get(AccessDeviceService.class);
-        Map<ConnectPoint, SubscriberAndDeviceInformation> info = service.getProgSubs();
+        Map<ConnectPoint, Set<UniTagInformation>> info = service.getProgSubs();
         info.forEach(this::display);
     }
 
-    private void display(ConnectPoint cp, SubscriberAndDeviceInformation sub) {
-        print("location=%s subscriber=%s", cp, sub);
+    private void display(ConnectPoint cp, Set<UniTagInformation> uniTagInformation) {
+        uniTagInformation.forEach(uniTag ->
+                print("location=%s tagInformation=%s", cp, uniTag));
     }
 }
diff --git a/app/src/main/java/org/opencord/olt/cli/ShowSubscribersCommand.java b/app/src/main/java/org/opencord/olt/cli/ShowSubscribersCommand.java
deleted file mode 100644
index 711d1fa..0000000
--- a/app/src/main/java/org/opencord/olt/cli/ShowSubscribersCommand.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2016-present Open Networking Foundation
- *
- * 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.olt.cli;
-
-import java.util.Map;
-
-import org.apache.karaf.shell.commands.Command;
-import org.onlab.packet.VlanId;
-import org.onosproject.cli.AbstractShellCommand;
-import org.onosproject.net.ConnectPoint;
-import org.opencord.olt.AccessDeviceService;
-
-/**
- * Shows provisioned (configured) subscribers. The data plane flows for the
- * subscribers may or may not have been programmed.
- */
-@Command(scope = "onos", name = "volt-subscribers",
-        description = "Shows pre-provisioned subscribers")
-public class ShowSubscribersCommand extends AbstractShellCommand {
-
-    private static final String FORMAT = "port=%s, svlan=%s, cvlan=%s";
-
-    @Override
-    protected void execute() {
-        AccessDeviceService service = AbstractShellCommand.get(AccessDeviceService.class);
-        service.getSubscribers().forEach(this::display);
-    }
-
-    private void display(Map.Entry<ConnectPoint, Map.Entry<VlanId, VlanId>> subscriber) {
-        print(FORMAT, subscriber.getKey(), subscriber.getValue().getKey(),
-                subscriber.getValue().getValue());
-    }
-}
diff --git a/app/src/main/java/org/opencord/olt/cli/UniTagAddCommand.java b/app/src/main/java/org/opencord/olt/cli/UniTagAddCommand.java
new file mode 100644
index 0000000..9ea5439
--- /dev/null
+++ b/app/src/main/java/org/opencord/olt/cli/UniTagAddCommand.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.olt.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
+import org.onlab.packet.VlanId;
+import org.onosproject.cli.AbstractShellCommand;
+import org.opencord.olt.AccessDeviceService;
+import org.opencord.olt.AccessSubscriberId;
+
+import java.util.Optional;
+
+/**
+ * Adds a subscriber uni tag.
+ */
+@Command(scope = "onos", name = "volt-add-subscriber-unitag",
+        description = "Adds a uni tag to an access device")
+public class UniTagAddCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "portName", description = "Port name",
+            required = true, multiValued = false)
+    private String strPortName = null;
+
+    @Option(name = "--cTag", description = "Inner vlan id",
+            required = false, multiValued = false)
+    private String strCtag = null;
+
+    @Option(name = "--sTag", description = "Outer vlan id",
+            required = false, multiValued = false)
+    private String strStag = null;
+
+    @Option(name = "--tpId", description = "Technology profile id",
+            required = false, multiValued = false)
+    private String strTpId = null;
+
+    @Override
+    protected void execute() {
+
+        AccessDeviceService service = AbstractShellCommand.get(AccessDeviceService.class);
+        AccessSubscriberId portName = new AccessSubscriberId(strPortName);
+
+        Optional<VlanId> cTag = strCtag == null ? Optional.empty() : Optional.of(VlanId.vlanId(strCtag));
+        Optional<VlanId> sTag = strStag == null ? Optional.empty() : Optional.of(VlanId.vlanId(strStag));
+        Optional<Integer> tpId = strTpId == null ? Optional.empty() : Optional.of(Integer.parseInt(strTpId));
+        service.provisionSubscriber(portName, sTag, cTag, tpId);
+    }
+}
diff --git a/app/src/main/java/org/opencord/olt/cli/UniTagRemoveCommand.java b/app/src/main/java/org/opencord/olt/cli/UniTagRemoveCommand.java
new file mode 100644
index 0000000..74d05b7
--- /dev/null
+++ b/app/src/main/java/org/opencord/olt/cli/UniTagRemoveCommand.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.olt.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
+import org.onlab.packet.VlanId;
+import org.onosproject.cli.AbstractShellCommand;
+import org.opencord.olt.AccessDeviceService;
+import org.opencord.olt.AccessSubscriberId;
+
+import java.util.Optional;
+
+/**
+ * Removes a uni tag from a subscriber (portname).
+ */
+@Command(scope = "onos", name = "volt-remove-subscriber-unitag",
+        description = "Removes a uni tag from an access device")
+public class UniTagRemoveCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "portName", description = "Port name",
+            required = true, multiValued = false)
+    private String strPortName = null;
+
+    @Option(name = "--cTag", description = "Inner vlan id",
+            required = false, multiValued = false)
+    private String strCtag = null;
+
+    @Option(name = "--sTag", description = "Outer vlan id",
+            required = false, multiValued = false)
+    private String strStag = null;
+
+    @Option(name = "--tpId", description = "Technology profile id",
+            required = false, multiValued = false)
+    private String strTpId = null;
+
+    @Override
+    protected void execute() {
+
+        AccessDeviceService service = AbstractShellCommand.get(AccessDeviceService.class);
+        AccessSubscriberId portName = new AccessSubscriberId(strPortName);
+
+        Optional<VlanId> cTag = strCtag == null ? Optional.empty() : Optional.of(VlanId.vlanId(strCtag));
+        Optional<VlanId> sTag = strStag == null ? Optional.empty() : Optional.of(VlanId.vlanId(strStag));
+        Optional<Integer> tpId = strTpId == null ? Optional.empty() : Optional.of(Integer.parseInt(strTpId));
+        service.removeSubscriber(portName, sTag, cTag, tpId);
+    }
+}
diff --git a/app/src/main/java/org/opencord/olt/impl/Olt.java b/app/src/main/java/org/opencord/olt/impl/Olt.java
index e0ac2b3..d4ed2ff 100644
--- a/app/src/main/java/org/opencord/olt/impl/Olt.java
+++ b/app/src/main/java/org/opencord/olt/impl/Olt.java
@@ -22,10 +22,7 @@
 import static org.onlab.util.Tools.groupedThreads;
 import static org.slf4j.LoggerFactory.getLogger;
 
-import java.util.AbstractMap;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
 import java.util.Dictionary;
 import java.util.List;
 import java.util.Map;
@@ -34,7 +31,6 @@
 import java.util.Properties;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.stream.Collectors;
@@ -47,12 +43,7 @@
 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.IPv4;
-import org.onlab.packet.IPv6;
-import org.onlab.packet.TpPort;
 import org.onlab.packet.VlanId;
-import org.onlab.util.Tools;
 import org.onosproject.cfg.ComponentConfigService;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
@@ -67,48 +58,27 @@
 import org.onosproject.net.device.DeviceEvent;
 import org.onosproject.net.device.DeviceListener;
 import org.onosproject.net.device.DeviceService;
-import org.onosproject.net.flow.DefaultTrafficSelector;
-import org.onosproject.net.flow.DefaultTrafficTreatment;
-import org.onosproject.net.flow.TrafficSelector;
-import org.onosproject.net.flow.TrafficTreatment;
-import org.onosproject.net.flow.criteria.Criteria;
-import org.onosproject.net.flowobjective.DefaultFilteringObjective;
-import org.onosproject.net.flowobjective.DefaultForwardingObjective;
-import org.onosproject.net.flowobjective.FilteringObjective;
 import org.onosproject.net.flowobjective.FlowObjectiveService;
 import org.onosproject.net.flowobjective.ForwardingObjective;
 import org.onosproject.net.flowobjective.Objective;
 import org.onosproject.net.flowobjective.ObjectiveContext;
 import org.onosproject.net.flowobjective.ObjectiveError;
-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.MeterContext;
-import org.onosproject.net.meter.MeterEvent;
-import org.onosproject.net.meter.MeterFailReason;
 import org.onosproject.net.meter.MeterId;
-import org.onosproject.net.meter.MeterKey;
-import org.onosproject.net.meter.MeterListener;
-import org.onosproject.net.meter.MeterRequest;
-import org.onosproject.net.meter.MeterService;
-import org.onosproject.store.serializers.KryoNamespaces;
-import org.onosproject.store.service.ConsistentMultimap;
-import org.onosproject.store.service.Serializer;
-import org.onosproject.store.service.StorageService;
 import org.opencord.olt.AccessDeviceEvent;
 import org.opencord.olt.AccessDeviceListener;
 import org.opencord.olt.AccessDeviceService;
 import org.opencord.olt.AccessSubscriberId;
+import org.opencord.olt.internalapi.AccessDeviceFlowService;
+import org.opencord.olt.internalapi.AccessDeviceMeterService;
 import org.opencord.sadis.BandwidthProfileInformation;
 import org.opencord.sadis.BaseInformationService;
 import org.opencord.sadis.SadisService;
 import org.opencord.sadis.SubscriberAndDeviceInformation;
+import org.opencord.sadis.UniTagInformation;
 import org.osgi.service.component.ComponentContext;
 import org.slf4j.Logger;
 
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 
@@ -122,16 +92,10 @@
         implements AccessDeviceService {
     private static final String APP_NAME = "org.opencord.olt";
 
-    private static final short DEFAULT_VLAN = 0;
     private static final short EAPOL_DEFAULT_VLAN = 4091;
-    private static final int DEFAULT_TP_ID = 64;
     private static final String DEFAULT_BP_ID = "Default";
-    private static final String ADDITIONAL_VLANS = "additional-vlans";
+    private static final String DEFAULT_MCAST_SERVICE_NAME = "MC";
     private static final String NO_UPLINK_PORT = "No uplink port found for OLT device {}";
-    private static final String INSTALLED = "installed";
-    private static final String REMOVED = "removed";
-    private static final String INSTALLATION = "installation";
-    private static final String REMOVAL = "removal";
 
     private final Logger log = getLogger(getClass());
 
@@ -154,73 +118,38 @@
     protected SadisService sadisService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected MeterService meterService;
+    protected AccessDeviceFlowService oltFlowService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected StorageService storageService;
-
-    @Property(name = "defaultVlan", intValue = DEFAULT_VLAN,
-            label = "Default VLAN RG<->ONU traffic")
-    private int defaultVlan = DEFAULT_VLAN;
-
-    @Property(name = "enableDhcpOnProvisioning", boolValue = true,
-            label = "Create the DHCP Flow rules when a subscriber is provisioned")
-    protected boolean enableDhcpOnProvisioning = false;
-
-    @Property(name = "enableDhcpV4", boolValue = true,
-            label = "Enable flows for DHCP v4")
-    protected boolean enableDhcpV4 = true;
-
-    @Property(name = "enableDhcpV6", boolValue = true,
-            label = "Enable flows for DHCP v6")
-    protected boolean enableDhcpV6 = false;
-
-    @Property(name = "enableIgmpOnProvisioning", boolValue = false,
-            label = "Create IGMP Flow rules when a subscriber is provisioned")
-    protected boolean enableIgmpOnProvisioning = false;
-
-    @Property(name = "deleteMeters", boolValue = true,
-            label = "Deleting Meters based on flow count statistics")
-    protected boolean deleteMeters = true;
-
-    @Property(name = "defaultTechProfileId", intValue = DEFAULT_TP_ID,
-            label = "Default technology profile id that is used for authentication trap flows")
-    protected int defaultTechProfileId = DEFAULT_TP_ID;
+    protected AccessDeviceMeterService oltMeterService;
 
     @Property(name = "defaultBpId", value = DEFAULT_BP_ID,
             label = "Default bandwidth profile id that is used for authentication trap flows")
     protected String defaultBpId = DEFAULT_BP_ID;
 
-    @Property(name = "enableEapol", boolValue = true,
-            label = "Send EAPOL authentication trap flows before subscriber provisioning")
-    protected boolean enableEapol = true;
+    @Property(name = "multicastServiceName", value = DEFAULT_MCAST_SERVICE_NAME,
+            label = "The multicast service name")
+    protected String mcastServiceName = DEFAULT_MCAST_SERVICE_NAME;
 
     private final DeviceListener deviceListener = new InternalDeviceListener();
-    private final MeterListener meterListener = new InternalMeterListener();
 
-    private ApplicationId appId;
     protected BaseInformationService<SubscriberAndDeviceInformation> subsService;
     private BaseInformationService<BandwidthProfileInformation> bpService;
 
-    private Map<String, List<MeterKey>> bpInfoToMeter = new ConcurrentHashMap<>();
-
     private ExecutorService oltInstallers = Executors.newFixedThreadPool(4,
             groupedThreads("onos/olt-service",
                     "olt-installer-%d"));
 
-    private ConsistentMultimap<ConnectPoint, Map.Entry<VlanId, VlanId>> additionalVlans;
-
     protected ExecutorService eventExecutor;
 
-    private Map<ConnectPoint, SubscriberAndDeviceInformation> programmedSubs;
-    private Set<MeterKey> programmedMeters;
-
+    private Map<ConnectPoint, Set<UniTagInformation>> programmedSubs;
 
     @Activate
     public void activate(ComponentContext context) {
-        eventExecutor = newSingleThreadScheduledExecutor(groupedThreads("onos/olt", "events-%d", log));
+        eventExecutor = newSingleThreadScheduledExecutor(groupedThreads("onos/olt",
+                "events-%d", log));
         modified(context);
-        appId = coreService.registerApplication(APP_NAME);
+        ApplicationId appId = coreService.registerApplication(APP_NAME);
 
         // ensure that flow rules are purged from flow-store upon olt-disconnection
         // when olt reconnects, the port-numbers may change for the ONUs
@@ -233,7 +162,6 @@
                         "purgeOnDisconnection", "true");
         componentConfigService.registerProperties(getClass());
         programmedSubs = Maps.newConcurrentMap();
-        programmedMeters = ConcurrentHashMap.newKeySet();
 
         eventDispatcher.addSink(AccessDeviceEvent.class, listenerRegistry);
 
@@ -247,15 +175,7 @@
             checkAndCreateDeviceFlows(d);
         }
 
-        additionalVlans = storageService.<ConnectPoint, Map.Entry<VlanId, VlanId>>consistentMultimapBuilder()
-                .withName(ADDITIONAL_VLANS)
-                .withSerializer(Serializer.using(Arrays.asList(KryoNamespaces.API),
-                        AbstractMap.SimpleEntry.class))
-                .build();
-
         deviceService.addListener(deviceListener);
-        meterService.addListener(meterListener);
-
         log.info("Started with Application ID {}", appId.id());
     }
 
@@ -263,7 +183,6 @@
     public void deactivate() {
         componentConfigService.unregisterProperties(getClass(), false);
         deviceService.removeListener(deviceListener);
-        meterService.removeListener(meterListener);
         eventDispatcher.removeSink(AccessDeviceEvent.class);
         log.info("Stopped");
     }
@@ -273,50 +192,15 @@
         Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
 
         try {
-            String s = get(properties, "defaultVlan");
-            defaultVlan = isNullOrEmpty(s) ? DEFAULT_VLAN : Integer.parseInt(s.trim());
+            defaultBpId = get(properties, "defaultBpId");
 
-            Boolean o = Tools.isPropertyEnabled(properties, "enableDhcpOnProvisioning");
-            if (o != null) {
-                enableDhcpOnProvisioning = o;
-            }
+            String serviceName = get(properties, "multicastServiceName");
+            mcastServiceName = isNullOrEmpty(serviceName) ? DEFAULT_MCAST_SERVICE_NAME : serviceName;
 
-            Boolean v4 = Tools.isPropertyEnabled(properties, "enableDhcpV4");
-            if (v4 != null) {
-                enableDhcpV4 = v4;
-            }
-
-            Boolean v6 = Tools.isPropertyEnabled(properties, "enableDhcpV6");
-            if (v6 != null) {
-                enableDhcpV6 = v6;
-            }
-
-            Boolean p = Tools.isPropertyEnabled(properties, "enableIgmpOnProvisioning");
-            if (p != null) {
-                enableIgmpOnProvisioning = p;
-            }
-
-            log.info("DHCP Settings [enableDhcpOnProvisioning: {}, enableDhcpV4: {}, enableDhcpV6: {}]",
-                    enableDhcpOnProvisioning, enableDhcpV4, enableDhcpV6);
-
-            Boolean d = Tools.isPropertyEnabled(properties, "deleteMeters");
-            if (d != null) {
-                deleteMeters = d;
-            }
-
-            String tpId = get(properties, "defaultTechProfileId");
-            defaultTechProfileId = isNullOrEmpty(s) ? DEFAULT_TP_ID : Integer.parseInt(tpId.trim());
-
-            String bpId = get(properties, "defaultBpId");
-            defaultBpId = bpId;
-
-            Boolean eap = Tools.isPropertyEnabled(properties, "enableEapol");
-            if (eap != null) {
-                enableEapol = eap;
-            }
+            log.debug("OLT properties: DefaultBpId: {}, MulticastServiceName: {}", defaultBpId, mcastServiceName);
 
         } catch (Exception e) {
-            defaultVlan = DEFAULT_VLAN;
+            log.error("Error while modifying the properties", e);
         }
     }
 
@@ -327,7 +211,8 @@
         PortNumber subscriberPortNo = connectPoint.port();
 
         checkNotNull(deviceService.getPort(deviceId, subscriberPortNo),
-                "Invalid connect point");
+                "Invalid connect point:" + connectPoint);
+
         // Find the subscriber on this connect point
         SubscriberAndDeviceInformation sub = getSubscriber(connectPoint);
         if (sub == null) {
@@ -342,37 +227,17 @@
             return false;
         }
 
-        SubscriberAndDeviceInformation prgSub = programmedSubs.get(connectPoint);
-        if (prgSub != null) {
-            log.warn("Subscriber {} on connectionPoint {} was previously programmed .. "
-                    + "taking no action. Note that updating a subscribers params "
-                    + "(vlans, bw, tpid etc) requires removing the subscriber"
-                    + "before re-provisioning the subscriber", prgSub.id(),
-                     connectPoint);
-            return true;
-        }
-
         //delete Eapol authentication flow with default bandwidth
         //wait until Eapol rule with defaultBpId is removed to install subscriber-based rules
-        CompletableFuture<ObjectiveError> filterFuture = new CompletableFuture();
-        processEapolFilteringObjectives(deviceId, subscriberPortNo, defaultBpId, filterFuture,
-                VlanId.vlanId(EAPOL_DEFAULT_VLAN), false);
-        // do not remove meter from bpInfoToMeter mapping as flows for other ONUs
-        // could still be using it - this also prevents duplicate meters from being
-        // created for the same bandwidth profile
-        // we still need to remove from programmedMeters so the meter can be
-        // deleted if its reference count drops to zero
-        removeMeterIdFromPrgMeters(deviceId, defaultBpId);
-
         //install subscriber flows
+        CompletableFuture<ObjectiveError> filterFuture = new CompletableFuture();
+        oltFlowService.processEapolFilteringObjectives(deviceId, subscriberPortNo, defaultBpId, filterFuture,
+                VlanId.vlanId(EAPOL_DEFAULT_VLAN), false);
         filterFuture.thenAcceptAsync(filterStatus -> {
             if (filterStatus == null) {
-                provisionSubscriberBasedFlows(connectPoint, uplinkPort.number(), Optional.empty(), sub);
+                provisionUniTagList(connectPoint, uplinkPort.number(), sub);
             }
         });
-
-        // cache subscriber info
-        programmedSubs.put(connectPoint, sub);
         return true;
     }
 
@@ -385,8 +250,8 @@
         DeviceId deviceId = connectPoint.deviceId();
         PortNumber subscriberPortNo = connectPoint.port();
 
-        SubscriberAndDeviceInformation subscriber = programmedSubs.get(connectPoint);
-        if (subscriber == null) {
+        Set<UniTagInformation> uniTagInformationSet = programmedSubs.get(connectPoint);
+        if (uniTagInformationSet == null || uniTagInformationSet.isEmpty()) {
             log.warn("Subscriber on connectionPoint {} was not previously programmed, " +
                     "no need to remove it", connectPoint);
             return true;
@@ -399,53 +264,38 @@
             return false;
         }
 
-        //delete dhcp & igmp trap flows
-        MeterId upstreamMeterId = getMeterIdFromBpMapping(deviceId, subscriber.upstreamBandwidthProfile());
+        for (UniTagInformation uniTag : uniTagInformationSet) {
 
-        // remove dhcp filters
-        processDhcpFilteringObjectives(deviceId, subscriberPortNo,
-                upstreamMeterId, subscriber.technologyProfileId(), false, true);
+            if (mcastServiceName.equals(uniTag.getServiceName())) {
+                continue;
+            }
 
-        // remove igmp filters
-        processIgmpFilteringObjectives(deviceId, subscriberPortNo,
-                upstreamMeterId, subscriber.technologyProfileId(), false);
+            unprovisionVlans(deviceId, uplinkPort.number(), subscriberPortNo, uniTag);
 
-        //unprovision vlans
-        unprovisionVlans(deviceId, uplinkPort.number(), subscriberPortNo, subscriber, Optional.empty());
+            // re-install eapol with default bandwidth profile
+            oltFlowService.processEapolFilteringObjectives(deviceId, subscriberPortNo,
+                    uniTag.getUpstreamBandwidthProfile(), null, uniTag.getPonCTag(), false);
 
-        // Remove if there are any flows for the additional Vlans
-        Collection<? extends Map.Entry<VlanId, VlanId>> vlansList = additionalVlans.get(connectPoint).value();
-
-        // Remove the flows for the additional vlans for this subscriber
-        for (Map.Entry<VlanId, VlanId> vlans : vlansList) {
-            unprovisionTransparentFlows(deviceId, uplinkPort.number(), subscriberPortNo,
-                    vlans.getValue(), vlans.getKey());
-
-            // Remove it from the map also
-            additionalVlans.remove(connectPoint, vlans);
+            Port port = deviceService.getPort(deviceId, subscriberPortNo);
+            if (port != null && port.isEnabled()) {
+                oltFlowService.processEapolFilteringObjectives(deviceId, subscriberPortNo, defaultBpId,
+                        null, VlanId.vlanId(EAPOL_DEFAULT_VLAN), true);
+            } else {
+                log.debug("Port {} is no longer enabled or it's unavailable. Not "
+                        + "reprogramming default eapol flow", connectPoint);
+            }
         }
-
-        // re-install eapol with default bandwidth profile
-        processEapolFilteringObjectives(deviceId, subscriberPortNo,
-                subscriber.upstreamBandwidthProfile(), null, subscriber.cTag(), false);
-
-        Port port = deviceService.getPort(deviceId, subscriberPortNo);
-        if (port != null && port.isEnabled()) {
-            processEapolFilteringObjectives(deviceId, subscriberPortNo, defaultBpId,
-                    null, VlanId.vlanId(EAPOL_DEFAULT_VLAN), true);
-        } else {
-            log.debug("Port {} is no longer enabled or it's unavailable. Not "
-                    + "reprogramming default eapol flow", connectPoint);
-        }
-
-        programmedSubs.remove(connectPoint);
         return true;
     }
 
-    @Override
-    public boolean provisionSubscriber(AccessSubscriberId subscriberId, Optional<VlanId> sTag, Optional<VlanId> cTag) {
 
-        log.info("Provisioning subscriber using subscriberId {}, sTag {}, cTag {}", subscriberId, sTag, cTag);
+
+    @Override
+    public boolean provisionSubscriber(AccessSubscriberId subscriberId, Optional<VlanId> sTag,
+                                       Optional<VlanId> cTag, Optional<Integer> tpId) {
+
+        log.info("Provisioning subscriber using subscriberId {}, sTag {}, cTag {}, tpId {}" +
+                "", subscriberId, sTag, cTag, tpId);
 
         // Check if we can find the connect point to which this subscriber is connected
         ConnectPoint subsPort = findSubscriberConnectPoint(subscriberId.toString());
@@ -456,31 +306,23 @@
 
         if (!sTag.isPresent() && !cTag.isPresent()) {
             return provisionSubscriber(subsPort);
-        } else if (sTag.isPresent() && cTag.isPresent()) {
+        } else if (sTag.isPresent() && cTag.isPresent() && tpId.isPresent()) {
             Port uplinkPort = getUplinkPort(deviceService.getDevice(subsPort.deviceId()));
             if (uplinkPort == null) {
                 log.warn(NO_UPLINK_PORT, subsPort.deviceId());
                 return false;
             }
 
-            CompletableFuture<ObjectiveError> filterFuture = new CompletableFuture();
-
             //delete Eapol authentication flow with default bandwidth
             //wait until Eapol rule with defaultBpId is removed to install subscriber-based rules
-            processEapolFilteringObjectives(subsPort.deviceId(), subsPort.port(), defaultBpId, filterFuture,
-                    VlanId.vlanId(EAPOL_DEFAULT_VLAN), false);
-            // do not remove meter from bpInfoToMeter mapping as flows for other ONUs
-            // could still be using it - this also prevents duplicate meters from being
-            // created for the same bandwidth profile
-            // we still need to remove from programmedMeters so the meter can be
-            // deleted if its reference count drops to zero
-            removeMeterIdFromPrgMeters(subsPort.deviceId(), defaultBpId);
-
             //install subscriber flows
+            CompletableFuture<ObjectiveError> filterFuture = new CompletableFuture();
+            oltFlowService.processEapolFilteringObjectives(subsPort.deviceId(), subsPort.port(), defaultBpId,
+                    filterFuture, VlanId.vlanId(EAPOL_DEFAULT_VLAN), false);
             filterFuture.thenAcceptAsync(filterStatus -> {
                 if (filterStatus == null) {
-                    provisionTransparentFlows(subsPort.deviceId(), uplinkPort.number(), subsPort.port(),
-                            cTag.get(), sTag.get());
+                    provisionUniTagInformation(subsPort.deviceId(), uplinkPort.number(), subsPort.port(),
+                            cTag.get(), sTag.get(), tpId.get());
                 }
             });
             return true;
@@ -491,7 +333,8 @@
     }
 
     @Override
-    public boolean removeSubscriber(AccessSubscriberId subscriberId, Optional<VlanId> sTag, Optional<VlanId> cTag) {
+    public boolean removeSubscriber(AccessSubscriberId subscriberId, Optional<VlanId> sTag,
+                                    Optional<VlanId> cTag, Optional<Integer> tpId) {
         // Check if we can find the connect point to which this subscriber is connected
         ConnectPoint subsPort = findSubscriberConnectPoint(subscriberId.toString());
         if (subsPort == null) {
@@ -501,7 +344,7 @@
 
         if (!sTag.isPresent() && !cTag.isPresent()) {
             return removeSubscriber(subsPort);
-        } else if (sTag.isPresent() && cTag.isPresent()) {
+        } else if (sTag.isPresent() && cTag.isPresent() && tpId.isPresent()) {
             // Get the uplink port
             Port uplinkPort = getUplinkPort(deviceService.getDevice(subsPort.deviceId()));
             if (uplinkPort == null) {
@@ -509,60 +352,28 @@
                 return false;
             }
 
-            unprovisionTransparentFlows(subsPort.deviceId(), uplinkPort.number(), subsPort.port(),
-                    cTag.get(), sTag.get());
+            Optional<UniTagInformation> tagInfo = getUniTagInformation(subsPort, cTag.get(), sTag.get(), tpId.get());
+            if (!tagInfo.isPresent()) {
+                log.warn("UniTagInformation does not exist for Device/Port {}, cTag {}, sTag {}, tpId {}",
+                        subsPort, cTag, sTag, tpId);
+                return false;
+            }
 
-            programmedSubs.remove(subsPort);
-
+            unprovisionVlans(subsPort.deviceId(), uplinkPort.number(), subsPort.port(), tagInfo.get());
             return true;
         } else {
-            log.warn("Removing subscriber failed for: {}", subscriberId);
+            log.warn("Removing subscriber is not possible - please check the provided information" +
+                    "for the subscriber: {}", subscriberId);
             return false;
         }
     }
 
     @Override
-    public Collection<Map.Entry<ConnectPoint, Map.Entry<VlanId, VlanId>>> getSubscribers() {
-        ArrayList<Map.Entry<ConnectPoint, Map.Entry<VlanId, VlanId>>> subs = new ArrayList<>();
-
-        // Get the subscribers for all the devices configured in sadis
-        // If the port is UNI, is enabled and exists in Sadis then copy it
-        for (Device d : deviceService.getDevices()) {
-            if (getOltInfo(d) == null) {
-                continue; // not an olt, or not configured in sadis
-            }
-            for (Port p : deviceService.getPorts(d.id())) {
-                if (isUniPort(d, p) && p.isEnabled()) {
-                    ConnectPoint cp = new ConnectPoint(d.id(), p.number());
-
-                    SubscriberAndDeviceInformation sub = getSubscriber(cp);
-                    if (sub != null) {
-                        Map.Entry<VlanId, VlanId> vlans = new AbstractMap.SimpleEntry(sub.sTag(), sub.cTag());
-                        subs.add(new AbstractMap.SimpleEntry(cp, vlans));
-                    }
-                }
-            }
-        }
-
-        return subs;
-    }
-
-    @Override
-    public ImmutableMap<ConnectPoint, SubscriberAndDeviceInformation> getProgSubs() {
+    public ImmutableMap<ConnectPoint, Set<UniTagInformation>> getProgSubs() {
         return ImmutableMap.copyOf(programmedSubs);
     }
 
     @Override
-    public ImmutableSet<MeterKey> getProgMeters() {
-        return ImmutableSet.copyOf(programmedMeters);
-    }
-
-    @Override
-    public ImmutableMap<String, List<MeterKey>> getBpMeterMappings() {
-        return ImmutableMap.copyOf(bpInfoToMeter);
-    }
-
-    @Override
     public List<DeviceId> fetchOlts() {
         // look through all the devices and find the ones that are OLTs as per Sadis
         List<DeviceId> olts = new ArrayList<>();
@@ -597,6 +408,12 @@
         return null;
     }
 
+    /**
+     * Gets the context of the bandwidth profile information for the given parameter.
+     *
+     * @param bandwidthProfile the bandwidth profile id
+     * @return the context of the bandwidth profile information
+     */
     private BandwidthProfileInformation getBandwidthProfileInformation(String bandwidthProfile) {
         if (bandwidthProfile == null) {
             return null;
@@ -610,30 +427,37 @@
      * @param deviceId       the device identifier
      * @param uplink         uplink port of the OLT
      * @param subscriberPort uni port
-     * @param subscriber     subscriber info that includes s, c tags, tech profile and bandwidth profile references
-     * @param defaultVlan    default vlan of the subscriber
+     * @param uniTag         uni tag information
      */
     private void unprovisionVlans(DeviceId deviceId, PortNumber uplink,
-                                  PortNumber subscriberPort, SubscriberAndDeviceInformation subscriber,
-                                  Optional<VlanId> defaultVlan) {
-        log.info("Unprovisioning vlans for subscriber {} on dev/port: {}/{}",
-                 subscriber, deviceId, subscriberPort);
+                                  PortNumber subscriberPort, UniTagInformation uniTag) {
+
+        log.info("Unprovisioning vlans for {} at {}/{}", uniTag, deviceId, subscriberPort);
 
         CompletableFuture<ObjectiveError> downFuture = new CompletableFuture();
         CompletableFuture<ObjectiveError> upFuture = new CompletableFuture();
 
-        VlanId deviceVlan = subscriber.sTag();
-        VlanId subscriberVlan = subscriber.cTag();
+        VlanId deviceVlan = uniTag.getPonSTag();
+        VlanId subscriberVlan = uniTag.getPonCTag();
 
-        MeterId upstreamMeterId = getMeterIdFromBpMapping(deviceId, subscriber.upstreamBandwidthProfile());
-        MeterId downstreamMeterId = getMeterIdFromBpMapping(deviceId, subscriber.downstreamBandwidthProfile());
+        MeterId upstreamMeterId = oltMeterService
+                .getMeterIdFromBpMapping(deviceId, uniTag.getUpstreamBandwidthProfile());
+        MeterId downstreamMeterId = oltMeterService
+                .getMeterIdFromBpMapping(deviceId, uniTag.getDownstreamBandwidthProfile());
 
-        ForwardingObjective.Builder upFwd = upBuilder(uplink, subscriberPort,
-                subscriberVlan, deviceVlan,
-                defaultVlan, upstreamMeterId, subscriber.technologyProfileId());
-        ForwardingObjective.Builder downFwd = downBuilder(uplink, subscriberPort,
-                subscriberVlan, deviceVlan,
-                defaultVlan, downstreamMeterId, subscriber.technologyProfileId());
+        ForwardingObjective.Builder upFwd =
+                oltFlowService.createUpBuilder(uplink, subscriberPort, upstreamMeterId, uniTag);
+        ForwardingObjective.Builder downFwd =
+                oltFlowService.createDownBuilder(uplink, subscriberPort, downstreamMeterId, uniTag);
+
+        if (uniTag.getIsIgmpRequired()) {
+            oltFlowService.processIgmpFilteringObjectives(deviceId, subscriberPort,
+                    upstreamMeterId, uniTag, false, true);
+        }
+        if (uniTag.getIsDhcpRequired()) {
+            oltFlowService.processDhcpFilteringObjectives(deviceId, subscriberPort,
+                    upstreamMeterId, uniTag, false, true);
+        }
 
         flowObjectiveService.forward(deviceId, upFwd.remove(new ObjectiveContext() {
             @Override
@@ -660,317 +484,148 @@
         }));
 
         upFuture.thenAcceptBothAsync(downFuture, (upStatus, downStatus) -> {
+            AccessDeviceEvent.Type type = AccessDeviceEvent.Type.SUBSCRIBER_UNI_TAG_UNREGISTERED;
             if (upStatus == null && downStatus == null) {
-                post(new AccessDeviceEvent(AccessDeviceEvent.Type.SUBSCRIBER_UNREGISTERED,
-                        deviceId,
-                        deviceVlan,
-                        subscriberVlan));
+                log.debug("Uni tag information is unregistered successfully for cTag {}, sTag {}, tpId {}, and" +
+                                "Device/Port{}", uniTag.getPonCTag(), uniTag.getPonSTag(),
+                        uniTag.getTechnologyProfileId(), subscriberPort);
+                updateProgrammedSubscriber(new ConnectPoint(deviceId, subscriberPort), uniTag, false);
             } else if (downStatus != null) {
                 log.error("Subscriber with vlan {} on device {} " +
                                 "on port {} failed downstream uninstallation: {}",
                         subscriberVlan, deviceId, subscriberPort, downStatus);
+                type = AccessDeviceEvent.Type.SUBSCRIBER_UNI_TAG_UNREGISTRATION_FAILED;
             } else if (upStatus != null) {
                 log.error("Subscriber with vlan {} on device {} " +
                                 "on port {} failed upstream uninstallation: {}",
                         subscriberVlan, deviceId, subscriberPort, upStatus);
+                type = AccessDeviceEvent.Type.SUBSCRIBER_UNI_TAG_UNREGISTRATION_FAILED;
             }
+            Port port = deviceService.getPort(deviceId, subscriberPort);
+            post(new AccessDeviceEvent(type, deviceId, port, deviceVlan, subscriberVlan,
+                    uniTag.getTechnologyProfileId()));
         }, oltInstallers);
-
-        programmedMeters.remove(MeterKey.key(deviceId, upstreamMeterId));
-        programmedMeters.remove(MeterKey.key(deviceId, downstreamMeterId));
-        log.debug("programmed Meters size {}", programmedMeters.size());
     }
 
     /**
      * Adds subscriber vlan flows, dhcp, eapol and igmp trap flows for the related uni port.
      *
-     * @param port        the connection point of the subscriber
-     * @param uplinkPort  uplink port of the OLT
-     * @param defaultVlan default vlan of the subscriber
-     * @param sub         subscriber information that includes s, c tags, tech profile and bandwidth profile references
+     * @param connectPoint the connection point of the subscriber
+     * @param uplinkPort   uplink port of the OLT (the nni port)
+     * @param sub          subscriber information that includes s, c tags, tech profile and bandwidth profile references
      */
-    private void provisionSubscriberBasedFlows(ConnectPoint port, PortNumber uplinkPort,
-                                               Optional<VlanId> defaultVlan,
-                                               SubscriberAndDeviceInformation sub) {
+    private void provisionUniTagList(ConnectPoint connectPoint, PortNumber uplinkPort,
+                                     SubscriberAndDeviceInformation sub) {
 
-        log.info("Provisioning vlans for subscriber {} on dev/port: {}",
-                 sub, port);
+        log.info("Provisioning vlans for subscriber {} on dev/port: {}", sub, connectPoint);
 
-        DeviceId deviceId = port.deviceId();
-        PortNumber subscriberPort = port.port();
-        VlanId deviceVlan = sub.sTag();
-        VlanId subscriberVlan = sub.cTag();
-        int techProfId = sub.technologyProfileId();
+        if (sub.uniTagList() == null || sub.uniTagList().isEmpty()) {
+            log.warn("Unitaglist doesn't exist for the subscriber {}", sub.id());
+            return;
+        }
 
-        BandwidthProfileInformation upstreamBpInfo = getBandwidthProfileInformation(sub.upstreamBandwidthProfile());
-        BandwidthProfileInformation downstreamBpInfo = getBandwidthProfileInformation(sub.downstreamBandwidthProfile());
+        DeviceId deviceId = connectPoint.deviceId();
+        PortNumber subscriberPort = connectPoint.port();
 
-        CompletableFuture<ObjectiveError> downFuture = new CompletableFuture();
-        CompletableFuture<ObjectiveError> upFuture = new CompletableFuture();
-        CompletableFuture<Object> upstreamMeterFuture = new CompletableFuture<>();
-        CompletableFuture<Object> downsteamMeterFuture = new CompletableFuture<>();
+        for (UniTagInformation uniTag : sub.uniTagList()) {
+            handleSubscriberFlows(deviceId, uplinkPort, subscriberPort, uniTag);
+        }
+    }
 
-        MeterId upstreamMeterId = createMeter(deviceId, upstreamBpInfo, upstreamMeterFuture);
-        MeterId downstreamMeterId = createMeter(deviceId, downstreamBpInfo, downsteamMeterFuture);
+    /**
+     * Finds the uni tag information and provisions the found information.
+     * If the uni tag information is not found, returns
+     *
+     * @param deviceId       the access device id
+     * @param uplinkPort     the nni port
+     * @param subscriberPort the uni port
+     * @param innerVlan      the pon c tag
+     * @param outerVlan      the pon s tag
+     * @param tpId           the technology profile id
+     */
+    private void provisionUniTagInformation(DeviceId deviceId, PortNumber uplinkPort,
+                                            PortNumber subscriberPort,
+                                            VlanId innerVlan,
+                                            VlanId outerVlan,
+                                            Integer tpId) {
 
-        //install upstream flows
-        upstreamMeterFuture.thenAcceptAsync(result -> {
-            if (result == null) {
-                log.info("Upstream Meter {} is in the device {}. " +
-                        "Sending subscriber flows.", upstreamMeterId, deviceId);
-                ForwardingObjective.Builder upFwd = upBuilder(uplinkPort, subscriberPort,
-                        subscriberVlan, deviceVlan,
-                        defaultVlan, upstreamMeterId, techProfId);
+        ConnectPoint cp = new ConnectPoint(deviceId, subscriberPort);
+        Optional<UniTagInformation> gotTagInformation = getUniTagInformation(cp, innerVlan, outerVlan, tpId);
+        if (!gotTagInformation.isPresent()) {
+            return;
+        }
+        UniTagInformation tagInformation = gotTagInformation.get();
+        handleSubscriberFlows(deviceId, uplinkPort, subscriberPort, tagInformation);
+    }
 
-
-                flowObjectiveService.forward(deviceId, upFwd.add(new ObjectiveContext() {
-                    @Override
-                    public void onSuccess(Objective objective) {
-                        upFuture.complete(null);
-                    }
-
-                    @Override
-                    public void onError(Objective objective, ObjectiveError error) {
-                        upFuture.complete(error);
-                    }
-                }));
-            } else {
-                log.error("Meter installation error for {} on {} while sending upstream flows. " +
-                        "Result {} and MeterId {}", deviceId, subscriberPort, result, upstreamMeterId);
-            }
-        });
-
-        //install downstream flows
-        downsteamMeterFuture.thenAcceptAsync(result -> {
-            if (result == null) {
-                log.info("Downstream Meter {} is in the device {}. " +
-                        "Sending subscriber flows.", downstreamMeterId, deviceId);
-                ForwardingObjective.Builder downFwd = downBuilder(uplinkPort, subscriberPort,
-                        subscriberVlan, deviceVlan,
-                        defaultVlan, downstreamMeterId, techProfId);
-
-                flowObjectiveService.forward(deviceId, downFwd.add(new ObjectiveContext() {
-                    @Override
-                    public void onSuccess(Objective objective) {
-                        downFuture.complete(null);
-                    }
-
-                    @Override
-                    public void onError(Objective objective, ObjectiveError error) {
-                        downFuture.complete(error);
-                    }
-                }));
-            } else {
-                log.error("Meter installation error for {} on {} while sending downstream flows. " +
-                        "Result {} and MeterId {}", deviceId, subscriberPort, result, downstreamMeterId);
-            }
-        });
-
-        //send eapol & dhcp & igmp flows
-        //send Subscriber Registered event
-        upFuture.thenAcceptBothAsync(downFuture, (upStatus, downStatus) -> {
-            if (upStatus == null && downStatus == null) {
-
-                if (upstreamMeterId != null) {
-                    //re-install Eapol authentication flow with the subscribers' upstream bandwidth profile
-                    processEapolFilteringObjectives(deviceId, subscriberPort, sub.upstreamBandwidthProfile(),
-                            null, sub.cTag(), true);
-
-                    processDhcpFilteringObjectives(deviceId, subscriberPort,
-                            upstreamMeterId, sub.technologyProfileId(), true, true);
-
-                    processIgmpFilteringObjectives(deviceId, subscriberPort,
-                            upstreamMeterId, sub.technologyProfileId(), true);
+    private void updateProgrammedSubscriber(ConnectPoint connectPoint, UniTagInformation tagInformation, Boolean add) {
+        programmedSubs.compute(connectPoint, (k, v) -> {
+            if (add) {
+                if (v == null) {
+                    v = Sets.newHashSet();
                 }
-
-                post(new AccessDeviceEvent(AccessDeviceEvent.Type.SUBSCRIBER_REGISTERED,
-                        deviceId,
-                        deviceVlan,
-                        subscriberVlan));
-
-            } else if (downStatus != null) {
-                log.error("Subscriber with vlan {} on device {} " +
-                                "on port {} failed downstream installation: {}",
-                        subscriberVlan, deviceId, subscriberPort, downStatus);
-            } else if (upStatus != null) {
-                log.error("Subscriber with vlan {} on device {} " +
-                                "on port {} failed upstream installation: {}",
-                        subscriberVlan, deviceId, subscriberPort, upStatus);
+                v.add(tagInformation);
+            } else {
+                if (v != null) {
+                    v.remove(tagInformation);
+                }
             }
-        }, oltInstallers);
+            return v;
+        });
     }
 
-    private MeterId createMeter(DeviceId deviceId, BandwidthProfileInformation bpInfo,
-                                CompletableFuture<Object> meterFuture) {
-        if (bpInfo == null) {
-            log.warn("Bandwidth profile information cannot be null when creating meter");
-            return null;
+    /**
+     * Installs a uni tag information flow.
+     *
+     * @param deviceId       the access device id
+     * @param uplinkPort     the nni port
+     * @param subscriberPort the uni port
+     * @param tagInfo        the uni tag information
+     */
+    private void handleSubscriberFlows(DeviceId deviceId, PortNumber uplinkPort, PortNumber subscriberPort,
+                                       UniTagInformation tagInfo) {
+
+        log.info("Provisioning vlan-based flows for the uniTagInformation {}", tagInfo);
+
+        Port port = deviceService.getPort(deviceId, subscriberPort);
+
+        if (mcastServiceName.equals(tagInfo.getServiceName())) {
+            // IGMP flows are taken care of along with VOD service
+            // Please note that for each service, Subscriber Registered event will be sent
+            post(new AccessDeviceEvent(AccessDeviceEvent.Type.SUBSCRIBER_UNI_TAG_REGISTERED,
+                    deviceId, port, tagInfo.getPonSTag(), tagInfo.getPonCTag(),
+                    tagInfo.getTechnologyProfileId()));
+            return;
         }
 
-        MeterId meterId = getMeterIdFromBpMapping(deviceId, bpInfo.id());
-        if (meterId != null) {
-            log.debug("Meter {} was previously created for bp {}", meterId,
-                     bpInfo.id());
-            meterFuture.complete(null);
-            return meterId;
-        }
-
-        List<Band> meterBands = createMeterBands(bpInfo);
-
-        MeterRequest meterRequest = DefaultMeterRequest.builder()
-                .withBands(meterBands)
-                .withUnit(Meter.Unit.KB_PER_SEC)
-                .withContext(new MeterContext() {
-                    @Override
-                    public void onSuccess(MeterRequest op) {
-                        log.debug("meter addition confirmed for bpInfo:{}", bpInfo);
-                        meterFuture.complete(null);
-                    }
-
-                    @Override
-                    public void onError(MeterRequest op, MeterFailReason reason) {
-                        meterFuture.complete(reason);
-                    }
-                })
-                .forDevice(deviceId)
-                .fromApp(appId)
-                .burst()
-                .add();
-
-        Meter meter = meterService.submit(meterRequest);
-        addMeterIdToBpMapping(deviceId, meter.id(), bpInfo.id());
-        log.info("Meter creation message sent for Meter Id {}", meter.id());
-        programmedMeters.add(MeterKey.key(deviceId, meter.id()));
-        return meter.id();
-    }
-
-    private List<Band> createMeterBands(BandwidthProfileInformation bpInfo) {
-        List<Band> meterBands = new ArrayList<>();
-
-        meterBands.add(createMeterBand(bpInfo.committedInformationRate(), bpInfo.committedBurstSize()));
-        meterBands.add(createMeterBand(bpInfo.exceededInformationRate(), bpInfo.exceededBurstSize()));
-        meterBands.add(createMeterBand(bpInfo.assuredInformationRate(), 0L));
-
-        return meterBands;
-    }
-
-    private Band createMeterBand(long rate, Long burst) {
-        return DefaultBand.builder()
-                .withRate(rate) //already Kbps
-                .burstSize(burst) // already Kbits
-                .ofType(Band.Type.DROP) // no matter
-                .build();
-    }
-
-    private ForwardingObjective.Builder downBuilder(PortNumber uplinkPort,
-                                                    PortNumber subscriberPort,
-                                                    VlanId subscriberVlan,
-                                                    VlanId deviceVlan,
-                                                    Optional<VlanId> defaultVlan,
-                                                    MeterId meterId,
-                                                    int techProfId) {
-        TrafficSelector downstream = DefaultTrafficSelector.builder()
-                .matchVlanId(deviceVlan)
-                .matchInPort(uplinkPort)
-                .matchInnerVlanId(subscriberVlan)
-                .build();
-
-        TrafficTreatment.Builder downstreamTreatmentBuilder = DefaultTrafficTreatment.builder()
-                .popVlan()
-                .setVlanId(defaultVlan.orElse(VlanId.vlanId((short) this.defaultVlan)))
-                .setOutput(subscriberPort);
-
-        if (meterId != null) {
-            downstreamTreatmentBuilder.meter(meterId);
-        }
-
-        downstreamTreatmentBuilder.writeMetadata(createMetadata(subscriberVlan, techProfId, subscriberPort), 0);
-
-        return DefaultForwardingObjective.builder()
-                .withFlag(ForwardingObjective.Flag.VERSATILE)
-                .withPriority(1000)
-                .makePermanent()
-                .withSelector(downstream)
-                .fromApp(appId)
-                .withTreatment(downstreamTreatmentBuilder.build());
-    }
-
-    private ForwardingObjective.Builder upBuilder(PortNumber uplinkPort,
-                                                  PortNumber subscriberPort,
-                                                  VlanId subscriberVlan,
-                                                  VlanId deviceVlan,
-                                                  Optional<VlanId> defaultVlan,
-                                                  MeterId meterId,
-                                                  int technologyProfileId) {
-
-
-        VlanId dVlan = defaultVlan.orElse(VlanId.vlanId((short) this.defaultVlan));
-
-        if (subscriberVlan.toShort() == 4096) {
-            dVlan = subscriberVlan;
-        }
-
-        TrafficSelector upstream = DefaultTrafficSelector.builder()
-                .matchVlanId(dVlan)
-                .matchInPort(subscriberPort)
-                .build();
-
-
-        TrafficTreatment.Builder upstreamTreatmentBuilder = DefaultTrafficTreatment.builder()
-                .pushVlan()
-                .setVlanId(subscriberVlan)
-                .pushVlan()
-                .setVlanId(deviceVlan)
-                .setOutput(uplinkPort);
-
-        if (meterId != null) {
-            upstreamTreatmentBuilder.meter(meterId);
-        }
-
-        upstreamTreatmentBuilder.writeMetadata(createMetadata(deviceVlan, technologyProfileId, uplinkPort), 0L);
-
-        return DefaultForwardingObjective.builder()
-                .withFlag(ForwardingObjective.Flag.VERSATILE)
-                .withPriority(1000)
-                .makePermanent()
-                .withSelector(upstream)
-                .fromApp(appId)
-                .withTreatment(upstreamTreatmentBuilder.build());
-    }
-
-    private void provisionTransparentFlows(DeviceId deviceId, PortNumber uplinkPort,
-                                           PortNumber subscriberPort,
-                                           VlanId innerVlan,
-                                           VlanId outerVlan) {
-
         ConnectPoint cp = new ConnectPoint(deviceId, subscriberPort);
 
-        SubscriberAndDeviceInformation subInfo = getSubscriber(cp);
-
-        BandwidthProfileInformation upstreamBpInfo = getBandwidthProfileInformation(
-                subInfo.upstreamBandwidthProfile());
-        BandwidthProfileInformation downstreamBpInfo = getBandwidthProfileInformation(
-                subInfo.downstreamBandwidthProfile());
+        BandwidthProfileInformation upstreamBpInfo =
+                getBandwidthProfileInformation(tagInfo.getUpstreamBandwidthProfile());
+        BandwidthProfileInformation downstreamBpInfo =
+                getBandwidthProfileInformation(tagInfo.getDownstreamBandwidthProfile());
 
         CompletableFuture<Object> upstreamMeterFuture = new CompletableFuture<>();
         CompletableFuture<Object> downsteamMeterFuture = new CompletableFuture<>();
-        CompletableFuture<ObjectiveError> upFuture = new CompletableFuture();
-        CompletableFuture<ObjectiveError> downFuture = new CompletableFuture();
+        CompletableFuture<ObjectiveError> upFuture = new CompletableFuture<>();
+        CompletableFuture<ObjectiveError> downFuture = new CompletableFuture<>();
 
-        MeterId upstreamMeterId = createMeter(deviceId, upstreamBpInfo, upstreamMeterFuture);
-        MeterId downstreamMeterId = createMeter(deviceId, downstreamBpInfo, downsteamMeterFuture);
+        MeterId upstreamMeterId = oltMeterService.createMeter(deviceId, upstreamBpInfo, upstreamMeterFuture);
+
+        MeterId downstreamMeterId = oltMeterService.createMeter(deviceId, downstreamBpInfo, downsteamMeterFuture);
 
         upstreamMeterFuture.thenAcceptAsync(result -> {
             if (result == null) {
                 log.info("Upstream Meter {} is sent to the device {}. " +
                         "Sending subscriber flows.", upstreamMeterId, deviceId);
 
-                ForwardingObjective.Builder upFwd = transparentUpBuilder(uplinkPort, subscriberPort,
-                        innerVlan, outerVlan, upstreamMeterId, subInfo);
+                ForwardingObjective.Builder upFwd =
+                        oltFlowService.createUpBuilder(uplinkPort, subscriberPort, upstreamMeterId, tagInfo);
 
                 flowObjectiveService.forward(deviceId, upFwd.add(new ObjectiveContext() {
                     @Override
                     public void onSuccess(Objective objective) {
+                        log.info("Upstream flow installed successfully");
                         upFuture.complete(null);
                     }
 
@@ -980,9 +635,15 @@
                     }
                 }));
 
+            } else if (upstreamBpInfo == null) {
+                log.warn("No meter installed since no Upstream BW Profile definition found for " +
+                                "ctag {} stag {} tpId {} and Device/port: {}:{}",
+                        tagInfo.getPonCTag(), tagInfo.getPonSTag(),
+                        tagInfo.getTechnologyProfileId(),
+                        deviceId, subscriberPort);
             } else {
-                log.error("Meter installation error for {} on {} while sending upstream flows. " +
-                        "Result {} and MeterId {}", deviceId, subscriberPort, result, upstreamMeterId);
+                log.warn("Meter installation error while sending upstream flows. " +
+                        "Result {} and MeterId {}", result, upstreamMeterId);
             }
         });
 
@@ -991,12 +652,13 @@
                 log.info("Downstream Meter {} is sent to the device {}. " +
                         "Sending subscriber flows.", downstreamMeterId, deviceId);
 
-                ForwardingObjective.Builder downFwd = transparentDownBuilder(uplinkPort, subscriberPort,
-                        innerVlan, outerVlan, downstreamMeterId, subInfo);
+                ForwardingObjective.Builder downFwd =
+                        oltFlowService.createDownBuilder(uplinkPort, subscriberPort, downstreamMeterId, tagInfo);
 
                 flowObjectiveService.forward(deviceId, downFwd.add(new ObjectiveContext() {
                     @Override
                     public void onSuccess(Objective objective) {
+                        log.info("Downstream flow installed successfully");
                         downFuture.complete(null);
                     }
 
@@ -1005,500 +667,94 @@
                         downFuture.complete(error);
                     }
                 }));
+
+            } else if (downstreamBpInfo == null) {
+                log.warn("No meter installed since no Downstream BW Profile definition found for " +
+                                "ctag {} stag {} tpId {} and Device/port: {}:{}",
+                        tagInfo.getPonCTag(), tagInfo.getPonSTag(),
+                        tagInfo.getTechnologyProfileId(),
+                        deviceId, subscriberPort);
             } else {
-                log.error("Meter installation error for {} on {} while sending downstream flows. " +
-                        "Result {} and MeterId {}", deviceId, subscriberPort, result, downstreamMeterId);
+                log.warn("Meter installation error while sending upstream flows. " +
+                        "Result {} and MeterId {}", result, downstreamMeterId);
             }
         });
 
-        additionalVlans.put(cp, new AbstractMap.SimpleEntry(outerVlan, innerVlan));
-
         upFuture.thenAcceptBothAsync(downFuture, (upStatus, downStatus) -> {
+            AccessDeviceEvent.Type type = AccessDeviceEvent.Type.SUBSCRIBER_UNI_TAG_REGISTERED;
             if (downStatus != null) {
                 log.error("Flow with innervlan {} and outerVlan {} on device {} " +
                                 "on port {} failed downstream installation: {}",
-                        innerVlan, outerVlan, deviceId, cp, downStatus);
+                        tagInfo.getPonCTag(), tagInfo.getPonSTag(), deviceId, cp, downStatus);
+                type = AccessDeviceEvent.Type.SUBSCRIBER_UNI_TAG_REGISTRATION_FAILED;
             } else if (upStatus != null) {
                 log.error("Flow with innerVlan {} and outerVlan {} on device {} " +
                                 "on port {} failed upstream installation: {}",
-                        innerVlan, outerVlan, deviceId, cp, upStatus);
+                        tagInfo.getPonCTag(), tagInfo.getPonSTag(), deviceId, cp, upStatus);
+                type = AccessDeviceEvent.Type.SUBSCRIBER_UNI_TAG_REGISTRATION_FAILED;
             } else {
-                processEapolFilteringObjectives(deviceId, subscriberPort, subInfo.upstreamBandwidthProfile(),
-                        null, subInfo.cTag(), true);
+                log.info("Upstream and downstream data plane flows are installed successfully.");
+                oltFlowService.processEapolFilteringObjectives(deviceId, subscriberPort,
+                        tagInfo.getUpstreamBandwidthProfile(), null, tagInfo.getPonCTag(), true);
+                if (tagInfo.getIsDhcpRequired()) {
+                    oltFlowService.processDhcpFilteringObjectives(deviceId, subscriberPort,
+                            upstreamMeterId, tagInfo, true, true);
+                }
 
-                // cache subscriber info
-                programmedSubs.put(cp, subInfo);
+                if (tagInfo.getIsIgmpRequired()) {
+                    oltFlowService.processIgmpFilteringObjectives(deviceId, subscriberPort, upstreamMeterId, tagInfo,
+                            true, true);
+                }
+                updateProgrammedSubscriber(cp, tagInfo, true);
+                post(new AccessDeviceEvent(type, deviceId, port, tagInfo.getPonSTag(), tagInfo.getPonCTag(),
+                        tagInfo.getTechnologyProfileId()));
             }
         }, oltInstallers);
-
     }
 
-    private ForwardingObjective.Builder transparentDownBuilder(PortNumber uplinkPort,
-                                                               PortNumber subscriberPort,
-                                                               VlanId innerVlan,
-                                                               VlanId outerVlan,
-                                                               MeterId downstreamMeterId,
-                                                               SubscriberAndDeviceInformation subInfo) {
-        TrafficSelector downstream = DefaultTrafficSelector.builder()
-                .matchVlanId(outerVlan)
-                .matchInPort(uplinkPort)
-                .matchInnerVlanId(innerVlan)
-                .build();
-
-        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
-        if (downstreamMeterId != null) {
-            tBuilder.meter(downstreamMeterId);
-        }
-
-        TrafficTreatment downstreamTreatment = tBuilder
-                .setOutput(subscriberPort)
-                .writeMetadata(createMetadata(subInfo.cTag(), subInfo.technologyProfileId(), subscriberPort), 0)
-                .build();
-
-        return DefaultForwardingObjective.builder()
-                .withFlag(ForwardingObjective.Flag.VERSATILE)
-                .withPriority(1000)
-                .makePermanent()
-                .withSelector(downstream)
-                .fromApp(appId)
-                .withTreatment(downstreamTreatment);
-    }
-
-    private ForwardingObjective.Builder transparentUpBuilder(PortNumber uplinkPort,
-                                                             PortNumber subscriberPort,
-                                                             VlanId innerVlan,
-                                                             VlanId outerVlan,
-                                                             MeterId upstreamMeterId,
-                                                             SubscriberAndDeviceInformation subInfo) {
-
-        TrafficSelector upstream = DefaultTrafficSelector.builder()
-                .matchVlanId(outerVlan)
-                .matchInPort(subscriberPort)
-                .matchInnerVlanId(innerVlan)
-                .build();
-
-        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
-        if (upstreamMeterId != null) {
-            tBuilder.meter(upstreamMeterId);
-        }
-
-        TrafficTreatment upstreamTreatment = tBuilder
-                .setOutput(uplinkPort)
-                .writeMetadata(createMetadata(subInfo.sTag(), subInfo.technologyProfileId(), uplinkPort), 0)
-                .build();
-
-        return DefaultForwardingObjective.builder()
-                .withFlag(ForwardingObjective.Flag.VERSATILE)
-                .withPriority(1000)
-                .makePermanent()
-                .withSelector(upstream)
-                .fromApp(appId)
-                .withTreatment(upstreamTreatment);
-    }
-
-    private void unprovisionTransparentFlows(DeviceId deviceId, PortNumber uplink,
-                                             PortNumber subscriberPort, VlanId innerVlan,
-                                             VlanId outerVlan) {
-
-        ConnectPoint cp = new ConnectPoint(deviceId, subscriberPort);
-
-        SubscriberAndDeviceInformation subInfo = programmedSubs.get(cp);
+    /**
+     * Checks the subscriber uni tag list and find the uni tag information.
+     * using the pon c tag, pon s tag and the technology profile id
+     * May return Optional<null>
+     *
+     * @param cp        the connection point of the subscriber
+     * @param innerVlan pon c tag
+     * @param outerVlan pon s tag
+     * @param tpId      the technology profile id
+     * @return the found uni tag information
+     */
+    private Optional<UniTagInformation> getUniTagInformation(ConnectPoint cp, VlanId innerVlan, VlanId outerVlan,
+                                                             int tpId) {
+        log.info("Getting uni tag information for cp: {}, innerVlan: {}, outerVlan: {}, tpId: {}", cp, innerVlan,
+                outerVlan, tpId);
+        SubscriberAndDeviceInformation subInfo = getSubscriber(cp);
         if (subInfo == null) {
-            log.warn("Subscriber is not programmed before for the connectPoint {}", cp);
-            return;
+            log.warn("Subscriber information doesn't exist for the connect point {}", cp);
+            return Optional.empty();
         }
 
-        additionalVlans.remove(cp, new AbstractMap.SimpleEntry(outerVlan, innerVlan));
+        List<UniTagInformation> uniTagList = subInfo.uniTagList();
+        if (uniTagList == null) {
+            log.warn("Uni tag list is not found for the subscriber {}", subInfo.id());
+            return Optional.empty();
+        }
 
-        CompletableFuture<ObjectiveError> downFuture = new CompletableFuture();
-        CompletableFuture<ObjectiveError> upFuture = new CompletableFuture();
-
-        MeterId upstreamMeterId = getMeterIdFromBpMapping(deviceId, subInfo.upstreamBandwidthProfile());
-        MeterId downstreamMeterId = getMeterIdFromBpMapping(deviceId, subInfo.downstreamBandwidthProfile());
-
-        ForwardingObjective.Builder upFwd = transparentUpBuilder(uplink, subscriberPort,
-                innerVlan, outerVlan, upstreamMeterId, subInfo);
-        ForwardingObjective.Builder downFwd = transparentDownBuilder(uplink, subscriberPort,
-                innerVlan, outerVlan, downstreamMeterId, subInfo);
-
-        flowObjectiveService.forward(deviceId, upFwd.remove(new ObjectiveContext() {
-            @Override
-            public void onSuccess(Objective objective) {
-                upFuture.complete(null);
-            }
-
-            @Override
-            public void onError(Objective objective, ObjectiveError error) {
-                upFuture.complete(error);
-            }
-        }));
-
-        flowObjectiveService.forward(deviceId, downFwd.remove(new ObjectiveContext() {
-            @Override
-            public void onSuccess(Objective objective) {
-                downFuture.complete(null);
-            }
-
-            @Override
-            public void onError(Objective objective, ObjectiveError error) {
-                downFuture.complete(error);
-            }
-        }));
-
-        upFuture.thenAcceptBothAsync(downFuture, (upStatus, downStatus) -> {
-            if (downStatus != null) {
-                log.error("Flow with innerVlan {} and outerVlan {} on device {} " +
-                                "on port {} failed downstream uninstallation: {}",
-                        innerVlan, outerVlan, deviceId, subscriberPort, downStatus);
-            } else if (upStatus != null) {
-                log.error("Flow with innerVlan {} and outerVlan {} on device {} " +
-                                "on port {} failed upstream uninstallation: {}",
-                        innerVlan, outerVlan, deviceId, subscriberPort, upStatus);
-            }
-        }, oltInstallers);
-
-        //re-install eapol
-        processEapolFilteringObjectives(deviceId, subscriberPort,
-                subInfo.upstreamBandwidthProfile(), null, subInfo.cTag(), false);
-        processEapolFilteringObjectives(deviceId, subscriberPort, defaultBpId,
-                null, VlanId.vlanId(EAPOL_DEFAULT_VLAN), true);
-
-        programmedMeters.remove(MeterKey.key(deviceId, upstreamMeterId));
-        programmedMeters.remove(MeterKey.key(deviceId, downstreamMeterId));
-    }
-
-    private int getDefaultTechProfileId(DeviceId devId, PortNumber portNumber) {
-        Port port = deviceService.getPort(devId, portNumber);
-        if (port != null) {
-            SubscriberAndDeviceInformation info = subsService.get(port.annotations().value(AnnotationKeys.PORT_NAME));
-            if (info != null && info.technologyProfileId() != -1) {
-                return info.technologyProfileId();
+        UniTagInformation service = null;
+        for (UniTagInformation tagInfo : subInfo.uniTagList()) {
+            if (innerVlan.equals(tagInfo.getPonCTag()) && outerVlan.equals(tagInfo.getPonSTag())
+                    && tpId == tagInfo.getTechnologyProfileId()) {
+                service = tagInfo;
+                break;
             }
         }
-        return defaultTechProfileId;
-    }
 
-    /**
-     * Returns the write metadata value including tech profile reference and innerVlan.
-     * For param cVlan, null can be sent
-     *
-     * @param cVlan         c (customer) tag of one subscriber
-     * @param techProfileId tech profile id of one subscriber
-     * @return the write metadata value including tech profile reference and innerVlan
-     */
-    private Long createTechProfValueForWm(VlanId cVlan, int techProfileId) {
-        if (cVlan == null) {
-            return (long) techProfileId << 32;
-        }
-        return ((long) (cVlan.id()) << 48 | (long) techProfileId << 32);
-    }
-
-    /**
-     * Trap eapol authentication packets to the controller.
-     *
-     * @param devId        the device identifier
-     * @param portNumber   the port for which this trap flow is designated
-     * @param bpId         bandwidth profile id to add the related meter to the flow
-     * @param filterFuture completable future for this filtering objective operation
-     * @param vlanId       the default or customer tag for a subscriber
-     * @param install      true to install the flow, false to remove the flow
-     */
-    private void processEapolFilteringObjectives(DeviceId devId, PortNumber portNumber, String bpId,
-                                                 CompletableFuture<ObjectiveError> filterFuture,
-                                                 VlanId vlanId, boolean install) {
-        if (!enableEapol) {
-            log.debug("Eapol filtering is disabled.");
-            if (filterFuture != null) {
-                filterFuture.complete(null);
-            }
-            return;
+        if (service == null) {
+            log.warn("SADIS doesn't include the service with ponCtag {} ponStag {} and tpId {}",
+                    innerVlan, outerVlan, tpId);
+            return Optional.empty();
         }
 
-        if (!mastershipService.isLocalMaster(devId)) {
-            return;
-        }
-        DefaultFilteringObjective.Builder builder = DefaultFilteringObjective.builder();
-        TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
-        CompletableFuture<Object> meterFuture = new CompletableFuture<>();
-
-        BandwidthProfileInformation bpInfo = getBandwidthProfileInformation(bpId);
-        if (bpInfo == null) {
-            log.warn("Bandwidth profile {} is not found. Authentication flow"
-                    + " will not be installed", bpId);
-            return;
-        }
-
-        // check if meter exists and create it only for an install
-        MeterId meterId = getMeterIdFromBpMapping(devId, bpInfo.id());
-        if (meterId == null) {
-            if (install) {
-                meterId = createMeter(devId, bpInfo, meterFuture);
-                treatmentBuilder.meter(meterId);
-            } else {
-                // this case should not happen as the request to remove an eapol
-                // flow should mean that the flow points to a meter that exists.
-                // Nevertheless we can still delete the flow as we only need the
-                // correct 'match' to do so.
-                log.warn("Unknown meter id for bp {}, still proceeding with "
-                        + "delete of eapol flow for {}/{}", bpInfo.id(), devId,
-                         portNumber);
-                meterFuture.complete(null);
-            }
-        } else {
-            log.debug("Meter {} was previously created for bp {}", meterId,
-                      bpInfo.id());
-            treatmentBuilder.meter(meterId);
-            meterFuture.complete(null);
-        }
-
-        final MeterId mId = meterId;
-        meterFuture.thenAcceptAsync(result -> {
-            if (result == null) {
-                log.info("Meter {} for {} on {}/{} exists. {} EAPOL trap flow",
-                         mId, bpId, devId, portNumber,
-                        (install) ? "Installing " : "Removing ");
-                int techProfileId = getDefaultTechProfileId(devId, portNumber);
-
-                //Authentication trap flow uses only tech profile id as write metadata value
-                FilteringObjective eapol = (install ? builder.permit() : builder.deny())
-                        .withKey(Criteria.matchInPort(portNumber))
-                        .addCondition(Criteria.matchEthType(EthType.EtherType.EAPOL.ethType()))
-                        .addCondition(Criteria.matchVlanId(vlanId))
-                        .withMeta(treatmentBuilder
-                                .writeMetadata(createTechProfValueForWm(vlanId, techProfileId), 0)
-                                .setOutput(PortNumber.CONTROLLER).build())
-                        .fromApp(appId)
-                        .withPriority(10000)
-                        .add(new ObjectiveContext() {
-                            @Override
-                            public void onSuccess(Objective objective) {
-                                log.info("Eapol filter for {} on {} {} with meter {}.",
-                                        devId, portNumber, (install) ? INSTALLED : REMOVED, mId);
-                                if (filterFuture != null) {
-                                    filterFuture.complete(null);
-                                }
-                            }
-
-                            @Override
-                            public void onError(Objective objective, ObjectiveError error) {
-                                log.info("Eapol filter for {} on {} with meter {} failed {} because {}",
-                                        devId, portNumber, mId, (install) ? INSTALLATION : REMOVAL,
-                                        error);
-                                if (filterFuture != null) {
-                                    filterFuture.complete(error);
-                                }
-                            }
-                        });
-
-                flowObjectiveService.filter(devId, eapol);
-            } else {
-                log.error("Meter installation error for {} on {} while sending eapol trap flow. " +
-                        "Result {} and MeterId {}", devId, portNumber, result, mId);
-            }
-        });
-    }
-
-    /**
-     * Installs trap filtering objectives for particular traffic types on an
-     * NNI port.
-     *
-     * @param devId   device ID
-     * @param port    port number
-     * @param install true to install, false to remove
-     */
-    private void processNniFilteringObjectives(DeviceId devId, PortNumber port, boolean install) {
-        processLldpFilteringObjective(devId, port, install);
-        processDhcpFilteringObjectives(devId, port, null, -1, install, false);
-    }
-
-    private void processLldpFilteringObjective(DeviceId devId, PortNumber port, boolean install) {
-        if (!mastershipService.isLocalMaster(devId)) {
-            return;
-        }
-        DefaultFilteringObjective.Builder builder = DefaultFilteringObjective.builder();
-
-        FilteringObjective lldp = (install ? builder.permit() : builder.deny())
-                .withKey(Criteria.matchInPort(port))
-                .addCondition(Criteria.matchEthType(EthType.EtherType.LLDP.ethType()))
-                .withMeta(DefaultTrafficTreatment.builder()
-                        .setOutput(PortNumber.CONTROLLER).build())
-                .fromApp(appId)
-                .withPriority(10000)
-                .add(new ObjectiveContext() {
-                    @Override
-                    public void onSuccess(Objective objective) {
-                        log.info("LLDP filter for device {} on port {} {}.",
-                                devId, port, (install) ? INSTALLED : REMOVED);
-                    }
-
-                    @Override
-                    public void onError(Objective objective, ObjectiveError error) {
-                        log.info("LLDP filter for device {} on port {} failed {} because {}",
-                                devId, port, (install) ? INSTALLATION : REMOVAL,
-                                error);
-                    }
-                });
-
-        flowObjectiveService.filter(devId, lldp);
-
-    }
-
-    /**
-     * Trap dhcp packets to the controller.
-     *
-     * @param devId           the device identifier
-     * @param port            the port for which this trap flow is designated
-     * @param upstreamMeterId the upstream meter id that includes the upstream
-     *                        bandwidth profile values such as PIR,CIR. If no meter id needs to be referenced,
-     *                        null can be sent
-     * @param techProfileId   the technology profile id that is used to create write
-     *                        metadata instruction value. If no tech profile id needs to be referenced,
-     *                        -1 can be sent
-     * @param install         true to install the flow, false to remove the flow
-     * @param upstream        true if trapped packets are flowing upstream towards
-     *                        server, false if packets are flowing downstream towards client
-     */
-    private void processDhcpFilteringObjectives(DeviceId devId, PortNumber port,
-                                                MeterId upstreamMeterId,
-                                                int techProfileId,
-                                                boolean install,
-                                                boolean upstream) {
-
-        if (!enableDhcpOnProvisioning) {
-            log.debug("Dhcp provisioning is disabled.");
-            return;
-        }
-
-        if (!mastershipService.isLocalMaster(devId)) {
-            return;
-        }
-
-        if (enableDhcpV4) {
-            int udpSrc = (upstream) ? 68 : 67;
-            int udpDst = (upstream) ? 67 : 68;
-
-            EthType ethType = EthType.EtherType.IPV4.ethType();
-            byte protocol = IPv4.PROTOCOL_UDP;
-
-            this.addDhcpFilteringObjectives(devId, port, udpSrc, udpDst, ethType,
-                    upstreamMeterId, techProfileId, protocol, install);
-        }
-
-        if (enableDhcpV6) {
-            int udpSrc = (upstream) ? 547 : 546;
-            int udpDst = (upstream) ? 546 : 547;
-
-            EthType ethType = EthType.EtherType.IPV6.ethType();
-            byte protocol = IPv6.PROTOCOL_UDP;
-
-            this.addDhcpFilteringObjectives(devId, port, udpSrc, udpDst, ethType,
-                    upstreamMeterId, techProfileId, protocol, install);
-        }
-
-    }
-
-    private void addDhcpFilteringObjectives(DeviceId devId,
-                                            PortNumber port,
-                                            int udpSrc,
-                                            int udpDst,
-                                            EthType ethType,
-                                            MeterId upstreamMeterId,
-                                            int techProfileId,
-                                            byte protocol,
-                                            boolean install) {
-
-        DefaultFilteringObjective.Builder builder = DefaultFilteringObjective.builder();
-        TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
-
-        if (upstreamMeterId != null) {
-            treatmentBuilder.meter(upstreamMeterId);
-        }
-
-        if (techProfileId != -1) {
-            treatmentBuilder.writeMetadata(createTechProfValueForWm(null, techProfileId), 0);
-        }
-
-        FilteringObjective dhcpUpstream = (install ? builder.permit() : builder.deny())
-                .withKey(Criteria.matchInPort(port))
-                .addCondition(Criteria.matchEthType(ethType))
-                .addCondition(Criteria.matchIPProtocol(protocol))
-                .addCondition(Criteria.matchUdpSrc(TpPort.tpPort(udpSrc)))
-                .addCondition(Criteria.matchUdpDst(TpPort.tpPort(udpDst)))
-                .withMeta(treatmentBuilder
-                        .setOutput(PortNumber.CONTROLLER).build())
-                .fromApp(appId)
-                .withPriority(10000)
-                .add(new ObjectiveContext() {
-                    @Override
-                    public void onSuccess(Objective objective) {
-                        log.info("DHCP {} filter for device {} on port {} {}.",
-                                (ethType.equals(EthType.EtherType.IPV4.ethType())) ? "v4" : "v6",
-                                devId, port, (install) ? INSTALLED : REMOVED);
-                    }
-
-                    @Override
-                    public void onError(Objective objective, ObjectiveError error) {
-                        log.info("DHCP {} filter for device {} on port {} failed {} because {}",
-                                (ethType.equals(EthType.EtherType.IPV4.ethType())) ? "v4" : "v6",
-                                devId, port, (install) ? INSTALLATION : REMOVAL,
-                                error);
-                    }
-                });
-
-        flowObjectiveService.filter(devId, dhcpUpstream);
-    }
-
-    private void processIgmpFilteringObjectives(DeviceId devId, PortNumber port,
-                                                MeterId upstreamMeterId,
-                                                int techProfileId,
-                                                boolean install) {
-
-        if (!enableIgmpOnProvisioning) {
-            log.debug("Igmp provisioning is disabled.");
-            return;
-        }
-
-        if (!mastershipService.isLocalMaster(devId)) {
-            return;
-        }
-
-        DefaultFilteringObjective.Builder builder = DefaultFilteringObjective.builder();
-        TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
-
-        if (upstreamMeterId != null) {
-            treatmentBuilder.meter(upstreamMeterId);
-        }
-
-        if (techProfileId != -1) {
-            treatmentBuilder.writeMetadata(createTechProfValueForWm(null, techProfileId), 0);
-        }
-
-        builder = install ? builder.permit() : builder.deny();
-
-        FilteringObjective igmp = builder
-                .withKey(Criteria.matchInPort(port))
-                .addCondition(Criteria.matchEthType(EthType.EtherType.IPV4.ethType()))
-                .addCondition(Criteria.matchIPProtocol(IPv4.PROTOCOL_IGMP))
-                .withMeta(treatmentBuilder
-                        .setOutput(PortNumber.CONTROLLER).build())
-                .fromApp(appId)
-                .withPriority(10000)
-                .add(new ObjectiveContext() {
-                    @Override
-                    public void onSuccess(Objective objective) {
-                        log.info("Igmp filter for {} on {} {}.",
-                                devId, port, (install) ? INSTALLED : REMOVED);
-                    }
-
-                    @Override
-                    public void onError(Objective objective, ObjectiveError error) {
-                        log.info("Igmp filter for {} on {} failed {} because {}.",
-                                devId, port, (install) ? INSTALLATION : REMOVAL,
-                                error);
-                    }
-                });
-
-        flowObjectiveService.filter(devId, igmp);
+        return Optional.of(service);
     }
 
     /**
@@ -1514,16 +770,20 @@
         }
         // check if this device is provisioned in Sadis
         SubscriberAndDeviceInformation deviceInfo = getOltInfo(dev);
-        log.debug("checkAndCreateDeviceFlows: deviceInfo {}", deviceInfo);
+        log.info("checkAndCreateDeviceFlows: deviceInfo {}", deviceInfo);
 
         if (deviceInfo != null) {
             // This is an OLT device as per Sadis, we create flows for UNI and NNI ports
             for (Port p : deviceService.getPorts(dev.id())) {
+                if (PortNumber.LOCAL.equals(p.number())) {
+                    continue;
+                }
                 if (isUniPort(dev, p)) {
-                    processEapolFilteringObjectives(dev.id(), p.number(), defaultBpId, null,
+                    log.info("Creating Eapol for the uni {}", p);
+                    oltFlowService.processEapolFilteringObjectives(dev.id(), p.number(), defaultBpId, null,
                             VlanId.vlanId(EAPOL_DEFAULT_VLAN), true);
                 } else {
-                    processNniFilteringObjectives(dev.id(), p.number(), true);
+                    oltFlowService.processNniFilteringObjectives(dev.id(), p.number(), true);
                 }
             }
         }
@@ -1574,22 +834,12 @@
     }
 
     /**
-     * Write metadata instruction value (metadata) is 8 bytes.
-     * <p>
-     * MS 2 bytes: C Tag
-     * Next 2 bytes: Technology Profile Id
-     * Next 4 bytes: Port number (uni or nni)
+     * Checks whether the given port of the device is a uni port or not.
+     *
+     * @param d the access device
+     * @param p the port of the device
+     * @return true if the given port is a uni port
      */
-
-    private Long createMetadata(VlanId innerVlan, int techProfileId, PortNumber egressPort) {
-
-        if (techProfileId == -1) {
-            techProfileId = DEFAULT_TP_ID;
-        }
-
-        return ((long) (innerVlan.id()) << 48 | (long) techProfileId << 32) | egressPort.toLong();
-    }
-
     private boolean isUniPort(Device d, Port p) {
         Port ulPort = getUplinkPort(d);
         if (ulPort != null) {
@@ -1598,64 +848,18 @@
         return false;
     }
 
+    /**
+     * Gets the given device details from SADIS.
+     * If the device is not found, returns null
+     *
+     * @param dev the access device
+     * @return the olt information
+     */
     private SubscriberAndDeviceInformation getOltInfo(Device dev) {
         String devSerialNo = dev.serialNumber();
         return subsService.get(devSerialNo);
     }
 
-    private MeterId getMeterIdFromBpMapping(DeviceId deviceId, String bandwidthProfile) {
-        if (bpInfoToMeter.get(bandwidthProfile) == null) {
-            log.warn("Bandwidth Profile '{}' is not currently mapped to a meter",
-                      bandwidthProfile);
-            return null;
-        }
-
-        Optional<MeterKey> meterKeyForDevice = bpInfoToMeter.get(bandwidthProfile)
-                .stream()
-                .filter(meterKey -> meterKey.deviceId().equals(deviceId))
-                .findFirst();
-        if (meterKeyForDevice.isPresent()) {
-            log.debug("Found meter {} for bandwidth profile {}",
-                      meterKeyForDevice.get().meterId(), bandwidthProfile);
-            return meterKeyForDevice.get().meterId();
-        } else {
-            log.warn("Bandwidth profile '{}' is not currently mapped to a meter",
-                     bandwidthProfile);
-            return null;
-        }
-    }
-
-    private void addMeterIdToBpMapping(DeviceId deviceId, MeterId meterId, String bandwidthProfile) {
-
-        if (bpInfoToMeter.get(bandwidthProfile) == null) {
-            bpInfoToMeter.put(bandwidthProfile,
-                    new ArrayList<>(Arrays.asList(MeterKey.key(deviceId, meterId))));
-        } else {
-            List<MeterKey> meterKeyListForBp = bpInfoToMeter.get(bandwidthProfile);
-            meterKeyListForBp.add(MeterKey.key(deviceId, meterId));
-        }
-    }
-
-    /**
-     * Removes meter key from programmed meters. If the meter has no reference
-     * count in the data plane and is not present in the programmed-meters store
-     * it will be removed.
-     *
-     * @param deviceId device identifier
-     * @param bandwidthProfileId name of bandwidth profile
-     */
-    private void removeMeterIdFromPrgMeters(DeviceId deviceId,
-                                             String bandwidthProfileId) {
-        List<MeterKey> meterKeysForBp = bpInfoToMeter.get(bandwidthProfileId);
-        if (meterKeysForBp != null) {
-            meterKeysForBp.stream()
-                    .filter(meterKey -> meterKey.deviceId().equals(deviceId))
-                    .findFirst().ifPresent(mk -> {
-                programmedMeters.remove(mk);
-            });
-        }
-    }
-
     private class InternalDeviceListener implements DeviceListener {
         private Set<DeviceId> programmedDevices = Sets.newConcurrentHashSet();
 
@@ -1665,11 +869,16 @@
                 DeviceId devId = event.subject().id();
                 Device dev = event.subject();
                 Port port = event.port();
+                DeviceEvent.Type eventType = event.type();
 
-                if (event.type() == DeviceEvent.Type.PORT_STATS_UPDATED) {
+                if (DeviceEvent.Type.PORT_STATS_UPDATED.equals(eventType) ||
+                        DeviceEvent.Type.DEVICE_SUSPENDED.equals(eventType) ||
+                        DeviceEvent.Type.DEVICE_UPDATED.equals(eventType)) {
                     return;
                 }
 
+                log.debug("OLT got {} event for {} {}", eventType, event.subject(), event.port());
+
                 if (getOltInfo(dev) == null) {
                     // it's possible that we got an event for a previously
                     // programmed OLT that is no longer available in SADIS
@@ -1681,112 +890,106 @@
                     }
                 }
 
-                log.debug("OLT got {} event for {}: {}", event.type(),
-                          event.subject().id(), event);
-
                 switch (event.type()) {
                     //TODO: Port handling and bookkeeping should be improved once
                     // olt firmware handles correct behaviour.
                     case PORT_ADDED:
                         if (isUniPort(dev, port)) {
                             post(new AccessDeviceEvent(AccessDeviceEvent.Type.UNI_ADDED, devId, port));
-                            if (port.isEnabled()) {
-                                processEapolFilteringObjectives(devId, port.number(), defaultBpId,
+
+                            if (port.isEnabled() && !port.number().equals(PortNumber.LOCAL)) {
+                                log.info("eapol will be sent for port added {}", port);
+                                oltFlowService.processEapolFilteringObjectives(devId, port.number(), defaultBpId,
                                         null, VlanId.vlanId(EAPOL_DEFAULT_VLAN), true);
                             }
                         } else {
-                            checkAndCreateDeviceFlows(dev);
+                            SubscriberAndDeviceInformation deviceInfo = getOltInfo(dev);
+                            if (deviceInfo != null) {
+                                oltFlowService.processNniFilteringObjectives(dev.id(), port.number(), true);
+                            }
                         }
                         break;
                     case PORT_REMOVED:
                         if (isUniPort(dev, port)) {
                             removeSubscriber(new ConnectPoint(devId, port.number()));
-                            processEapolFilteringObjectives(devId, port.number(), defaultBpId,
+                            log.info("eapol will be send for port removed", port);
+                            oltFlowService.processEapolFilteringObjectives(devId, port.number(), defaultBpId,
                                     null, VlanId.vlanId(EAPOL_DEFAULT_VLAN), false);
 
                             post(new AccessDeviceEvent(AccessDeviceEvent.Type.UNI_REMOVED, devId, port));
                         }
-
                         break;
                     case PORT_UPDATED:
                         if (!isUniPort(dev, port)) {
                             break;
                         }
-
-                        SubscriberAndDeviceInformation sub = programmedSubs
+                        Set<UniTagInformation> uniTagInformationSet = programmedSubs
                                 .get(new ConnectPoint(devId, port.number()));
-                        VlanId vlanId = sub == null ? VlanId.vlanId(EAPOL_DEFAULT_VLAN) : sub.cTag();
-
-                        String bpId = getCurrentBandwidthProfile(new ConnectPoint(devId, port.number()));
-
+                        if (uniTagInformationSet == null || uniTagInformationSet.isEmpty()) {
+                            if (port.isEnabled() && !port.number().equals(PortNumber.LOCAL)) {
+                                log.info("eapol will be processed for port updated {}", port);
+                                oltFlowService.processEapolFilteringObjectives(devId, port.number(), DEFAULT_BP_ID,
+                                        null, VlanId.vlanId(EAPOL_DEFAULT_VLAN), port.isEnabled());
+                            }
+                        } else {
+                            log.info("eapol will be processed for port updated {}", port);
+                            uniTagInformationSet.forEach(uniTag ->
+                                    oltFlowService.processEapolFilteringObjectives(devId, port.number(),
+                                            uniTag.getUpstreamBandwidthProfile(), null,
+                                            uniTag.getPonCTag(), port.isEnabled()));
+                        }
                         if (port.isEnabled()) {
-                            processEapolFilteringObjectives(devId, port.number(), bpId,
-                                    null, vlanId, true);
-
                             post(new AccessDeviceEvent(AccessDeviceEvent.Type.UNI_ADDED, devId, port));
                         } else {
-                            processEapolFilteringObjectives(devId, port.number(), bpId,
-                                    null, vlanId, false);
                             post(new AccessDeviceEvent(AccessDeviceEvent.Type.UNI_REMOVED, devId, port));
                         }
                         break;
                     case DEVICE_ADDED:
-                        post(new AccessDeviceEvent(
-                                AccessDeviceEvent.Type.DEVICE_CONNECTED, devId,
-                                null, null));
-                        programmedDevices.add(devId);
-                        // Send UNI_ADDED events for all existing ports
-                        deviceService.getPorts(devId).stream()
-                                .filter(p -> isUniPort(dev, p))
-                                .filter(Port::isEnabled)
-                                .forEach(p -> post(new AccessDeviceEvent(
-                                        AccessDeviceEvent.Type.UNI_ADDED, devId, p)));
-
-                        checkAndCreateDeviceFlows(dev);
+                        handleDeviceConnection(dev, true);
                         break;
                     case DEVICE_REMOVED:
-                        deviceService.getPorts(devId).stream()
-                                .filter(p -> isUniPort(dev, p))
-                                .forEach(p -> post(new AccessDeviceEvent(
-                                        AccessDeviceEvent.Type.UNI_REMOVED, devId, p)));
-                        programmedDevices.remove(devId);
-                        removeAllSubscribers(devId);
-                        removeDeviceMetersFromBpMap(devId);
-                        post(new AccessDeviceEvent(
-                                AccessDeviceEvent.Type.DEVICE_DISCONNECTED, devId,
-                                null, null));
+                        handleDeviceDisconnection(dev, true);
                         break;
                     case DEVICE_AVAILABILITY_CHANGED:
                         if (deviceService.isAvailable(devId)) {
-                            post(new AccessDeviceEvent(
-                                    AccessDeviceEvent.Type.DEVICE_CONNECTED, devId,
-                                    null, null));
-                            programmedDevices.add(devId);
-                            checkAndCreateDeviceFlows(dev);
+                            handleDeviceConnection(dev, false);
                         } else {
-                            programmedDevices.remove(devId);
-                            removeAllSubscribers(devId);
-                            removeDeviceMetersFromBpMap(devId);
-                            post(new AccessDeviceEvent(
-                                    AccessDeviceEvent.Type.DEVICE_DISCONNECTED, devId,
-                                    null, null));
+                            handleDeviceDisconnection(dev, false);
                         }
                         break;
-                    case DEVICE_UPDATED:
-                    case DEVICE_SUSPENDED:
-                    case PORT_STATS_UPDATED:
                     default:
                         return;
                 }
             });
         }
 
-        private String getCurrentBandwidthProfile(ConnectPoint connectPoint) {
-            SubscriberAndDeviceInformation sub = programmedSubs.get(connectPoint);
-            if (sub != null) {
-                return sub.upstreamBandwidthProfile();
+        private void sendUniEvent(Device device, AccessDeviceEvent.Type eventType) {
+            deviceService.getPorts(device.id()).stream()
+                    .filter(p -> !PortNumber.LOCAL.equals(p.number()))
+                    .filter(p -> isUniPort(device, p))
+                    .forEach(p -> post(new AccessDeviceEvent(eventType, device.id(), p)));
+        }
+
+        private void handleDeviceDisconnection(Device device, boolean sendUniEvent) {
+            programmedDevices.remove(device.id());
+            removeAllSubscribers(device.id());
+            post(new AccessDeviceEvent(
+                    AccessDeviceEvent.Type.DEVICE_DISCONNECTED, device.id(),
+                    null, null, null));
+            if (sendUniEvent) {
+                sendUniEvent(device, AccessDeviceEvent.Type.UNI_REMOVED);
             }
-            return defaultBpId;
+        }
+
+        private void handleDeviceConnection(Device dev, boolean sendUniEvent) {
+            post(new AccessDeviceEvent(
+                    AccessDeviceEvent.Type.DEVICE_CONNECTED, dev.id(),
+                    null, null, null));
+            programmedDevices.add(dev.id());
+            checkAndCreateDeviceFlows(dev);
+            if (sendUniEvent) {
+                sendUniEvent(dev, AccessDeviceEvent.Type.UNI_ADDED);
+            }
         }
 
         private void removeAllSubscribers(DeviceId deviceId) {
@@ -1797,64 +1000,5 @@
             connectPoints.forEach(cp -> programmedSubs.remove(cp));
         }
 
-        private void removeDeviceMetersFromBpMap(DeviceId deviceId) {
-            bpInfoToMeter.values().forEach(meterKeys -> meterKeys.stream()
-                    .filter(meterKey -> meterKey.deviceId().equals(deviceId)).findFirst().
-                            ifPresent(mk -> {
-                                meterKeys.remove(mk);
-                                programmedMeters.remove(mk);
-                                log.info("Deleted from the internal map. MeterKey {}", mk);
-                                log.info("Programmed meters {}", programmedMeters);
-                            }));
-        }
-
-    }
-
-    private class InternalMeterListener implements MeterListener {
-
-        @Override
-        public void event(MeterEvent meterEvent) {
-            if (deleteMeters && MeterEvent.Type.METER_REFERENCE_COUNT_ZERO.equals(meterEvent.type())) {
-                log.debug("Zero Count Meter {} received", meterEvent.subject());
-                Meter meter = meterEvent.subject();
-                if (meter != null && appId.equals(meter.appId()) &&
-                        !programmedMeters.contains(MeterKey.key(meter.deviceId(), meter.id()))) {
-                    log.info("Deleting unreferenced, no longer programmed Meter {}",
-                             meter.id());
-                    deleteMeter(meter.deviceId(), meter.id());
-                }
-            } else if (MeterEvent.Type.METER_REMOVED.equals(meterEvent.type())) {
-                log.debug("Meter removed event is received. Meter is {}",
-                          meterEvent.subject());
-                removeMeterFromBpMap(meterEvent.subject());
-            }
-        }
-
-        private void deleteMeter(DeviceId deviceId, MeterId meterId) {
-            Meter meter = meterService.getMeter(deviceId, meterId);
-            if (meter != null) {
-                MeterRequest meterRequest = DefaultMeterRequest.builder()
-                        .withBands(meter.bands())
-                        .withUnit(meter.unit())
-                        .forDevice(deviceId)
-                        .fromApp(appId)
-                        .burst()
-                        .remove();
-
-                meterService.withdraw(meterRequest, meterId);
-            }
-        }
-
-        private void removeMeterFromBpMap(Meter meter) {
-            bpInfoToMeter.values().forEach(meterKeys -> meterKeys.stream()
-                    .filter(meterKey -> (meterKey.deviceId().equals(meter.deviceId()))
-                            && meterKey.meterId().equals(meter.id())).findFirst().
-                            ifPresent(mk -> {
-                                meterKeys.remove(mk);
-                                programmedMeters.remove(mk);
-                                log.info("Deleted from the internal map. MeterKey {}", mk);
-                                log.info("Programmed meters {}", programmedMeters);
-                            }));
-        }
     }
 }
diff --git a/app/src/main/java/org/opencord/olt/impl/OltFlowService.java b/app/src/main/java/org/opencord/olt/impl/OltFlowService.java
new file mode 100644
index 0000000..3f683ef
--- /dev/null
+++ b/app/src/main/java/org/opencord/olt/impl/OltFlowService.java
@@ -0,0 +1,698 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.olt.impl;
+
+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.Modified;
+import org.apache.felix.scr.annotations.Property;
+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.IPv4;
+import org.onlab.packet.IPv6;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.VlanId;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criteria;
+import org.onosproject.net.flowobjective.DefaultFilteringObjective;
+import org.onosproject.net.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.FilteringObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.flowobjective.ObjectiveContext;
+import org.onosproject.net.flowobjective.ObjectiveError;
+import org.onosproject.net.meter.MeterId;
+import org.opencord.olt.internalapi.AccessDeviceFlowService;
+import org.opencord.olt.internalapi.AccessDeviceMeterService;
+import org.opencord.sadis.BandwidthProfileInformation;
+import org.opencord.sadis.BaseInformationService;
+import org.opencord.sadis.SadisService;
+import org.opencord.sadis.SubscriberAndDeviceInformation;
+import org.opencord.sadis.UniTagInformation;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+
+import java.util.Dictionary;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static org.onlab.util.Tools.get;
+import static org.slf4j.LoggerFactory.getLogger;
+
+@Service
+@Component(immediate = true)
+public class OltFlowService implements AccessDeviceFlowService {
+
+    private static final String APP_NAME = "org.opencord.olt";
+    private static final int NONE_TP_ID = -1;
+    private static final int NO_PCP = -1;
+    private static final Integer MAX_PRIORITY = 10000;
+    private static final Integer MIN_PRIORITY = 1000;
+    private static final int DEFAULT_TP_ID = 64;
+    private static final String INSTALLED = "installed";
+    private static final String REMOVED = "removed";
+    private static final String INSTALLATION = "installation";
+    private static final String REMOVAL = "removal";
+    private static final String V4 = "V4";
+    private static final String V6 = "V6";
+    private static final String NO_MAC = "A4:23:05:00:00:00";
+
+    private final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowObjectiveService flowObjectiveService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MastershipService mastershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected SadisService sadisService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected AccessDeviceMeterService oltMeterService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ComponentConfigService componentConfigService;
+
+    @Property(name = "enableIgmpOnProvisioning", boolValue = false,
+            label = "Create IGMP Flow rules when an NNI port is up")
+    protected boolean enableIgmpOnProvisioning = false;
+
+    @Property(name = "enableDhcpOnProvisioning", boolValue = true,
+            label = "Create the DHCP Flow rules when an NNI port is up")
+    protected boolean enableDhcpOnProvisioning = false;
+
+    @Property(name = "enableDhcpV4", boolValue = true,
+            label = "Enable flows for DHCP v4")
+    protected boolean enableDhcpV4 = true;
+
+    @Property(name = "enableDhcpV6", boolValue = false,
+            label = "Enable flows for DHCP v6")
+    protected boolean enableDhcpV6 = false;
+
+    @Property(name = "enableEapol", boolValue = false,
+            label = "Send EAPOL authentication trap flows before subscriber provisioning")
+    protected boolean enableEapol = false;
+
+    @Property(name = "defaultTechProfileId", intValue = DEFAULT_TP_ID,
+            label = "Default technology profile id that is used for authentication trap flows")
+    protected int defaultTechProfileId = DEFAULT_TP_ID;
+
+    protected ApplicationId appId;
+    protected BaseInformationService<BandwidthProfileInformation> bpService;
+    protected BaseInformationService<SubscriberAndDeviceInformation> subsService;
+    private Set<PortNumber> pendingAddEapol = Sets.newConcurrentHashSet();
+
+    @Activate
+    public void activate(ComponentContext context) {
+        bpService = sadisService.getBandwidthProfileService();
+        subsService = sadisService.getSubscriberInfoService();
+        componentConfigService.registerProperties(getClass());
+        appId = coreService.getAppId(APP_NAME);
+        log.info("Olt Flow Service started");
+    }
+
+
+    @Deactivate
+    public void deactivate(ComponentContext context) {
+        componentConfigService.unregisterProperties(getClass(), false);
+        log.info("Olt flow service stopped");
+    }
+
+    @Modified
+    public void modified(ComponentContext context) {
+
+        Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
+
+        Boolean o = Tools.isPropertyEnabled(properties, "enableDhcpOnProvisioning");
+        if (o != null) {
+            enableDhcpOnProvisioning = o;
+        }
+
+        Boolean v4 = Tools.isPropertyEnabled(properties, "enableDhcpV4");
+        if (v4 != null) {
+            enableDhcpV4 = v4;
+        }
+
+        Boolean v6 = Tools.isPropertyEnabled(properties, "enableDhcpV6");
+        if (v6 != null) {
+            enableDhcpV6 = v6;
+        }
+
+        Boolean p = Tools.isPropertyEnabled(properties, "enableIgmpOnProvisioning");
+        if (p != null) {
+            enableIgmpOnProvisioning = p;
+        }
+
+        Boolean eap = Tools.isPropertyEnabled(properties, "enableEapol");
+        if (eap != null) {
+            enableEapol = eap;
+        }
+
+        String tpId = get(properties, "defaultTechProfileId");
+        defaultTechProfileId = isNullOrEmpty(tpId) ? DEFAULT_TP_ID : Integer.parseInt(tpId.trim());
+
+    }
+
+    @Override
+    public void processDhcpFilteringObjectives(DeviceId devId, PortNumber port,
+                                               MeterId upstreamMeterId,
+                                               UniTagInformation tagInformation,
+                                               boolean install,
+                                               boolean upstream) {
+        if (!enableDhcpOnProvisioning && !upstream) {
+            log.debug("Dhcp provisioning is disabled.");
+            return;
+        }
+
+        if (!mastershipService.isLocalMaster(devId)) {
+            return;
+        }
+
+        int techProfileId = tagInformation != null ? tagInformation.getTechnologyProfileId() : NONE_TP_ID;
+        VlanId cTag = tagInformation != null ? tagInformation.getPonCTag() : VlanId.NONE;
+        VlanId unitagMatch = tagInformation != null ? tagInformation.getUniTagMatch() : VlanId.ANY;
+
+        if (enableDhcpV4) {
+            int udpSrc = (upstream) ? 68 : 67;
+            int udpDst = (upstream) ? 67 : 68;
+
+            EthType ethType = EthType.EtherType.IPV4.ethType();
+            byte protocol = IPv4.PROTOCOL_UDP;
+
+            this.addDhcpFilteringObjectives(devId, port, udpSrc, udpDst, ethType,
+                    upstreamMeterId, techProfileId, protocol, cTag, unitagMatch, install);
+        }
+
+        if (enableDhcpV6) {
+            int udpSrc = (upstream) ? 547 : 546;
+            int udpDst = (upstream) ? 546 : 547;
+
+            EthType ethType = EthType.EtherType.IPV6.ethType();
+            byte protocol = IPv6.PROTOCOL_UDP;
+
+            this.addDhcpFilteringObjectives(devId, port, udpSrc, udpDst, ethType,
+                    upstreamMeterId, techProfileId, protocol, cTag, unitagMatch, install);
+        }
+    }
+
+    private void addDhcpFilteringObjectives(DeviceId devId, PortNumber port, int udpSrc, int udpDst,
+                                            EthType ethType, MeterId upstreamMeterId, int techProfileId, byte protocol,
+                                            VlanId cTag, VlanId unitagMatch, boolean install) {
+
+        DefaultFilteringObjective.Builder builder = DefaultFilteringObjective.builder();
+        TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
+
+        if (upstreamMeterId != null) {
+            treatmentBuilder.meter(upstreamMeterId);
+        }
+
+        if (techProfileId != NONE_TP_ID) {
+            treatmentBuilder.writeMetadata(createTechProfValueForWm(unitagMatch, techProfileId), 0);
+        }
+
+        FilteringObjective.Builder dhcpUpstreamBuilder = (install ? builder.permit() : builder.deny())
+                .withKey(Criteria.matchInPort(port))
+                .addCondition(Criteria.matchEthType(ethType))
+                .addCondition(Criteria.matchIPProtocol(protocol))
+                .addCondition(Criteria.matchUdpSrc(TpPort.tpPort(udpSrc)))
+                .addCondition(Criteria.matchUdpDst(TpPort.tpPort(udpDst)))
+                .withMeta(treatmentBuilder
+                        .setOutput(PortNumber.CONTROLLER).build())
+                .fromApp(appId)
+                .withPriority(MAX_PRIORITY);
+
+        if (!VlanId.NONE.equals(cTag)) {
+            dhcpUpstreamBuilder.addCondition(Criteria.matchVlanId(cTag));
+        }
+
+        FilteringObjective dhcpUpstream = dhcpUpstreamBuilder.add(new ObjectiveContext() {
+            @Override
+            public void onSuccess(Objective objective) {
+                log.info("DHCP {} filter for device {} on port {} {}.",
+                        (ethType.equals(EthType.EtherType.IPV4.ethType())) ? V4 : V6,
+                        devId, port, (install) ? INSTALLED : REMOVED);
+            }
+
+            @Override
+            public void onError(Objective objective, ObjectiveError error) {
+                log.info("DHCP {} filter for device {} on port {} failed {} because {}",
+                        (ethType.equals(EthType.EtherType.IPV4.ethType())) ? V4 : V6,
+                        devId, port, (install) ? INSTALLATION : REMOVAL,
+                        error);
+            }
+        });
+
+        flowObjectiveService.filter(devId, dhcpUpstream);
+
+    }
+
+    @Override
+    public void processIgmpFilteringObjectives(DeviceId devId, PortNumber port,
+                                               MeterId upstreamMeterId,
+                                               UniTagInformation tagInformation,
+                                               boolean install,
+                                               boolean upstream) {
+        if (!enableIgmpOnProvisioning && !upstream) {
+            log.debug("Igmp provisioning is disabled.");
+            return;
+        }
+
+        if (!mastershipService.isLocalMaster(devId)) {
+            return;
+        }
+
+        DefaultFilteringObjective.Builder builder = DefaultFilteringObjective.builder();
+        TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
+        if (upstream) {
+
+            if (tagInformation.getTechnologyProfileId() != NONE_TP_ID) {
+                treatmentBuilder.writeMetadata(createTechProfValueForWm(null,
+                        tagInformation.getTechnologyProfileId()), 0);
+            }
+
+
+            if (upstreamMeterId != null) {
+                treatmentBuilder.meter(upstreamMeterId);
+            }
+
+            if (!VlanId.NONE.equals(tagInformation.getPonCTag())) {
+                builder.addCondition(Criteria.matchVlanId(tagInformation.getPonCTag()));
+            }
+
+            if (tagInformation.getUsPonCTagPriority() != NO_PCP) {
+                builder.addCondition(Criteria.matchVlanPcp((byte) tagInformation.getUsPonCTagPriority()));
+            }
+        }
+
+        builder = install ? builder.permit() : builder.deny();
+
+        FilteringObjective igmp = builder
+                .withKey(Criteria.matchInPort(port))
+                .addCondition(Criteria.matchEthType(EthType.EtherType.IPV4.ethType()))
+                .addCondition(Criteria.matchIPProtocol(IPv4.PROTOCOL_IGMP))
+                .withMeta(treatmentBuilder
+                        .setOutput(PortNumber.CONTROLLER).build())
+                .fromApp(appId)
+                .withPriority(MAX_PRIORITY)
+                .add(new ObjectiveContext() {
+                    @Override
+                    public void onSuccess(Objective objective) {
+                        log.info("Igmp filter for {} on {} {}.",
+                                devId, port, (install) ? INSTALLED : REMOVED);
+                    }
+
+                    @Override
+                    public void onError(Objective objective, ObjectiveError error) {
+                        log.info("Igmp filter for {} on {} failed {} because {}.",
+                                devId, port, (install) ? INSTALLATION : REMOVAL,
+                                error);
+                    }
+                });
+
+        flowObjectiveService.filter(devId, igmp);
+    }
+
+    @Override
+    public void processEapolFilteringObjectives(DeviceId devId, PortNumber portNumber, String bpId,
+                                                CompletableFuture<ObjectiveError> filterFuture,
+                                                VlanId vlanId, boolean install) {
+
+        if (!enableEapol) {
+            log.debug("Eapol filtering is disabled. Completing filteringFuture immediately for the device {}", devId);
+            if (filterFuture != null) {
+                filterFuture.complete(null);
+            }
+            return;
+        }
+
+        if (!mastershipService.isLocalMaster(devId)) {
+            log.warn("The master of the device {} is another instance", devId);
+            if (filterFuture != null) {
+                filterFuture.complete(ObjectiveError.DEVICEMISSING);
+            }
+            return;
+        }
+
+        BandwidthProfileInformation bpInfo = getBandwidthProfileInformation(bpId);
+        if (bpInfo == null) {
+            log.warn("Bandwidth profile {} is not found. Authentication flow"
+                    + " will not be installed", bpId);
+            if (filterFuture != null) {
+                filterFuture.complete(ObjectiveError.BADPARAMS);
+            }
+            return;
+        }
+
+        if (install) {
+            boolean added = pendingAddEapol.add(portNumber);
+            if (!added) {
+                if (filterFuture != null) {
+                    log.warn("The eapol flow is processing for the port {}. Ignoring this request", portNumber);
+                    filterFuture.complete(null);
+                }
+                return;
+            }
+            log.info("portNumber added to pendingAddEapol map {}", portNumber);
+        }
+
+        DefaultFilteringObjective.Builder builder = DefaultFilteringObjective.builder();
+        TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
+        CompletableFuture<Object> meterFuture = new CompletableFuture<>();
+
+        // check if meter exists and create it only for an install
+        MeterId meterId = oltMeterService.getMeterIdFromBpMapping(devId, bpInfo.id());
+        if (meterId == null) {
+            if (install) {
+                meterId = oltMeterService.createMeter(devId, bpInfo, meterFuture);
+                treatmentBuilder.meter(meterId);
+            } else {
+                // this case should not happen as the request to remove an eapol
+                // flow should mean that the flow points to a meter that exists.
+                // Nevertheless we can still delete the flow as we only need the
+                // correct 'match' to do so.
+                log.warn("Unknown meter id for bp {}, still proceeding with "
+                        + "delete of eapol flow for {}/{}", bpInfo.id(), devId, portNumber);
+                meterFuture.complete(null);
+            }
+        } else {
+            log.debug("Meter {} was previously created for bp {}", meterId, bpInfo.id());
+            treatmentBuilder.meter(meterId);
+            meterFuture.complete(null);
+        }
+
+        final MeterId mId = meterId;
+        meterFuture.thenAcceptAsync(result -> {
+            if (result == null) {
+                log.info("Meter {} for {} on {}/{} exists. {} EAPOL trap flow",
+                        mId, bpId, devId, portNumber,
+                        (install) ? "Installing" : "Removing");
+                int techProfileId = getDefaultTechProfileId(devId, portNumber);
+
+                //Authentication trap flow uses only tech profile id as write metadata value
+                FilteringObjective eapol = (install ? builder.permit() : builder.deny())
+                        .withKey(Criteria.matchInPort(portNumber))
+                        .addCondition(Criteria.matchEthType(EthType.EtherType.EAPOL.ethType()))
+                        .addCondition(Criteria.matchVlanId(vlanId))
+                        .withMeta(treatmentBuilder
+                                .writeMetadata(createTechProfValueForWm(vlanId, techProfileId), 0)
+                                .setOutput(PortNumber.CONTROLLER).build())
+                        .fromApp(appId)
+                        .withPriority(MAX_PRIORITY)
+                        .add(new ObjectiveContext() {
+                            @Override
+                            public void onSuccess(Objective objective) {
+                                log.info("Eapol filter for {} on {} {} with meter {}.",
+                                        devId, portNumber, (install) ? INSTALLED : REMOVED, mId);
+                                if (filterFuture != null) {
+                                    filterFuture.complete(null);
+                                }
+                                pendingAddEapol.remove(portNumber);
+                            }
+
+                            @Override
+                            public void onError(Objective objective, ObjectiveError error) {
+                                log.info("Eapol filter for {} on {} with meter {} failed {} because {}",
+                                        devId, portNumber, mId, (install) ? INSTALLATION : REMOVAL,
+                                        error);
+                                if (filterFuture != null) {
+                                    filterFuture.complete(error);
+                                }
+                                pendingAddEapol.remove(portNumber);
+                            }
+                        });
+
+                flowObjectiveService.filter(devId, eapol);
+            } else {
+                log.warn("Meter installation error while sending eapol trap flow. " +
+                        "Result {} and MeterId {}", result, mId);
+            }
+        });
+    }
+
+    /**
+     * Installs trap filtering objectives for particular traffic types on an
+     * NNI port.
+     *
+     * @param devId   device ID
+     * @param port    port number
+     * @param install true to install, false to remove
+     */
+    @Override
+    public void processNniFilteringObjectives(DeviceId devId, PortNumber port, boolean install) {
+        log.info("Sending flows for NNI port {} of the device {}", port, devId);
+        processLldpFilteringObjective(devId, port, install);
+        processDhcpFilteringObjectives(devId, port, null, null, install, false);
+        processIgmpFilteringObjectives(devId, port, null, null, install, false);
+    }
+
+
+    @Override
+    public void processLldpFilteringObjective(DeviceId devId, PortNumber port, boolean install) {
+        if (!mastershipService.isLocalMaster(devId)) {
+            return;
+        }
+        DefaultFilteringObjective.Builder builder = DefaultFilteringObjective.builder();
+
+        FilteringObjective lldp = (install ? builder.permit() : builder.deny())
+                .withKey(Criteria.matchInPort(port))
+                .addCondition(Criteria.matchEthType(EthType.EtherType.LLDP.ethType()))
+                .withMeta(DefaultTrafficTreatment.builder()
+                        .setOutput(PortNumber.CONTROLLER).build())
+                .fromApp(appId)
+                .withPriority(MAX_PRIORITY)
+                .add(new ObjectiveContext() {
+                    @Override
+                    public void onSuccess(Objective objective) {
+                        log.info("LLDP filter for device {} on port {} {}.",
+                                devId, port, (install) ? INSTALLED : REMOVED);
+                    }
+
+                    @Override
+                    public void onError(Objective objective, ObjectiveError error) {
+                        log.info("LLDP filter for device {} on port {} failed {} because {}",
+                                devId, port, (install) ? INSTALLATION : REMOVAL,
+                                error);
+                    }
+                });
+
+        flowObjectiveService.filter(devId, lldp);
+    }
+
+    @Override
+    public ForwardingObjective.Builder createTransparentBuilder(PortNumber uplinkPort,
+                                                                PortNumber subscriberPort,
+                                                                MeterId meterId,
+                                                                UniTagInformation tagInfo,
+                                                                boolean upstream) {
+
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchVlanId(tagInfo.getPonSTag())
+                .matchInPort(upstream ? subscriberPort : uplinkPort)
+                .matchInnerVlanId(tagInfo.getPonCTag())
+                .build();
+
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+        if (meterId != null) {
+            tBuilder.meter(meterId);
+        }
+
+        TrafficTreatment treatment = tBuilder
+                .setOutput(upstream ? uplinkPort : subscriberPort)
+                .writeMetadata(createMetadata(upstream ? tagInfo.getPonSTag() : tagInfo.getPonCTag(),
+                        tagInfo.getTechnologyProfileId(), upstream ? uplinkPort : subscriberPort), 0)
+                .build();
+
+        return createForwardingObjectiveBuilder(selector, treatment, MIN_PRIORITY);
+    }
+
+    @Override
+    public ForwardingObjective.Builder createUpBuilder(PortNumber uplinkPort,
+                                                       PortNumber subscriberPort,
+                                                       MeterId upstreamMeterId,
+                                                       UniTagInformation uniTagInformation) {
+
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchInPort(subscriberPort)
+                .matchVlanId(uniTagInformation.getUniTagMatch())
+                .build();
+
+        TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder()
+                .pushVlan()
+                .setVlanId(uniTagInformation.getPonCTag());
+
+        if (uniTagInformation.getUsPonCTagPriority() != NO_PCP) {
+            treatmentBuilder.setVlanPcp((byte) uniTagInformation.getUsPonCTagPriority());
+        }
+
+        treatmentBuilder.pushVlan()
+                .setVlanId(uniTagInformation.getPonSTag());
+
+        if (uniTagInformation.getUsPonSTagPriority() != NO_PCP) {
+            treatmentBuilder.setVlanPcp((byte) uniTagInformation.getUsPonSTagPriority());
+        }
+
+        treatmentBuilder.setOutput(uplinkPort)
+                .writeMetadata(createMetadata(uniTagInformation.getPonCTag(),
+                        uniTagInformation.getTechnologyProfileId(), uplinkPort), 0L);
+
+        if (upstreamMeterId != null) {
+            treatmentBuilder.meter(upstreamMeterId);
+        }
+
+        return createForwardingObjectiveBuilder(selector, treatmentBuilder.build(), MIN_PRIORITY);
+    }
+
+    @Override
+    public ForwardingObjective.Builder createDownBuilder(PortNumber uplinkPort,
+                                                         PortNumber subscriberPort,
+                                                         MeterId downstreamMeterId,
+                                                         UniTagInformation tagInformation) {
+        TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder()
+                .matchVlanId(tagInformation.getPonSTag())
+                .matchInPort(uplinkPort)
+                .matchInnerVlanId(tagInformation.getPonCTag())
+                .matchMetadata(tagInformation.getPonCTag().toShort());
+
+        if (tagInformation.getDsPonSTagPriority() != NO_PCP) {
+            selectorBuilder.matchVlanPcp((byte) tagInformation.getDsPonSTagPriority());
+        }
+
+        if (tagInformation.getConfiguredMacAddress() != null &&
+                !NO_MAC.equals(tagInformation.getConfiguredMacAddress())) {
+            selectorBuilder.matchEthDst(MacAddress.valueOf(tagInformation.getConfiguredMacAddress()));
+        }
+
+        TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder()
+                .popVlan()
+                .setOutput(subscriberPort)
+                .writeMetadata(createMetadata(tagInformation.getPonCTag(), tagInformation.getTechnologyProfileId(),
+                        subscriberPort), 0);
+
+        // to remark inner vlan header
+        if (tagInformation.getUsPonCTagPriority() != NO_PCP) {
+            treatmentBuilder.setVlanPcp((byte) tagInformation.getUsPonCTagPriority());
+        }
+
+        if (!VlanId.NONE.equals(tagInformation.getUniTagMatch())) {
+            treatmentBuilder.setVlanId(tagInformation.getUniTagMatch());
+        }
+
+        if (downstreamMeterId != null) {
+            treatmentBuilder.meter(downstreamMeterId);
+        }
+
+        return createForwardingObjectiveBuilder(selectorBuilder.build(), treatmentBuilder.build(), MIN_PRIORITY);
+    }
+
+    private DefaultForwardingObjective.Builder createForwardingObjectiveBuilder(TrafficSelector selector,
+                                                                                TrafficTreatment treatment,
+                                                                                Integer priority) {
+        return DefaultForwardingObjective.builder()
+                .withFlag(ForwardingObjective.Flag.VERSATILE)
+                .withPriority(priority)
+                .makePermanent()
+                .withSelector(selector)
+                .fromApp(appId)
+                .withTreatment(treatment);
+    }
+
+    /**
+     * Returns the write metadata value including tech profile reference and innerVlan.
+     * For param cVlan, null can be sent
+     *
+     * @param cVlan         c (customer) tag of one subscriber
+     * @param techProfileId tech profile id of one subscriber
+     * @return the write metadata value including tech profile reference and innerVlan
+     */
+    private Long createTechProfValueForWm(VlanId cVlan, int techProfileId) {
+        if (cVlan == null || VlanId.NONE.equals(cVlan)) {
+            return (long) techProfileId << 32;
+        }
+        return ((long) (cVlan.id()) << 48 | (long) techProfileId << 32);
+    }
+
+    private BandwidthProfileInformation getBandwidthProfileInformation(String bandwidthProfile) {
+        if (bandwidthProfile == null) {
+            return null;
+        }
+        return bpService.get(bandwidthProfile);
+    }
+
+    /**
+     * It will be used to support AT&T use case (for EAPOL flows).
+     * If multiple services are found in uniServiceList, returns default tech profile id
+     * If one service is found, returns the found one
+     *
+     * @param devId
+     * @param portNumber
+     * @return the default technology profile id
+     */
+    private int getDefaultTechProfileId(DeviceId devId, PortNumber portNumber) {
+        Port port = deviceService.getPort(devId, portNumber);
+        if (port != null) {
+            SubscriberAndDeviceInformation info = subsService.get(port.annotations().value(AnnotationKeys.PORT_NAME));
+            if (info != null && info.uniTagList().size() == 1) {
+                return info.uniTagList().get(0).getTechnologyProfileId();
+            }
+        }
+        return defaultTechProfileId;
+    }
+
+    /**
+     * Write metadata instruction value (metadata) is 8 bytes.
+     * <p>
+     * MS 2 bytes: C Tag
+     * Next 2 bytes: Technology Profile Id
+     * Next 4 bytes: Port number (uni or nni)
+     */
+    private Long createMetadata(VlanId innerVlan, int techProfileId, PortNumber egressPort) {
+        if (techProfileId == NONE_TP_ID) {
+            techProfileId = DEFAULT_TP_ID;
+        }
+
+        return ((long) (innerVlan.id()) << 48 | (long) techProfileId << 32) | egressPort.toLong();
+    }
+
+
+}
diff --git a/app/src/main/java/org/opencord/olt/impl/OltMeterService.java b/app/src/main/java/org/opencord/olt/impl/OltMeterService.java
new file mode 100644
index 0000000..df434eb
--- /dev/null
+++ b/app/src/main/java/org/opencord/olt/impl/OltMeterService.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.olt.impl;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+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.Modified;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flowobjective.ObjectiveError;
+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.MeterContext;
+import org.onosproject.net.meter.MeterEvent;
+import org.onosproject.net.meter.MeterFailReason;
+import org.onosproject.net.meter.MeterId;
+import org.onosproject.net.meter.MeterKey;
+import org.onosproject.net.meter.MeterListener;
+import org.onosproject.net.meter.MeterRequest;
+import org.onosproject.net.meter.MeterService;
+import org.opencord.olt.internalapi.AccessDeviceMeterService;
+import org.opencord.sadis.BandwidthProfileInformation;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+
+import java.util.Dictionary;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.onlab.util.Tools.groupedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
+
+@Service
+@Component(immediate = true)
+public class OltMeterService implements AccessDeviceMeterService {
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MeterService meterService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ComponentConfigService componentConfigService;
+
+    @Property(name = "deleteMeters", boolValue = true,
+            label = "Deleting Meters based on flow count statistics")
+    protected boolean deleteMeters = true;
+
+    protected Map<String, Set<MeterKey>> bpInfoToMeter;
+    protected Set<MeterKey> programmedMeters;
+    private ApplicationId appId;
+    private static final String APP_NAME = "org.opencord.olt";
+
+    private final MeterListener meterListener = new InternalMeterListener();
+
+    private final Logger log = getLogger(getClass());
+
+    protected ExecutorService eventExecutor;
+
+    @Activate
+    public void activate(ComponentContext context) {
+        eventExecutor = Executors.newFixedThreadPool(5, groupedThreads("onos/olt",
+                "events-%d", log));
+        appId = coreService.registerApplication(APP_NAME);
+        bpInfoToMeter = Maps.newConcurrentMap();
+        programmedMeters = Sets.newConcurrentHashSet();
+        meterService.addListener(meterListener);
+        componentConfigService.registerProperties(getClass());
+        log.info("Olt Meter service started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        meterService.removeListener(meterListener);
+    }
+
+
+    @Modified
+    public void modified(ComponentContext context) {
+        Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
+
+        Boolean d = Tools.isPropertyEnabled(properties, "deleteMeters");
+        if (d != null) {
+            deleteMeters = d;
+        }
+    }
+
+    @Override
+    public ImmutableMap<String, Set<MeterKey>> getBpMeterMappings() {
+        return ImmutableMap.copyOf(bpInfoToMeter);
+    }
+
+    @Override
+    public void addMeterIdToBpMapping(DeviceId deviceId, MeterId meterId, String bandwidthProfile) {
+        bpInfoToMeter.compute(bandwidthProfile, (k, v) -> {
+            if (v == null) {
+                return Sets.newHashSet(MeterKey.key(deviceId, meterId));
+            } else {
+                v.add(MeterKey.key(deviceId, meterId));
+                return v;
+            }
+        });
+    }
+
+    @Override
+    public MeterId getMeterIdFromBpMapping(DeviceId deviceId, String bandwidthProfile) {
+        if (bpInfoToMeter.get(bandwidthProfile) == null) {
+            log.warn("Bandwidth Profile '{}' is not currently mapped to a meter",
+                    bandwidthProfile);
+            return null;
+        }
+
+        Optional<MeterKey> meterKeyForDevice = bpInfoToMeter.get(bandwidthProfile)
+                .stream()
+                .filter(meterKey -> meterKey.deviceId().equals(deviceId))
+                .findFirst();
+        if (meterKeyForDevice.isPresent()) {
+            log.debug("Found meter {} for bandwidth profile {}",
+                    meterKeyForDevice.get().meterId(), bandwidthProfile);
+            return meterKeyForDevice.get().meterId();
+        } else {
+            log.warn("Bandwidth profile '{}' is not currently mapped to a meter",
+                    bandwidthProfile);
+            return null;
+        }
+    }
+
+    @Override
+    public ImmutableSet<MeterKey> getProgMeters() {
+        return ImmutableSet.copyOf(programmedMeters);
+    }
+
+    @Override
+    public MeterId createMeter(DeviceId deviceId, BandwidthProfileInformation bpInfo,
+                               CompletableFuture<Object> meterFuture) {
+        if (bpInfo == null) {
+            log.warn("Requested bandwidth profile information is NULL");
+            meterFuture.complete(ObjectiveError.BADPARAMS);
+            return null;
+        }
+
+        MeterId meterId = getMeterIdFromBpMapping(deviceId, bpInfo.id());
+        if (meterId != null) {
+            log.debug("Meter {} was previously created for bp {}", meterId, bpInfo.id());
+            meterFuture.complete(null);
+            return meterId;
+        }
+
+        List<Band> meterBands = createMeterBands(bpInfo);
+
+        final AtomicReference<MeterId> meterIdRef = new AtomicReference<>();
+        MeterRequest meterRequest = DefaultMeterRequest.builder()
+                .withBands(meterBands)
+                .withUnit(Meter.Unit.KB_PER_SEC)
+                .withContext(new MeterContext() {
+                    @Override
+                    public void onSuccess(MeterRequest op) {
+                        meterFuture.complete(null);
+                    }
+
+                    @Override
+                    public void onError(MeterRequest op, MeterFailReason reason) {
+                        bpInfoToMeter.remove(MeterKey.key(deviceId, meterIdRef.get()));
+                        meterFuture.complete(reason);
+                    }
+                })
+                .forDevice(deviceId)
+                .fromApp(appId)
+                .burst()
+                .add();
+
+        Meter meter = meterService.submit(meterRequest);
+        meterIdRef.set(meter.id());
+        addMeterIdToBpMapping(deviceId, meterIdRef.get(), bpInfo.id());
+        programmedMeters.add(MeterKey.key(deviceId, meter.id()));
+        log.info("Meter is created. Meter Id {}", meter.id());
+        return meter.id();
+    }
+
+    private List<Band> createMeterBands(BandwidthProfileInformation bpInfo) {
+        List<Band> meterBands = new ArrayList<>();
+
+        meterBands.add(createMeterBand(bpInfo.committedInformationRate(), bpInfo.committedBurstSize()));
+        meterBands.add(createMeterBand(bpInfo.exceededInformationRate(), bpInfo.exceededBurstSize()));
+        meterBands.add(createMeterBand(bpInfo.assuredInformationRate(), 0L));
+
+        return meterBands;
+    }
+
+    private Band createMeterBand(long rate, Long burst) {
+        return DefaultBand.builder()
+                .withRate(rate) //already Kbps
+                .burstSize(burst) // already Kbits
+                .ofType(Band.Type.DROP) // no matter
+                .build();
+    }
+
+    private class InternalMeterListener implements MeterListener {
+
+        Map<MeterKey, AtomicInteger> pendingRemoveMeters = Maps.newConcurrentMap();
+
+        @Override
+        public void event(MeterEvent meterEvent) {
+            eventExecutor.execute(() -> {
+                Meter meter = meterEvent.subject();
+                MeterKey key = MeterKey.key(meter.deviceId(), meter.id());
+                if (deleteMeters && MeterEvent.Type.METER_REFERENCE_COUNT_ZERO.equals(meterEvent.type())) {
+                    log.info("Zero Count Meter Event is received. Meter is {}", meter.id());
+                    incrementMeterCount(key);
+
+                    if (meter != null && appId.equals(meter.appId()) && pendingRemoveMeters.get(key).get() == 3) {
+                        log.info("Deleting unreferenced, no longer programmed Meter {}", meter.id());
+                        deleteMeter(meter.deviceId(), meter.id());
+                    }
+                }
+                if (MeterEvent.Type.METER_REMOVED.equals(meterEvent.type())) {
+                    log.info("Meter Removed Event is received for {}", meter.id());
+                    programmedMeters.remove(key);
+                    pendingRemoveMeters.remove(key);
+                    removeMeterFromBpMapping(meter);
+                }
+            });
+        }
+
+        private void incrementMeterCount(MeterKey key) {
+            if (key == null) {
+                return;
+            }
+            pendingRemoveMeters.compute(key,
+                    (k, v) -> {
+                        if (v == null) {
+                            return new AtomicInteger(1);
+                        }
+                        v.addAndGet(1);
+                        return v;
+                    });
+        }
+
+        private void deleteMeter(DeviceId deviceId, MeterId meterId) {
+            Meter meter = meterService.getMeter(deviceId, meterId);
+            if (meter != null) {
+                MeterRequest meterRequest = DefaultMeterRequest.builder()
+                        .withBands(meter.bands())
+                        .withUnit(meter.unit())
+                        .forDevice(deviceId)
+                        .fromApp(appId)
+                        .burst()
+                        .remove();
+
+                meterService.withdraw(meterRequest, meterId);
+            }
+        }
+
+        private void removeMeterFromBpMapping(Meter meter) {
+            MeterKey meterKey = MeterKey.key(meter.deviceId(), meter.id());
+            Iterator<Map.Entry<String, Set<MeterKey>>> iterator = bpInfoToMeter.entrySet().iterator();
+            while (iterator.hasNext()) {
+                Map.Entry<String, Set<MeterKey>> entry = iterator.next();
+                if (entry.getValue().contains(meterKey)) {
+                    iterator.remove();
+                    log.info("Deleted meter for MeterKey {} - Last prog meters {}", meterKey, programmedMeters);
+                    break;
+                }
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/org/opencord/olt/internalapi/AccessDeviceFlowService.java b/app/src/main/java/org/opencord/olt/internalapi/AccessDeviceFlowService.java
new file mode 100644
index 0000000..362b154
--- /dev/null
+++ b/app/src/main/java/org/opencord/olt/internalapi/AccessDeviceFlowService.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.olt.internalapi;
+
+import org.onlab.packet.VlanId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.ObjectiveError;
+import org.onosproject.net.meter.MeterId;
+import org.opencord.sadis.UniTagInformation;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Olt service for flow operations.
+ */
+public interface AccessDeviceFlowService {
+
+    /**
+     * Provisions or removes trap-to-controller DHCP packets.
+     *
+     * @param devId           the target device identifier
+     * @param port            the uni port for which this trap flow is designated
+     * @param upstreamMeterId the upstream meter id that includes the upstream
+     *                        bandwidth profile values such as PIR,CIR. If no meter id needs to be referenced,
+     *                        null can be sent
+     * @param tagInformation  the uni tag (ctag, stag) information
+     * @param install         true to install the flow, false to remove the flow
+     * @param upstream        true if trapped packets are flowing upstream towards
+     *                        server, false if packets are flowing downstream towards client
+     */
+    void processDhcpFilteringObjectives(DeviceId devId, PortNumber port,
+                                        MeterId upstreamMeterId,
+                                        UniTagInformation tagInformation,
+                                        boolean install,
+                                        boolean upstream);
+
+    /**
+     * Trap igmp packets to the controller.
+     *
+     * @param devId           Device identifier to send the flow
+     * @param port            Uni Port number
+     * @param upstreamMeterId upstream meter id that represents the upstream bandwidth profile
+     * @param tagInformation  the uni tag information of the subscriber
+     * @param install         the indicator to install or to remove the flow
+     * @param upstream        determines the direction of the flow
+     */
+    void processIgmpFilteringObjectives(DeviceId devId, PortNumber port,
+                                        MeterId upstreamMeterId,
+                                        UniTagInformation tagInformation,
+                                        boolean install,
+                                        boolean upstream);
+
+    /**
+     * Trap eapol authentication packets to the controller.
+     *
+     * @param devId        the device identifier
+     * @param portNumber   the port for which this trap flow is designated
+     * @param bpId         bandwidth profile id to add the related meter to the flow
+     * @param filterFuture completable future for this filtering objective operation
+     * @param vlanId       the default or customer tag for a subscriber
+     * @param install      true to install the flow, false to remove the flow
+     */
+    void processEapolFilteringObjectives(DeviceId devId, PortNumber portNumber, String bpId,
+                                         CompletableFuture<ObjectiveError> filterFuture,
+                                         VlanId vlanId, boolean install);
+
+    /**
+     * Trap lldp packets to the controller.
+     *
+     * @param devId   the device identifier
+     * @param port    the port for which this trap flow is designated
+     * @param install true to install the flow, false to remove the flow
+     */
+    void processLldpFilteringObjective(DeviceId devId, PortNumber port, boolean install);
+
+    /**
+     * Installs trap filtering objectives for particular traffic types (LLDP, IGMP and DHCP) on an
+     * NNI port.
+     *
+     * @param devId   device ID
+     * @param port    port number
+     * @param install true to install, false to remove
+     */
+    void processNniFilteringObjectives(DeviceId devId, PortNumber port, boolean install);
+
+    /**
+     * Creates a ForwardingObjective builder with double-tag match criteria and output
+     * action. The treatment will not contain pop or push actions.
+     * If the last parameter is true, use the upstream meter id and vice versa.
+     *
+     * @param uplinkPort      the nni port
+     * @param subscriberPort  the uni port
+     * @param meterId         the meter id that is assigned to upstream or downstream flows
+     * @param tagInfo         the uni tag information
+     * @param upstream        true to create upstream, false to create downstream builder
+     * @return ForwardingObjective.Builder
+     */
+    ForwardingObjective.Builder createTransparentBuilder(PortNumber uplinkPort,
+                                                         PortNumber subscriberPort,
+                                                         MeterId meterId,
+                                                         UniTagInformation tagInfo,
+                                                         boolean upstream);
+
+    /**
+     * Creates a ForwardingObjective builder for the upstream flows.
+     * The treatment will contain push action
+     *
+     * @param uplinkPort the nni port
+     * @param subscriberPort the uni port
+     * @param upstreamMeterId the meter id that is assigned to upstream flows
+     * @param uniTagInformation the uni tag information
+     * @return ForwardingObjective.Builder
+     */
+    ForwardingObjective.Builder createUpBuilder(PortNumber uplinkPort,
+                                                PortNumber subscriberPort,
+                                                MeterId upstreamMeterId,
+                                                UniTagInformation uniTagInformation);
+
+    /**
+     * Creates a ForwardingObjective builder for the downstream flows.
+     * The treatment will contain pop action
+     *
+     * @param uplinkPort the nni port
+     * @param subscriberPort the uni port
+     * @param downstreamMeterId the meter id that is assigned to downstream flows
+     * @param tagInformation the uni tag information
+     * @return ForwardingObjective.Builder
+     */
+    ForwardingObjective.Builder createDownBuilder(PortNumber uplinkPort,
+                                                  PortNumber subscriberPort,
+                                                  MeterId downstreamMeterId,
+                                                  UniTagInformation tagInformation);
+}
diff --git a/app/src/main/java/org/opencord/olt/internalapi/AccessDeviceMeterService.java b/app/src/main/java/org/opencord/olt/internalapi/AccessDeviceMeterService.java
new file mode 100644
index 0000000..d2d51b2
--- /dev/null
+++ b/app/src/main/java/org/opencord/olt/internalapi/AccessDeviceMeterService.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.olt.internalapi;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.meter.MeterId;
+import org.onosproject.net.meter.MeterKey;
+import org.opencord.sadis.BandwidthProfileInformation;
+
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Olt service for meter operations.
+ */
+public interface AccessDeviceMeterService {
+
+    /**
+     * Returns information about bandwidthProfile-meterKey (device / meter) mappings
+     * that have been programmed in the data-plane.
+     *
+     * @return an immutable map of bandwidthProfile-meterKey (device / meter) mappings
+     */
+    ImmutableMap<String, Set<MeterKey>> getBpMeterMappings();
+
+    /**
+     * Adds a bandwidthProfile-meterKey (device / meter) mapping that have been programmed
+     * in the data plane.
+     *
+     * @param deviceId         the access device id
+     * @param meterId          the meter id that is mapped to the bandwidth profile
+     * @param bandwidthProfile the bandwidth profile id
+     */
+    void addMeterIdToBpMapping(DeviceId deviceId, MeterId meterId, String bandwidthProfile);
+
+    /**
+     * Returns the meter id for a given bandwidth profile.
+     *
+     * @param deviceId         the access device id
+     * @param bandwidthProfile the bandwidth profile id
+     * @return the meter id
+     */
+    MeterId getMeterIdFromBpMapping(DeviceId deviceId, String bandwidthProfile);
+
+    /**
+     * Returns information about device-meter relations that have been programmed in the
+     * data-plane.
+     *
+     * @return an immutable set of device-meter mappings
+     */
+    ImmutableSet<MeterKey> getProgMeters();
+
+    /**
+     * Creates a meter and sends it to the device.
+     *
+     * @param deviceId    the access device id
+     * @param bpInfo      the bandwidth profile information
+     * @param meterFuture the meter future to indicate whether the meter creation is
+     *                    successful or not.
+     * @return meter id that is generated for the given parameters
+     */
+    MeterId createMeter(DeviceId deviceId, BandwidthProfileInformation bpInfo,
+                        CompletableFuture<Object> meterFuture);
+
+
+}
diff --git a/app/src/main/java/org/opencord/olt/internalapi/package-info.java b/app/src/main/java/org/opencord/olt/internalapi/package-info.java
new file mode 100644
index 0000000..2b5d98d
--- /dev/null
+++ b/app/src/main/java/org/opencord/olt/internalapi/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Internal APIs for the OLT application.
+ */
+package org.opencord.olt.internalapi;
diff --git a/app/src/main/java/org/opencord/olt/rest/OltWebResource.java b/app/src/main/java/org/opencord/olt/rest/OltWebResource.java
index 1631b09..54af4ac 100644
--- a/app/src/main/java/org/opencord/olt/rest/OltWebResource.java
+++ b/app/src/main/java/org/opencord/olt/rest/OltWebResource.java
@@ -24,6 +24,7 @@
 import org.opencord.olt.AccessSubscriberId;
 
 import java.util.Optional;
+import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
@@ -32,7 +33,7 @@
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 
-import static javax.ws.rs.core.Response.Status.NOT_FOUND;
+import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
 
 /**
  * OLT REST APIs.
@@ -46,19 +47,23 @@
      *
      * @param device device id
      * @param port port number
-     * @return 200 OK
+     * @return 200 OK or 500 Internal Server Error
      */
     @POST
     @Produces(MediaType.APPLICATION_JSON)
     @Path("{device}/{port}")
     public Response provisionSubscriber(
-            @PathParam("device")String device,
-            @PathParam("port")long port) {
+            @PathParam("device") String device,
+            @PathParam("port") long port) {
         AccessDeviceService service = get(AccessDeviceService.class);
         DeviceId deviceId = DeviceId.deviceId(device);
         PortNumber portNumber = PortNumber.portNumber(port);
         ConnectPoint connectPoint = new ConnectPoint(deviceId, portNumber);
-        service.provisionSubscriber(connectPoint);
+        try {
+            service.provisionSubscriber(connectPoint);
+        } catch (Exception e) {
+            return Response.status(INTERNAL_SERVER_ERROR).build();
+        }
         return ok("").build();
     }
 
@@ -86,90 +91,102 @@
      * Provision service for a subscriber.
      *
      * @param portName Name of the port on which the subscriber is connected
-     * @return 200 OK or 404 NOT_FOUND
+     * @return 200 OK or 204 NO CONTENT
      */
     @POST
     @Produces(MediaType.APPLICATION_JSON)
     @Path("services/{portName}")
     public Response provisionServices(
-            @PathParam("portName")String portName) {
+            @PathParam("portName") String portName) {
         AccessDeviceService service = get(AccessDeviceService.class);
 
         Optional<VlanId> emptyVlan = Optional.empty();
-        if (service.provisionSubscriber(new AccessSubscriberId(portName), emptyVlan, emptyVlan)) {
+        Optional<Integer> emptyTpId = Optional.empty();
+        if (service.provisionSubscriber(new AccessSubscriberId(portName), emptyVlan, emptyVlan, emptyTpId)) {
             return ok("").build();
         }
-        return Response.status(NOT_FOUND).build();
+        return Response.noContent().build();
     }
 
     /**
      * Provision service with particular tags for a subscriber.
      *
      * @param portName Name of the port on which the subscriber is connected
-     * @param sTagVal additional outer tag on this port
-     * @param cTagVal additional innter tag on this port
-     * @return 200 OK or 404 NOT_FOUND
+     * @param sTagVal  additional outer tag on this port
+     * @param cTagVal  additional innter tag on this port
+     * @param tpIdVal  technology profile id
+     * @return 200 OK or 204 NO CONTENT
      */
     @POST
+    @Consumes(MediaType.APPLICATION_JSON)
     @Produces(MediaType.APPLICATION_JSON)
-    @Path("services/{portName}/{sTag}/{cTag}")
+    @Path("services/{portName}/{sTag}/{cTag}/{tpId}")
     public Response provisionAdditionalVlans(
-            @PathParam("portName")String portName,
-            @PathParam("sTag")String sTagVal,
-            @PathParam("cTag")String cTagVal) {
+            @PathParam("portName") String portName,
+            @PathParam("sTag") String sTagVal,
+            @PathParam("cTag") String cTagVal,
+            @PathParam("tpId") String tpIdVal) {
         AccessDeviceService service = get(AccessDeviceService.class);
         VlanId cTag = VlanId.vlanId(cTagVal);
         VlanId sTag = VlanId.vlanId(sTagVal);
+        Integer tpId = Integer.valueOf(tpIdVal);
 
-        if (service.provisionSubscriber(new AccessSubscriberId(portName), Optional.of(sTag), Optional.of(cTag))) {
+        if (service.provisionSubscriber(new AccessSubscriberId(portName), Optional.of(sTag),
+                Optional.of(cTag), Optional.of(tpId))) {
             return ok("").build();
         }
-        return Response.status(NOT_FOUND).build();
+        return Response.noContent().build();
     }
 
     /**
      * Removes services for a subscriber.
      *
      * @param portName Name of the port on which the subscriber is connected
-     * @return 200 OK or 404 NOT_FOUND
+     * @return 200 OK or 204 NO CONTENT
      */
     @DELETE
     @Produces(MediaType.APPLICATION_JSON)
     @Path("services/{portName}")
     public Response deleteServices(
-            @PathParam("portName")String portName) {
+            @PathParam("portName") String portName) {
         AccessDeviceService service = get(AccessDeviceService.class);
 
         Optional<VlanId> emptyVlan = Optional.empty();
-        if (service.removeSubscriber(new AccessSubscriberId(portName), emptyVlan, emptyVlan)) {
+        Optional<Integer> emptyTpId = Optional.empty();
+        if (service.removeSubscriber(new AccessSubscriberId(portName), emptyVlan, emptyVlan, emptyTpId)) {
             return ok("").build();
         }
-        return Response.status(NOT_FOUND).build();
+        return Response.noContent().build();
     }
 
     /**
      * Removes additional vlans of a particular subscriber.
      *
      * @param portName Name of the port on which the subscriber is connected
-     * @param sTagVal additional outer tag on this port which needs to be removed
-     * @param cTagVal additional inner tag on this port which needs to be removed
-     * @return 200 OK or 404 NOT_FOUND
+     * @param sTagVal  additional outer tag on this port which needs to be removed
+     * @param cTagVal  additional inner tag on this port which needs to be removed
+     * @param tpIdVal  additional technology profile id
+     * @return 200 OK or 204 NO CONTENT
      */
     @DELETE
+    @Consumes(MediaType.APPLICATION_JSON)
     @Produces(MediaType.APPLICATION_JSON)
-    @Path("services/{portName}/{sTag}/{cTag}")
+    @Path("services/{portName}/{sTag}/{cTag}/{tpId}")
     public Response removeAdditionalVlans(
-            @PathParam("portName")String portName,
-            @PathParam("sTag")String sTagVal,
-            @PathParam("cTag")String cTagVal) {
+            @PathParam("portName") String portName,
+            @PathParam("sTag") String sTagVal,
+            @PathParam("cTag") String cTagVal,
+            @PathParam("tpId") String tpIdVal) {
         AccessDeviceService service = get(AccessDeviceService.class);
         VlanId cTag = VlanId.vlanId(cTagVal);
         VlanId sTag = VlanId.vlanId(sTagVal);
+        Integer tpId = Integer.valueOf(tpIdVal);
 
-        if (service.removeSubscriber(new AccessSubscriberId(portName), Optional.of(sTag), Optional.of(cTag))) {
+        if (service.removeSubscriber(new AccessSubscriberId(portName), Optional.of(sTag),
+                Optional.of(cTag), Optional.of(tpId))) {
             return ok("").build();
         }
-        return Response.status(NOT_FOUND).build();
+        return Response.noContent().build();
     }
 
 }
diff --git a/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index f871bc9..a472391 100644
--- a/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -34,9 +34,6 @@
             <action class="org.opencord.olt.cli.ShowOltCommand"/>
         </command>
         <command>
-            <action class="org.opencord.olt.cli.ShowSubscribersCommand"/>
-        </command>
-        <command>
             <action class="org.opencord.olt.cli.ShowProgrammedSubscribersCommand"/>
         </command>
         <command>
@@ -45,6 +42,12 @@
         <command>
             <action class="org.opencord.olt.cli.ShowBpMeterMappingsCommand"/>
         </command>
+        <command>
+            <action class="org.opencord.olt.cli.UniTagAddCommand"/>
+        </command>
+        <command>
+            <action class="org.opencord.olt.cli.UniTagRemoveCommand"/>
+        </command>
     </command-bundle>
 
     <bean id="deviceIdCompleter" class="org.onosproject.cli.net.DeviceIdCompleter"/>
diff --git a/app/src/main/resources/vlan_cfg.json b/app/src/main/resources/vlan_cfg.json
index 133aed7..3bb577f 100644
--- a/app/src/main/resources/vlan_cfg.json
+++ b/app/src/main/resources/vlan_cfg.json
@@ -1,6 +1,6 @@
 {
-"apps" : {
-  "org.opencord.sadis" : {
+  "apps" : {
+    "org.opencord.sadis" : {
       "sadis" : {
         "integration" : {
           "cache" : {
@@ -11,12 +11,44 @@
         },
         "entries" : [ {
           "id" : "s1-eth1",
-          "cTag" : 2,
-          "sTag" : 4,
           "nasPortId" : "s1-eth1",
-          "technologyProfileId" : 10,
-          "upstreamBandwidthProfile" : "High-Speed-Internet",
-          "downstreamBandwidthProfile" : "User1-Specific"
+          "uniTagList": [
+            {
+              "uniTagMatch": 35,
+              "ponCTag":33,
+              "ponSTag":7,
+              "technologyProfileId": 2,
+              "upstreamBandwidthProfile":"High-Speed-Internet",
+              "downstreamBandwidthProfile":"Service1"
+            },
+            {
+              "uniTagMatch": 45,
+              "ponCTag":43,
+              "ponSTag":10,
+              "technologyProfileId": 3,
+              "upstreamBandwidthProfile":"VOIP",
+              "downstreamBandwidthProfile":"Service2",
+              "isDhcpRequired":"true"
+            },
+            {
+              "uniTagMatch": 55,
+              "ponCTag": 55,
+              "ponSTag": 550,
+              "technologyProfileId": 4,
+              "upstreamBandwidthProfile": "VOD",
+              "downstreamBandwidthProfile": "Service3",
+              "isDhcpRequired": "true",
+              "isIgmpRequired": "true"
+            },
+            {
+              "ponCTag": 55,
+              "ponSTag": 555,
+              "dsPonCTagPriority": 5,
+              "dsPonSTagPriority": 5,
+              "technologyProfileId": 4,
+              "serviceName": "MC"
+            }
+          ]
         }, {
           "id" : "1",
           "hardwareIdentifier" : "00:00:00:00:00:01",
@@ -24,39 +56,76 @@
           "uplinkPort": "2"
         } ]
       },
-         "bandwidthprofile":{
-            "integration":{
-               "cache":{
-                  "enabled":true,
-                  "maxsize":40,
-                  "ttl":"PT1m"
-               }
-            },
-            "entries":[
-               {
-                  "id":"High-Speed-Internet",
-                  "cir":200000000,
-                  "cbs":348000,
-                  "eir":10000000,
-                  "ebs":348000,
-                  "air":10000000
-               },
-               {
-                  "id":"User1-Specific",
-                  "cir":300000000,
-                  "cbs":348000,
-                  "eir":20000000,
-                  "ebs":348000
-               }
-            ]
-         }
+      "bandwidthprofile":{
+        "integration":{
+          "cache":{
+            "enabled":true,
+            "maxsize":40,
+            "ttl":"PT1m"
+          }
+        },
+        "entries":[
+          {
+            "id":"High-Speed-Internet",
+            "cir": 500000,
+            "cbs": 10000,
+            "eir": 500000,
+            "ebs": 10000,
+            "air": 100000
+          },
+          {
+            "id":"VOIP",
+            "cir": 500000,
+            "cbs": 10000,
+            "eir": 500000,
+            "ebs": 10000,
+            "air": 100000
+          },
+          {
+            "id":"Service1",
+            "cir": 600000,
+            "cbs": 10000,
+            "eir": 400000,
+            "ebs": 10000
+          },
+          {
+            "id":"Service2",
+            "cir": 600000,
+            "cbs": 10000,
+            "eir": 400000,
+            "ebs": 10000
+          },
+          {
+            "id":"VOD",
+            "cir": 600000,
+            "cbs": 10000,
+            "eir": 400000,
+            "ebs": 10000
+          },
+          {
+            "id":"Service3",
+            "cir": 600000,
+            "cbs": 10000,
+            "eir": 400000,
+            "ebs": 10000
+          },
+          {
+            "id":"Default",
+            "cir": 0,
+            "cbs": 0,
+            "eir": 512,
+            "ebs": 30,
+            "air": 0
+          }
+        ]
+      }
     }
   },
-   "devices":{
-      "of:0000000000000001":{
-         "basic":{
-            "driver":"pmc-olt"
-         }
+  "devices":{
+    "of:0000000000000001":{
+      "basic":{
+        "driver":"pmc-olt"
       }
-   }
+    }
+  }
 }
diff --git a/app/src/main/resources/vlan_cfg_with_default.json b/app/src/main/resources/vlan_cfg_with_default.json
index 7cd792a..e168283 100644
--- a/app/src/main/resources/vlan_cfg_with_default.json
+++ b/app/src/main/resources/vlan_cfg_with_default.json
@@ -11,12 +11,17 @@
         },
         "entries" : [ {
           "id" : "s1-eth1",
-          "cTag" : 2,
-          "sTag" : 4,
           "nasPortId" : "s1-eth1",
-          "technologyProfileId" : 10,
-          "upstreamBandwidthProfile" : "High-Speed-Internet",
-          "downstreamBandwidthProfile" : "User1-Specific"
+          "uniTagList": [
+            {
+              "uniTagMatch": 35,
+              "ponCTag":33,
+              "ponSTag":7,
+              "technologyProfileId": 2,
+              "upstreamBandwidthProfile":"High-Speed-Internet",
+              "downstreamBandwidthProfile":"Service1"
+            }
+          ]
         }, {
           "id" : "1",
           "hardwareIdentifier" : "00:00:00:00:00:01",
@@ -35,26 +40,55 @@
         "entries":[
           {
             "id":"High-Speed-Internet",
-            "cir":200000000,
-            "cbs":348000,
-            "eir":10000000,
-            "ebs":348000,
-            "air":10000000
+            "cir": 500000,
+            "cbs": 10000,
+            "eir": 500000,
+            "ebs": 10000,
+            "air": 100000
           },
           {
-            "id":"User1-Specific",
-            "cir":300000000,
-            "cbs":348000,
-            "eir":20000000,
-            "ebs":348000
+            "id":"VOIP",
+            "cir": 500000,
+            "cbs": 10000,
+            "eir": 500000,
+            "ebs": 10000,
+            "air": 100000
+          },
+          {
+            "id":"Service1",
+            "cir": 600000,
+            "cbs": 10000,
+            "eir": 400000,
+            "ebs": 10000
+          },
+          {
+            "id":"Service2",
+            "cir": 600000,
+            "cbs": 10000,
+            "eir": 400000,
+            "ebs": 10000
+          },
+          {
+            "id":"VOD",
+            "cir": 600000,
+            "cbs": 10000,
+            "eir": 400000,
+            "ebs": 10000
+          },
+          {
+            "id":"Service3",
+            "cir": 600000,
+            "cbs": 10000,
+            "eir": 400000,
+            "ebs": 10000
           },
           {
             "id":"Default",
-            "cir":0,
-            "cbs":0,
-            "eir":512,
-            "ebs":30,
-            "air":0
+            "cir": 0,
+            "cbs": 0,
+            "eir": 512,
+            "ebs": 30,
+            "air": 0
           }
         ]
       }
diff --git a/app/src/test/java/org/opencord/olt/impl/OltFlowTest.java b/app/src/test/java/org/opencord/olt/impl/OltFlowTest.java
new file mode 100644
index 0000000..8a80ee0
--- /dev/null
+++ b/app/src/test/java/org/opencord/olt/impl/OltFlowTest.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.olt.impl;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ListMultimap;
+import org.apache.commons.lang3.tuple.Pair;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.EthType;
+import org.onlab.packet.VlanId;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.cluster.RoleInfo;
+import org.onosproject.mastership.MastershipInfo;
+import org.onosproject.mastership.MastershipListener;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.MastershipRole;
+import org.onosproject.net.PortNumber;
+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.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.flow.instructions.L2ModificationInstruction;
+import org.onosproject.net.flowobjective.FilteringObjQueueKey;
+import org.onosproject.net.flowobjective.FilteringObjective;
+import org.onosproject.net.flowobjective.ForwardingObjQueueKey;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.NextObjQueueKey;
+import org.onosproject.net.flowobjective.NextObjective;
+import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.meter.MeterId;
+import org.onosproject.net.meter.MeterKey;
+import org.opencord.sadis.BandwidthProfileInformation;
+import org.opencord.sadis.UniTagInformation;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+
+public class OltFlowTest extends TestBase {
+    private OltFlowService oltFlowService;
+
+    PortNumber uniPortNumber = PortNumber.portNumber(1);
+    PortNumber nniPortNumber = PortNumber.portNumber(65535);
+
+    UniTagInformation.Builder tagInfoBuilder = new UniTagInformation.Builder();
+    UniTagInformation uniTagInfo = tagInfoBuilder.setUniTagMatch(VlanId.vlanId((short) 35))
+            .setPonCTag(VlanId.vlanId((short) 33))
+            .setPonSTag(VlanId.vlanId((short) 7))
+            .setDsPonCTagPriority(0)
+            .setUsPonSTagPriority(0)
+            .setTechnologyProfileId(64)
+            .setDownstreamBandwidthProfile(dsBpId)
+            .setUpstreamBandwidthProfile(usBpId)
+            .build();
+
+    @Before
+    public void setUp() {
+        oltFlowService = new OltFlowService();
+        oltFlowService.oltMeterService = new MockOltMeterService();
+        oltFlowService.flowObjectiveService = new MockOltFlowObjectiveService();
+        oltFlowService.mastershipService = new MockMastershipService();
+        oltFlowService.sadisService = new MockSadisService();
+        oltFlowService.bpService = oltFlowService.sadisService.getBandwidthProfileService();
+        oltFlowService.appId = appId;
+    }
+
+    @Test
+    public void testDhcpFiltering() {
+        oltFlowService.processDhcpFilteringObjectives(DEVICE_ID_1, uniPortNumber, usMeterId, uniTagInfo,
+                true, true);
+        oltFlowService.processDhcpFilteringObjectives(DEVICE_ID_1, uniPortNumber, usMeterId, uniTagInfo,
+                false, true);
+        oltFlowService.processDhcpFilteringObjectives(DEVICE_ID_1, uniPortNumber, dsMeterId, uniTagInfo,
+                true, false);
+        oltFlowService.processDhcpFilteringObjectives(DEVICE_ID_1, uniPortNumber, usMeterId, uniTagInfo,
+                false, false);
+    }
+
+    @Test
+    public void testIgmpFiltering() {
+        oltFlowService.processIgmpFilteringObjectives(DEVICE_ID_1, uniPortNumber, usMeterId, uniTagInfo,
+                true, true);
+        oltFlowService.processIgmpFilteringObjectives(DEVICE_ID_1, uniPortNumber, usMeterId, uniTagInfo,
+                false, true);
+    }
+
+    @Test
+    public void testEapolFiltering() {
+        addBandwidthProfile(uniTagInfo.getUpstreamBandwidthProfile());
+        oltFlowService.enableEapol = true;
+
+        //will install
+        oltFlowService.processEapolFilteringObjectives(DEVICE_ID_1, uniPortNumber,
+                uniTagInfo.getUpstreamBandwidthProfile(), new CompletableFuture<>(),
+                uniTagInfo.getUniTagMatch(), true);
+
+        //bp profile doesn't exist
+        oltFlowService.processEapolFilteringObjectives(DEVICE_ID_1, uniPortNumber,
+                uniTagInfo.getDownstreamBandwidthProfile(), new CompletableFuture<>(),
+                uniTagInfo.getUniTagMatch(), true);
+    }
+
+    @Test
+    public void testLldpFiltering() {
+        oltFlowService.processLldpFilteringObjective(DEVICE_ID_1, nniPortNumber, true);
+        oltFlowService.processLldpFilteringObjective(DEVICE_ID_1, nniPortNumber, false);
+    }
+
+    @Test
+    public void testNniFiltering() {
+        oltFlowService.enableDhcpOnProvisioning = true;
+        oltFlowService.enableIgmpOnProvisioning = true;
+        oltFlowService.processNniFilteringObjectives(DEVICE_ID_1, nniPortNumber, true);
+        oltFlowService.processNniFilteringObjectives(DEVICE_ID_1, nniPortNumber, false);
+    }
+
+    @Test
+    public void testUpBuilder() {
+        ForwardingObjective objective =
+                oltFlowService.createUpBuilder(nniPortNumber, uniPortNumber, usMeterId, uniTagInfo).add();
+        checkObjective(objective, true);
+    }
+
+    @Test
+    public void testDownBuilder() {
+        ForwardingObjective objective =
+                oltFlowService.createDownBuilder(nniPortNumber, uniPortNumber, dsMeterId, uniTagInfo).remove();
+        checkObjective(objective, false);
+    }
+
+    private void checkObjective(ForwardingObjective fwd, boolean upstream) {
+        TrafficTreatment treatment = fwd.treatment();
+
+        //check instructions
+        Set<Instructions.MeterInstruction> meters = treatment.meters();
+        assert !meters.isEmpty();
+
+        Instructions.MetadataInstruction writeMetadata = treatment.writeMetadata();
+        assert writeMetadata != null;
+
+        List<Instruction> immediateInstructions = treatment.immediate();
+        Optional<Instruction> vlanInstruction = immediateInstructions.stream()
+                .filter(i -> i.type() == Instruction.Type.L2MODIFICATION)
+                .filter(i -> ((L2ModificationInstruction) i).subtype() ==
+                        L2ModificationInstruction.L2SubType.VLAN_PUSH ||
+                        ((L2ModificationInstruction) i).subtype() ==
+                                L2ModificationInstruction.L2SubType.VLAN_POP)
+                .findAny();
+
+        assert vlanInstruction.isPresent();
+
+        //check match criteria
+        TrafficSelector selector = fwd.selector();
+        assert selector.getCriterion(Criterion.Type.IN_PORT) != null;
+        assert selector.getCriterion(Criterion.Type.VLAN_VID) != null;
+
+        if (!upstream) {
+            assert  selector.getCriterion(Criterion.Type.METADATA) != null;
+        }
+    }
+
+    private class MockOltMeterService implements org.opencord.olt.internalapi.AccessDeviceMeterService {
+        @Override
+        public ImmutableMap<String, Set<MeterKey>> getBpMeterMappings() {
+            return null;
+        }
+
+        @Override
+        public void addMeterIdToBpMapping(DeviceId deviceId, MeterId meterId, String bandwidthProfile) {
+
+        }
+
+        @Override
+        public MeterId getMeterIdFromBpMapping(DeviceId deviceId, String bandwidthProfile) {
+            return null;
+        }
+
+
+        @Override
+        public ImmutableSet<MeterKey> getProgMeters() {
+            return null;
+        }
+
+        @Override
+        public MeterId createMeter(DeviceId deviceId, BandwidthProfileInformation bpInfo,
+                                   CompletableFuture<Object> meterFuture) {
+            return usMeterId;
+        }
+    }
+
+    private class MockOltFlowObjectiveService implements org.onosproject.net.flowobjective.FlowObjectiveService {
+        @Override
+        public void filter(DeviceId deviceId, FilteringObjective filteringObjective) {
+
+            EthTypeCriterion ethType = (EthTypeCriterion)
+                    filterForCriterion(filteringObjective.conditions(), Criterion.Type.ETH_TYPE);
+
+            Instructions.MeterInstruction meter = filteringObjective.meta().metered();
+            Instruction writeMetadata = filteringObjective.meta().writeMetadata();
+            VlanIdCriterion vlanIdCriterion = (VlanIdCriterion)
+                    filterForCriterion(filteringObjective.conditions(), Criterion.Type.VLAN_VID);
+            PortCriterion portCriterion = (PortCriterion) filteringObjective.key();
+
+
+            if (ethType.ethType().equals(EthType.EtherType.LLDP.ethType()) ||
+                    portCriterion.port().equals(nniPortNumber)) {
+                assert meter == null;
+                assert writeMetadata == null;
+                assert vlanIdCriterion == null;
+            } else {
+                assert meter.meterId().equals(usMeterId) || meter.meterId().equals(dsMeterId);
+                assert writeMetadata != null;
+                assert vlanIdCriterion.vlanId() == uniTagInfo.getPonCTag();
+            }
+
+        }
+
+        @Override
+        public void forward(DeviceId deviceId, ForwardingObjective forwardingObjective) {
+
+        }
+
+        @Override
+        public void next(DeviceId deviceId, NextObjective nextObjective) {
+
+        }
+
+        @Override
+        public int allocateNextId() {
+            return 0;
+        }
+
+        @Override
+        public void initPolicy(String s) {
+
+        }
+
+        @Override
+        public void apply(DeviceId deviceId, Objective objective) {
+
+        }
+
+        @Override
+        public Map<Pair<Integer, DeviceId>, List<String>> getNextMappingsChain() {
+            return null;
+        }
+
+        @Override
+        public List<String> getNextMappings() {
+            return null;
+        }
+
+        @Override
+        public List<String> getPendingFlowObjectives() {
+            return null;
+        }
+
+        @Override
+        public ListMultimap<FilteringObjQueueKey, Objective> getFilteringObjQueue() {
+            return null;
+        }
+
+        @Override
+        public ListMultimap<ForwardingObjQueueKey, Objective> getForwardingObjQueue() {
+            return null;
+        }
+
+        @Override
+        public ListMultimap<NextObjQueueKey, Objective> getNextObjQueue() {
+            return null;
+        }
+
+        @Override
+        public Map<FilteringObjQueueKey, Objective> getFilteringObjQueueHead() {
+            return null;
+        }
+
+        @Override
+        public Map<ForwardingObjQueueKey, Objective> getForwardingObjQueueHead() {
+            return null;
+        }
+
+        @Override
+        public Map<NextObjQueueKey, Objective> getNextObjQueueHead() {
+            return null;
+        }
+
+        @Override
+        public void clearQueue() {
+
+        }
+
+        private Criterion filterForCriterion(Collection<Criterion> criteria, Criterion.Type type) {
+            return criteria.stream()
+                    .filter(c -> c.type().equals(type))
+                    .limit(1)
+                    .findFirst().orElse(null);
+        }
+    }
+
+    private class MockMastershipService implements org.onosproject.mastership.MastershipService {
+        @Override
+        public MastershipRole getLocalRole(DeviceId deviceId) {
+            return null;
+        }
+
+        @Override
+        public boolean isLocalMaster(DeviceId deviceId) {
+            return true;
+        }
+
+        @Override
+        public CompletableFuture<MastershipRole> requestRoleFor(DeviceId deviceId) {
+            return null;
+        }
+
+        @Override
+        public CompletableFuture<Void> relinquishMastership(DeviceId deviceId) {
+            return null;
+        }
+
+        @Override
+        public NodeId getMasterFor(DeviceId deviceId) {
+            return null;
+        }
+
+        @Override
+        public RoleInfo getNodesFor(DeviceId deviceId) {
+            return null;
+        }
+
+        @Override
+        public MastershipInfo getMastershipFor(DeviceId deviceId) {
+            return null;
+        }
+
+        @Override
+        public Set<DeviceId> getDevicesOf(NodeId nodeId) {
+            return null;
+        }
+
+        @Override
+        public void addListener(MastershipListener mastershipListener) {
+
+        }
+
+        @Override
+        public void removeListener(MastershipListener mastershipListener) {
+
+        }
+    }
+}
diff --git a/app/src/test/java/org/opencord/olt/impl/OltMeterTest.java b/app/src/test/java/org/opencord/olt/impl/OltMeterTest.java
new file mode 100644
index 0000000..7100b0f
--- /dev/null
+++ b/app/src/test/java/org/opencord/olt/impl/OltMeterTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.olt.impl;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.meter.DefaultMeter;
+import org.onosproject.net.meter.Meter;
+import org.onosproject.net.meter.MeterId;
+import org.onosproject.net.meter.MeterKey;
+import org.onosproject.net.meter.MeterListener;
+import org.onosproject.net.meter.MeterRequest;
+import org.opencord.sadis.BandwidthProfileInformation;
+
+import java.util.Collection;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+
+public class OltMeterTest extends TestBase {
+    private OltMeterService oltMeterService;
+
+    private BandwidthProfileInformation bandwidthProfileInformation = new BandwidthProfileInformation();
+
+    @Before
+    public void setUp() {
+        oltMeterService = new OltMeterService();
+        oltMeterService.bpInfoToMeter = Maps.newConcurrentMap();
+        oltMeterService.programmedMeters = Sets.newConcurrentHashSet();
+        oltMeterService.meterService = new MockMeterService();
+    }
+
+    @Test
+    public void testAddAndGetMeterIdToBpMapping() {
+        oltMeterService.addMeterIdToBpMapping(DEVICE_ID_1, usMeterId, usBpId);
+        MeterId usMeterId = oltMeterService.getMeterIdFromBpMapping(DEVICE_ID_1, usBpId);
+        assert usMeterId.equals(this.usMeterId);
+
+        oltMeterService.addMeterIdToBpMapping(DEVICE_ID_1, dsMeterId, dsBpId);
+        MeterId dsMeterId = oltMeterService.getMeterIdFromBpMapping(DEVICE_ID_1, dsBpId);
+        assert  dsMeterId.equals(this.dsMeterId);
+
+        ImmutableMap<String, Set<MeterKey>> meterMappings = oltMeterService.getBpMeterMappings();
+        assert  meterMappings.size() == 2;
+    }
+
+    @Test
+    public void testCreateMeter() {
+        //with provided bandwidth profile information
+        bandwidthProfileInformation.setId(usBpId);
+        bandwidthProfileInformation.setExceededInformationRate(10000);
+        bandwidthProfileInformation.setExceededBurstSize(10000L);
+        bandwidthProfileInformation.setCommittedBurstSize(10000L);
+        bandwidthProfileInformation.setCommittedInformationRate(10000);
+
+        oltMeterService.addMeterIdToBpMapping(DEVICE_ID_1, usMeterId, usBpId);
+
+
+        MeterId meterId =
+                oltMeterService.createMeter(DEVICE_ID_1, bandwidthProfileInformation, new CompletableFuture<>());
+        assert meterId != null;
+
+        //with null bandwidth profile information
+        meterId = oltMeterService.createMeter(DEVICE_ID_1, null, new CompletableFuture<>());
+        assert meterId == null;
+    }
+
+
+    private class MockMeterService implements org.onosproject.net.meter.MeterService {
+        @Override
+        public Meter submit(MeterRequest meterRequest) {
+            return DefaultMeter.builder()
+                    .forDevice(DEVICE_ID_1)
+                    .fromApp(appId)
+                    .withId(usMeterId)
+                    .build();
+        }
+
+        @Override
+        public void withdraw(MeterRequest meterRequest, MeterId meterId) {
+
+        }
+
+        @Override
+        public Meter getMeter(DeviceId deviceId, MeterId meterId) {
+            return null;
+        }
+
+        @Override
+        public Collection<Meter> getAllMeters() {
+            return null;
+        }
+
+        @Override
+        public Collection<Meter> getMeters(DeviceId deviceId) {
+            return null;
+        }
+
+        @Override
+        public MeterId allocateMeterId(DeviceId deviceId) {
+            return null;
+        }
+
+        @Override
+        public void freeMeterId(DeviceId deviceId, MeterId meterId) {
+
+        }
+
+        @Override
+        public void addListener(MeterListener meterListener) {
+
+        }
+
+        @Override
+        public void removeListener(MeterListener meterListener) {
+
+        }
+    }
+}
diff --git a/app/src/test/java/org/opencord/olt/impl/OltTest.java b/app/src/test/java/org/opencord/olt/impl/OltTest.java
index a925aa3..c417590 100644
--- a/app/src/test/java/org/opencord/olt/impl/OltTest.java
+++ b/app/src/test/java/org/opencord/olt/impl/OltTest.java
@@ -18,14 +18,10 @@
 import static org.junit.Assert.assertEquals;
 
 import java.util.Set;
-
 import org.junit.Before;
 import org.junit.Test;
 
 import org.onlab.packet.ChassisId;
-import org.onlab.packet.Ip4Address;
-import org.onlab.packet.MacAddress;
-import org.onlab.packet.VlanId;
 import org.onosproject.net.AnnotationKeys;
 import org.onosproject.net.Annotations;
 import org.onosproject.net.ConnectPoint;
@@ -38,24 +34,15 @@
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.device.DeviceServiceAdapter;
 import org.onosproject.net.provider.ProviderId;
-import org.opencord.sadis.BandwidthProfileInformation;
-import org.opencord.sadis.BaseInformationService;
 import org.opencord.sadis.SubscriberAndDeviceInformation;
-import org.opencord.sadis.SadisService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class OltTest {
+public class OltTest extends TestBase {
     private final Logger log = LoggerFactory.getLogger(getClass());
     private Olt olt;
 
-    private static final VlanId CLIENT_C_TAG = VlanId.vlanId((short) 999);
-    private static final VlanId CLIENT_S_TAG = VlanId.vlanId((short) 111);
-    private static final String CLIENT_NAS_PORT_ID = "PON 1/1";
-    private static final String CLIENT_CIRCUIT_ID = "CIR-PON 1/1";
 
-    private static final String OLT_DEV_ID = "of:00000000000000aa";
-    private static final DeviceId DEVICE_ID_1 = DeviceId.deviceId(OLT_DEV_ID);
     private static final String SCHEME_NAME = "olt";
     private static final DefaultAnnotations DEVICE_ANNOTATIONS = DefaultAnnotations.builder()
             .set(AnnotationKeys.PROTOCOL, SCHEME_NAME.toUpperCase()).build();
@@ -66,7 +53,6 @@
         olt.deviceService = new MockDeviceService();
         olt.sadisService = new MockSadisService();
         olt.subsService = olt.sadisService.getSubscriberInfoService();
-
     }
 
     /**
@@ -92,8 +78,6 @@
         SubscriberAndDeviceInformation s =  olt.getSubscriber(cp);
 
         assertEquals(s.circuitId(), CLIENT_CIRCUIT_ID);
-        assertEquals(s.cTag(), CLIENT_C_TAG);
-        assertEquals(s.sTag(), CLIENT_S_TAG);
         assertEquals(s.nasPortId(), CLIENT_NAS_PORT_ID);
     }
 
@@ -103,7 +87,7 @@
                           String manufacturer, String hwVersion, String swVersion,
                           String serialNumber, ChassisId chassisId, Annotations... annotations) {
             super(providerId, id, type, manufacturer, hwVersion, swVersion, serialNumber,
-                  chassisId, annotations);
+                    chassisId, annotations);
         }
     }
 
@@ -111,8 +95,8 @@
 
         private ProviderId providerId = new ProviderId("of", "foo");
         private final Device device1 = new MockDevice(providerId, DEVICE_ID_1, Device.Type.SWITCH,
-                                                      "foo.inc", "0", "0", OLT_DEV_ID, new ChassisId(),
-                                                      DEVICE_ANNOTATIONS);
+                "foo.inc", "0", "0", OLT_DEV_ID, new ChassisId(),
+                DEVICE_ANNOTATIONS);
 
         @Override
         public Device getDevice(DeviceId devId) {
@@ -170,53 +154,8 @@
         }
     }
 
-    private class MockSadisService implements SadisService {
 
 
-        @Override
-        public BaseInformationService<SubscriberAndDeviceInformation> getSubscriberInfoService() {
-            return new MockSubService();
-        }
 
-        @Override
-        public BaseInformationService<BandwidthProfileInformation> getBandwidthProfileService() {
-            return null;
-        }
-    }
-
-    private class MockSubService implements BaseInformationService<SubscriberAndDeviceInformation> {
-        MockSubscriberAndDeviceInformation sub =
-                new MockSubscriberAndDeviceInformation(CLIENT_NAS_PORT_ID, CLIENT_C_TAG,
-                                                       CLIENT_S_TAG, CLIENT_NAS_PORT_ID, CLIENT_CIRCUIT_ID, null, null);
-        @Override
-        public SubscriberAndDeviceInformation get(String id) {
-            return  sub;
-        }
-
-        @Override
-        public void invalidateAll() {}
-        @Override
-        public void invalidateId(String id) {}
-        @Override
-        public SubscriberAndDeviceInformation getfromCache(String id) {
-            return null;
-        }
-    }
-
-    private class MockSubscriberAndDeviceInformation extends SubscriberAndDeviceInformation {
-
-        MockSubscriberAndDeviceInformation(String id, VlanId ctag,
-                                           VlanId stag, String nasPortId,
-                                           String circuitId, MacAddress hardId,
-                                           Ip4Address ipAddress) {
-            this.setCTag(ctag);
-            this.setHardwareIdentifier(hardId);
-            this.setId(id);
-            this.setIPAddress(ipAddress);
-            this.setSTag(stag);
-            this.setNasPortId(nasPortId);
-            this.setCircuitId(circuitId);
-        }
-    }
 
 }
\ No newline at end of file
diff --git a/app/src/test/java/org/opencord/olt/impl/TestBase.java b/app/src/test/java/org/opencord/olt/impl/TestBase.java
new file mode 100644
index 0000000..b8447a9
--- /dev/null
+++ b/app/src/test/java/org/opencord/olt/impl/TestBase.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.olt.impl;
+
+import com.google.common.collect.Maps;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.MacAddress;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.meter.MeterId;
+import org.opencord.sadis.BandwidthProfileInformation;
+import org.opencord.sadis.BaseInformationService;
+import org.opencord.sadis.SadisService;
+import org.opencord.sadis.SubscriberAndDeviceInformation;
+
+import java.util.Map;
+
+public class TestBase {
+
+    protected static final String CLIENT_NAS_PORT_ID = "PON 1/1";
+    protected static final String CLIENT_CIRCUIT_ID = "CIR-PON 1/1";
+    protected static final String OLT_DEV_ID = "of:00000000000000aa";
+    protected static final DeviceId DEVICE_ID_1 = DeviceId.deviceId(OLT_DEV_ID);
+    protected MeterId usMeterId = MeterId.meterId(1);
+    protected MeterId dsMeterId = MeterId.meterId(2);
+    protected String usBpId = "HSIA-US";
+    protected String dsBpId = "HSIA-DS";
+    protected DefaultApplicationId appId = new DefaultApplicationId(1, "OltServices");
+
+    Map<String, BandwidthProfileInformation> bpInformation = Maps.newConcurrentMap();
+
+    protected void addBandwidthProfile(String id) {
+        BandwidthProfileInformation bpInfo = new BandwidthProfileInformation();
+        bpInfo.setAssuredInformationRate(0);
+        bpInfo.setCommittedInformationRate(10000);
+        bpInfo.setCommittedBurstSize(1000L);
+        bpInfo.setExceededBurstSize(2000L);
+        bpInfo.setExceededInformationRate(20000);
+        bpInformation.put(id, bpInfo);
+    }
+
+    protected class MockSadisService implements SadisService {
+
+        @Override
+        public BaseInformationService<SubscriberAndDeviceInformation> getSubscriberInfoService() {
+            return new MockSubService();
+        }
+
+        @Override
+        public BaseInformationService<BandwidthProfileInformation> getBandwidthProfileService() {
+            return new MockBpService();
+        }
+    }
+
+    private class MockBpService implements BaseInformationService<BandwidthProfileInformation> {
+
+        @Override
+        public void invalidateAll() {
+
+        }
+
+        @Override
+        public void invalidateId(String id) {
+
+        }
+
+        @Override
+        public BandwidthProfileInformation get(String id) {
+            return bpInformation.get(id);
+        }
+
+        @Override
+        public BandwidthProfileInformation getfromCache(String id) {
+            return null;
+        }
+    }
+
+    private class MockSubService implements BaseInformationService<SubscriberAndDeviceInformation> {
+        MockSubscriberAndDeviceInformation sub =
+                new MockSubscriberAndDeviceInformation(CLIENT_NAS_PORT_ID,
+                        CLIENT_NAS_PORT_ID, CLIENT_CIRCUIT_ID, null, null);
+
+        @Override
+        public SubscriberAndDeviceInformation get(String id) {
+            return sub;
+        }
+
+        @Override
+        public void invalidateAll() {
+        }
+
+        @Override
+        public void invalidateId(String id) {
+        }
+
+        @Override
+        public SubscriberAndDeviceInformation getfromCache(String id) {
+            return null;
+        }
+    }
+
+    private class MockSubscriberAndDeviceInformation extends SubscriberAndDeviceInformation {
+
+        MockSubscriberAndDeviceInformation(String id, String nasPortId,
+                                           String circuitId, MacAddress hardId,
+                                           Ip4Address ipAddress) {
+            this.setHardwareIdentifier(hardId);
+            this.setId(id);
+            this.setIPAddress(ipAddress);
+            this.setNasPortId(nasPortId);
+            this.setCircuitId(circuitId);
+        }
+    }
+}
diff --git a/pom.xml b/pom.xml
index 8853d7e..262cdc8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.onosproject</groupId>
         <artifactId>onos-dependencies</artifactId>
-        <version>1.13.9</version>
+        <version>1.13.10-rc2</version>
         <relativePath></relativePath>
     </parent>
 
@@ -33,7 +33,7 @@
 
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-        <onos.version>1.13.9</onos.version>
+        <onos.version>1.13.10-rc2</onos.version>
     </properties>
 
     <modules>
@@ -80,6 +80,18 @@
 
     </dependencies>
 
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-checkstyle-plugin</artifactId>
+                <configuration>
+                    <sourceDirectories>${project.build.sourceDirectory}</sourceDirectories>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
     <repositories>
         <repository>
             <id>central</id>