[VOL-3148] Only one meter creation is attempted for a given bandwidth profile.
Achieved through coordination of subscribers and meters.

Change-Id: I0377633a4ff5f34e817ec53382431d4a74d974c1
diff --git a/app/src/main/java/org/opencord/olt/impl/DeviceBandwidthProfile.java b/app/src/main/java/org/opencord/olt/impl/DeviceBandwidthProfile.java
new file mode 100644
index 0000000..49a4a34
--- /dev/null
+++ b/app/src/main/java/org/opencord/olt/impl/DeviceBandwidthProfile.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2020-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 org.onosproject.net.DeviceId;
+import org.opencord.sadis.BandwidthProfileInformation;
+
+import java.util.Objects;
+
+/**
+ * Class containing a mapping of DeviceId to BandwidthProfileInformation.
+ */
+class DeviceBandwidthProfile {
+    private final DeviceId devId;
+    private BandwidthProfileInformation bwInfo;
+
+    /**
+     * Creates the Mapping.
+     *
+     * @param devId  the device id
+     * @param bwInfo the bandwidth profile information
+     */
+    DeviceBandwidthProfile(DeviceId devId, BandwidthProfileInformation bwInfo) {
+        this.devId = devId;
+        this.bwInfo = bwInfo;
+    }
+
+    /**
+     * Returns the device id.
+     *
+     * @return device id.
+     */
+    DeviceId getDevId() {
+        return devId;
+    }
+
+    /**
+     * Returns the Bandwidth profile for this device.
+     *
+     * @return bandwidth profile information
+     */
+    BandwidthProfileInformation getBwInfo() {
+        return bwInfo;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        DeviceBandwidthProfile that = (DeviceBandwidthProfile) o;
+        return devId.equals(that.devId)
+                && bwInfo.equals(that.bwInfo);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(devId, bwInfo);
+    }
+
+    @Override
+    public String toString() {
+        return com.google.common.base.MoreObjects.toStringHelper(this)
+                .add("devId", devId)
+                .add("bwInfo", bwInfo)
+                .toString();
+    }
+}
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 2886025..d5e8e32 100644
--- a/app/src/main/java/org/opencord/olt/impl/Olt.java
+++ b/app/src/main/java/org/opencord/olt/impl/Olt.java
@@ -34,12 +34,14 @@
 import java.util.ArrayList;
 import java.util.Collection;
 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.ConcurrentHashMap;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
@@ -183,10 +185,14 @@
 
     private ConsistentMultimap<ConnectPoint, UniTagInformation> programmedSubs;
 
+    private Set<DeviceBandwidthProfile> pendingMeters;
+    private Set<SubscriberFlowInfo> pendingSubscribers;
+
     @Activate
     public void activate(ComponentContext context) {
         eventExecutor = newSingleThreadScheduledExecutor(groupedThreads("onos/olt",
                                                                         "events-%d", log));
+
         modified(context);
         ApplicationId appId = coreService.registerApplication(APP_NAME);
         componentConfigService.registerProperties(getClass());
@@ -202,6 +208,8 @@
                 .withApplicationId(appId)
                 .build();
 
+        pendingMeters = ConcurrentHashMap.newKeySet();
+        pendingSubscribers = Sets.newConcurrentHashSet();
         eventDispatcher.addSink(AccessDeviceEvent.class, listenerRegistry);
 
         subsService = sadisService.getSubscriberInfoService();
@@ -233,6 +241,7 @@
         clusterService.removeListener(clusterListener);
         deviceService.removeListener(deviceListener);
         eventDispatcher.removeSink(AccessDeviceEvent.class);
+        eventExecutor.shutdown();
         log.info("Stopped");
     }
 
@@ -258,7 +267,7 @@
 
     @Override
     public boolean provisionSubscriber(ConnectPoint connectPoint) {
-        log.info("Call to provision subscriber at {}", connectPoint);
+        log.info("Call to provisioning subscriber at {}", connectPoint);
         DeviceId deviceId = connectPoint.deviceId();
         PortNumber subscriberPortNo = connectPoint.port();
 
@@ -569,7 +578,7 @@
     private void provisionUniTagList(ConnectPoint connectPoint, PortNumber uplinkPort,
                                      SubscriberAndDeviceInformation sub) {
 
-        log.info("Provisioning vlans for subscriber {} on dev/port: {}", sub, connectPoint);
+        log.debug("Provisioning vlans for subscriber {} on dev/port: {}", sub, connectPoint);
 
         if (sub.uniTagList() == null || sub.uniTagList().isEmpty()) {
             log.warn("Unitaglist doesn't exist for the subscriber {}", sub.id());
@@ -629,7 +638,7 @@
     private void handleSubscriberFlows(DeviceId deviceId, PortNumber uplinkPort, PortNumber subscriberPort,
                                        UniTagInformation tagInfo) {
 
-        log.info("Provisioning vlan-based flows for the uniTagInformation {}", tagInfo);
+        log.debug("Provisioning vlan-based flows for the uniTagInformation {}", tagInfo);
 
         Port port = deviceService.getPort(deviceId, subscriberPort);
 
@@ -642,122 +651,192 @@
             return;
         }
 
-        ConnectPoint cp = new ConnectPoint(deviceId, subscriberPort);
-
         BandwidthProfileInformation upstreamBpInfo =
                 getBandwidthProfileInformation(tagInfo.getUpstreamBandwidthProfile());
         BandwidthProfileInformation downstreamBpInfo =
                 getBandwidthProfileInformation(tagInfo.getDownstreamBandwidthProfile());
+        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);
+            return;
+        }
+        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);
+            return;
+        }
 
-        CompletableFuture<Object> upstreamMeterFuture = new CompletableFuture<>();
-        CompletableFuture<Object> downsteamMeterFuture = new CompletableFuture<>();
+        // check for meterIds for the upstream and downstream bandwidth profiles
+        MeterId upMeterId = oltMeterService
+                .getMeterIdFromBpMapping(deviceId, upstreamBpInfo.id());
+        MeterId downMeterId = oltMeterService
+                .getMeterIdFromBpMapping(deviceId, downstreamBpInfo.id());
+        SubscriberFlowInfo fi = new SubscriberFlowInfo(deviceId, uplinkPort, subscriberPort,
+                                                       tagInfo, downMeterId, upMeterId,
+                                                       downstreamBpInfo.id(), upstreamBpInfo.id());
+
+        if (upMeterId != null && downMeterId != null) {
+            log.debug("Meters are existing for upstream {} and downstream {}",
+                     upstreamBpInfo.id(), downstreamBpInfo.id());
+            handleSubFlowsWithMeters(fi);
+        } else {
+            log.debug("Adding {} to pending subs", fi);
+            // one or both meters are not ready. It's possible they are in the process of being
+            // created for other subscribers that share the same bandwidth profile.
+            pendingSubscribers.add(fi);
+
+            // queue up the meters to be created
+            if (upMeterId == null) {
+                log.debug("Missing meter for upstream {}", upstreamBpInfo.id());
+                checkAndCreateDevMeter(new DeviceBandwidthProfile(deviceId, upstreamBpInfo));
+            }
+            if (downMeterId == null) {
+                log.debug("Missing meter for downstream {}", downstreamBpInfo.id());
+                checkAndCreateDevMeter(new DeviceBandwidthProfile(deviceId, downstreamBpInfo));
+            }
+        }
+    }
+    private void checkAndCreateDevMeter(DeviceBandwidthProfile dm) {
+        if (pendingMeters.contains(dm)) {
+            return;
+        }
+        pendingMeters.add(dm);
+        createMeter(dm);
+    }
+
+    private void createMeter(DeviceBandwidthProfile dm) {
+        log.debug("Creating Meter {} from queue", dm);
+        CompletableFuture<Object> meterFuture = new CompletableFuture<>();
+        MeterId meterId = oltMeterService.createMeter(dm.getDevId(), dm.getBwInfo(),
+                                                      meterFuture);
+
+        meterFuture.thenAcceptAsync(result -> {
+            // iterate through the subscribers on hold
+            Iterator<SubscriberFlowInfo> subsIterator = pendingSubscribers.iterator();
+            while (subsIterator.hasNext()) {
+                SubscriberFlowInfo fi = subsIterator.next();
+                if (result == null) {
+                    // meter install sent to device
+                    log.debug("Meter {} installed for bw {}", meterId, dm.getBwInfo());
+
+                    MeterId upMeterId = oltMeterService
+                            .getMeterIdFromBpMapping(dm.getDevId(), fi.getUpBpInfo());
+                    MeterId downMeterId = oltMeterService
+                            .getMeterIdFromBpMapping(dm.getDevId(), fi.getDownBpInfo());
+                    if (upMeterId != null && downMeterId != null) {
+                        log.debug("Provisioning subscriber after meter {}" +
+                                          "installation and both meters are present " +
+                                          "upstream {} and downstream {}",
+                                  meterId, upMeterId, downMeterId);
+                        // put in the meterIds  because when fi was first
+                        // created there may or may not have been a meterId
+                        // depending on whether the meter was created or
+                        // not at that time.
+                        fi.setUpMeterId(upMeterId);
+                        fi.setDownMeterId(downMeterId);
+                        handleSubFlowsWithMeters(fi);
+                        subsIterator.remove();
+                    }
+                    pendingMeters.remove(new DeviceBandwidthProfile(dm.getDevId(), dm.getBwInfo()));
+                } else {
+                    // meter install failed
+                    log.error("Addition of subscriber {} failed due to meter " +
+                                      "{} with result {}", fi, meterId, result);
+                    subsIterator.remove();
+                    pendingMeters.remove(new DeviceBandwidthProfile(dm.getDevId(), dm.getBwInfo()));
+                }
+            }
+        });
+    }
+    /**
+     * Add subscriber flows given meter information for both upstream and
+     * downstream directions.
+     *
+     * @param subscriberFlowInfo relevant information for subscriber
+     */
+    private void handleSubFlowsWithMeters(SubscriberFlowInfo subscriberFlowInfo) {
+        log.debug("Provisioning subscriber flows based on {}", subscriberFlowInfo);
+        UniTagInformation tagInfo = subscriberFlowInfo.getTagInfo();
         CompletableFuture<ObjectiveError> upFuture = new CompletableFuture<>();
         CompletableFuture<ObjectiveError> downFuture = new CompletableFuture<>();
 
-        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 =
-                        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);
-                    }
-
-                    @Override
-                    public void onError(Objective objective, ObjectiveError error) {
-                        upFuture.complete(error);
-                    }
-                }));
-
-            } 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.warn("Meter installation error while sending upstream flows. " +
-                                 "Result {} and MeterId {}", result, upstreamMeterId);
+        ForwardingObjective.Builder upFwd =
+                oltFlowService.createUpBuilder(subscriberFlowInfo.getNniPort(), subscriberFlowInfo.getUniPort(),
+                                               subscriberFlowInfo.getUpId(), subscriberFlowInfo.getTagInfo());
+        flowObjectiveService.forward(subscriberFlowInfo.getDevId(), upFwd.add(new ObjectiveContext() {
+            @Override
+            public void onSuccess(Objective objective) {
+                log.debug("Upstream flow installed successfully {}", subscriberFlowInfo);
+                upFuture.complete(null);
             }
-        }).exceptionally(ex -> {
-            log.error("Upstream flow failed: " + ex.getMessage());
-            upFuture.complete(ObjectiveError.UNKNOWN);
-            return null;
-        });
 
-        downsteamMeterFuture.thenAcceptAsync(result -> {
-            if (result == null) {
-                log.info("Downstream Meter {} is sent to the device {}. " +
-                                 "Sending subscriber flows.", downstreamMeterId, deviceId);
-
-                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);
-                    }
-
-                    @Override
-                    public void onError(Objective objective, ObjectiveError error) {
-                        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.warn("Meter installation error while sending upstream flows. " +
-                                 "Result {} and MeterId {}", result, downstreamMeterId);
+            @Override
+            public void onError(Objective objective, ObjectiveError error) {
+                upFuture.complete(error);
             }
-        }).exceptionally(ex -> {
-            log.error("Downstream flow failed: " + ex.getMessage());
-            downFuture.complete(ObjectiveError.UNKNOWN);
-            return null;
-        });
+        }));
+
+        ForwardingObjective.Builder downFwd =
+                oltFlowService.createDownBuilder(subscriberFlowInfo.getNniPort(), subscriberFlowInfo.getUniPort(),
+                                                 subscriberFlowInfo.getDownId(), subscriberFlowInfo.getTagInfo());
+        flowObjectiveService.forward(subscriberFlowInfo.getDevId(), downFwd.add(new ObjectiveContext() {
+            @Override
+            public void onSuccess(Objective objective) {
+                log.debug("Downstream flow installed successfully {}", subscriberFlowInfo);
+                downFuture.complete(null);
+            }
+
+            @Override
+            public void onError(Objective objective, ObjectiveError error) {
+                downFuture.complete(error);
+            }
+        }));
 
         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: {}",
-                          tagInfo.getPonCTag(), tagInfo.getPonSTag(), deviceId, cp, downStatus);
+                          tagInfo.getPonCTag(), tagInfo.getPonSTag(), subscriberFlowInfo.getDevId(),
+                          subscriberFlowInfo.getUniPort(), 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: {}",
-                          tagInfo.getPonCTag(), tagInfo.getPonSTag(), deviceId, cp, upStatus);
+                          tagInfo.getPonCTag(), tagInfo.getPonSTag(), subscriberFlowInfo.getDevId(),
+                          subscriberFlowInfo.getUniPort(), upStatus);
                 type = AccessDeviceEvent.Type.SUBSCRIBER_UNI_TAG_REGISTRATION_FAILED;
             } else {
-                log.info("Upstream and downstream data plane flows are installed successfully.");
-                oltFlowService.processEapolFilteringObjectives(deviceId, subscriberPort,
+                log.debug("Upstream and downstream data plane flows are installed successfully " +
+                                 "for {}", subscriberFlowInfo);
+                oltFlowService.processEapolFilteringObjectives(subscriberFlowInfo.getDevId(),
+                                                               subscriberFlowInfo.getUniPort(),
                                                                tagInfo.getUpstreamBandwidthProfile(),
                                                                null, tagInfo.getPonCTag(), true);
-                oltFlowService.processDhcpFilteringObjectives(deviceId, subscriberPort,
-                                                              upstreamMeterId, tagInfo, true, true);
+                oltFlowService.processDhcpFilteringObjectives(subscriberFlowInfo.getDevId(),
+                                                              subscriberFlowInfo.getUniPort(),
+                                                              subscriberFlowInfo.getUpId(),
+                                                              tagInfo, true, true);
 
-                oltFlowService.processIgmpFilteringObjectives(deviceId, subscriberPort, upstreamMeterId, tagInfo,
-                                                              true, true);
-                updateProgrammedSubscriber(cp, tagInfo, true);
-                post(new AccessDeviceEvent(type, deviceId, port, tagInfo.getPonSTag(), tagInfo.getPonCTag(),
-                                           tagInfo.getTechnologyProfileId()));
+                oltFlowService.processIgmpFilteringObjectives(subscriberFlowInfo.getDevId(),
+                                                              subscriberFlowInfo.getUniPort(),
+                                                              subscriberFlowInfo.getUpId(),
+                                                              tagInfo, true, true);
+                updateProgrammedSubscriber(new ConnectPoint(subscriberFlowInfo.getDevId(),
+                                                            subscriberFlowInfo.getUniPort()),
+                                           tagInfo, true);
             }
+            post(new AccessDeviceEvent(type, subscriberFlowInfo.getDevId(),
+                                       deviceService.getPort(subscriberFlowInfo.getDevId(),
+                                                             subscriberFlowInfo.getUniPort()),
+                                       tagInfo.getPonSTag(), tagInfo.getPonCTag(),
+                                       tagInfo.getTechnologyProfileId()));
         }, oltInstallers);
     }
 
@@ -860,9 +939,9 @@
         }
         // Return the port that has been configured as the uplink port of this OLT in Sadis
         Optional<Port> optionalPort = deviceService.getPorts(dev.id()).stream()
-            .filter(port -> isNniPort(port) ||
-                (port.number().toLong() == deviceInfo.uplinkPort()))
-            .findFirst();
+                .filter(port -> isNniPort(port) ||
+                        (port.number().toLong() == deviceInfo.uplinkPort()))
+                .findFirst();
         if (optionalPort.isPresent()) {
             log.trace("getUplinkPort: Found port {}", optionalPort.get());
             return optionalPort.get();
@@ -964,7 +1043,7 @@
                 if (!isLocalLeader && event.type().equals(DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED)
                         && !deviceService.isAvailable(devId) && deviceService.getPorts(devId).isEmpty()) {
                     log.info("Cleaning local state for non master instance upon " +
-                                      "device disconnection {}", devId);
+                                     "device disconnection {}", devId);
                     programmedDevices.remove(devId);
                     // Since no mastership of the device is present upon disconnection
                     // the method in the FlowRuleManager only empties the local copy
@@ -1073,12 +1152,12 @@
                         } else {
                             if (deviceService.getPorts(devId).isEmpty()) {
                                 log.info("Handling controlled device disconnection .. "
-                                        + "flushing all state for dev:{}", devId);
+                                                 + "flushing all state for dev:{}", devId);
                                 handleDeviceDisconnection(dev, false);
                             } else {
                                 log.info("Disconnected device has available ports .. "
-                                        + "assuming temporary disconnection, "
-                                        + "retaining state for device {}", devId);
+                                                 + "assuming temporary disconnection, "
+                                                 + "retaining state for device {}", devId);
                             }
                         }
                         break;
@@ -1141,4 +1220,5 @@
             }
         }
     }
+
 }
diff --git a/app/src/main/java/org/opencord/olt/impl/OltMeterService.java b/app/src/main/java/org/opencord/olt/impl/OltMeterService.java
index 3e3962c..80aae4a 100644
--- a/app/src/main/java/org/opencord/olt/impl/OltMeterService.java
+++ b/app/src/main/java/org/opencord/olt/impl/OltMeterService.java
@@ -157,6 +157,8 @@
     }
 
     void addMeterIdToBpMapping(DeviceId deviceId, MeterId meterId, String bandwidthProfile) {
+        log.debug("adding bp {} to meter {} mapping for device {}",
+                 bandwidthProfile, meterId, deviceId);
         bpInfoToMeter.put(bandwidthProfile, MeterKey.key(deviceId, meterId));
     }
 
@@ -177,8 +179,8 @@
                     meterKeyForDevice.get().meterId(), bandwidthProfile);
             return meterKeyForDevice.get().meterId();
         } else {
-            log.warn("Bandwidth profile '{}' is not currently mapped to a meter",
-                    bandwidthProfile);
+            log.warn("Bandwidth Profile '{}' is not currently mapped to a meter in {}",
+                     bandwidthProfile, bpInfoToMeter.get(bandwidthProfile).value());
             return null;
         }
     }
@@ -215,6 +217,9 @@
                 .withContext(new MeterContext() {
                     @Override
                     public void onSuccess(MeterRequest op) {
+                        log.debug("Meter {} is installed on the device {}",
+                                 meterId, deviceId);
+                        addMeterIdToBpMapping(deviceId, meterIdRef.get(), bpInfo.id());
                         meterFuture.complete(null);
                     }
 
@@ -232,7 +237,6 @@
 
         Meter meter = meterService.submit(meterRequest);
         meterIdRef.set(meter.id());
-        addMeterIdToBpMapping(deviceId, meterIdRef.get(), bpInfo.id());
         log.info("Meter is created. Meter Id {}", meter.id());
         return meter.id();
     }
diff --git a/app/src/main/java/org/opencord/olt/impl/SubscriberFlowInfo.java b/app/src/main/java/org/opencord/olt/impl/SubscriberFlowInfo.java
new file mode 100644
index 0000000..f923b83
--- /dev/null
+++ b/app/src/main/java/org/opencord/olt/impl/SubscriberFlowInfo.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2020-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 org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.meter.MeterId;
+import org.opencord.sadis.UniTagInformation;
+
+import java.util.Objects;
+
+/**
+ * Contains the mapping of a given port to flow information, including bandwidth profile.
+ */
+class SubscriberFlowInfo {
+    private final DeviceId devId;
+    private final PortNumber nniPort;
+    private final PortNumber uniPort;
+    private final UniTagInformation tagInfo;
+    private MeterId downId;
+    private MeterId upId;
+    private final String downBpInfo;
+    private final String upBpInfo;
+
+    /**
+     * Builds the mapper of information.
+     * @param devId the device id
+     * @param nniPort the nni port
+     * @param uniPort the uni port
+     * @param tagInfo the tag info
+     * @param downId the downstream meter id
+     * @param upId the upstream meter id
+     * @param downBpInfo the downstream bandwidth profile
+     * @param upBpInfo the upstream bandwidth profile
+     */
+    SubscriberFlowInfo(DeviceId devId, PortNumber nniPort, PortNumber uniPort,
+                       UniTagInformation tagInfo, MeterId downId, MeterId upId,
+                       String downBpInfo, String upBpInfo) {
+        this.devId = devId;
+        this.nniPort = nniPort;
+        this.uniPort = uniPort;
+        this.tagInfo = tagInfo;
+        this.downId = downId;
+        this.upId = upId;
+        this.downBpInfo = downBpInfo;
+        this.upBpInfo = upBpInfo;
+    }
+
+    /**
+     * Gets the device id of this subscriber and flow information.
+     *
+     * @return device id
+     */
+    DeviceId getDevId() {
+        return devId;
+    }
+
+    /**
+     * Gets the nni of this subscriber and flow information.
+     *
+     * @return nni port
+     */
+    PortNumber getNniPort() {
+        return nniPort;
+    }
+
+    /**
+     * Gets the uni port of this subscriber and flow information.
+     *
+     * @return uni port
+     */
+    PortNumber getUniPort() {
+        return uniPort;
+    }
+
+    /**
+     * Gets the tag of this subscriber and flow information.
+     *
+     * @return tag of the subscriber
+     */
+    UniTagInformation getTagInfo() {
+        return tagInfo;
+    }
+
+    /**
+     * Gets the downstream meter id of this subscriber and flow information.
+     *
+     * @return downstream meter id
+     */
+    MeterId getDownId() {
+        return downId;
+    }
+
+    /**
+     * Gets the upstream meter id of this subscriber and flow information.
+     *
+     * @return upstream meter id
+     */
+    MeterId getUpId() {
+        return upId;
+    }
+
+    /**
+     * Gets the downstream bandwidth profile of this subscriber and flow information.
+     *
+     * @return downstream bandwidth profile
+     */
+    String getDownBpInfo() {
+        return downBpInfo;
+    }
+
+    /**
+     * Gets the upstream bandwidth profile of this subscriber and flow information.
+     *
+     * @return upstream bandwidth profile.
+     */
+    String getUpBpInfo() {
+        return upBpInfo;
+    }
+
+    /**
+     * Sets the upstream meter id.
+     * @param upMeterId the upstream meter id
+     */
+    void setUpMeterId(MeterId upMeterId) {
+        this.upId = upMeterId;
+    }
+
+    /**
+     * Sets the downstream meter id.
+     * @param downMeterId the downstream meter id
+     */
+    void setDownMeterId(MeterId downMeterId) {
+        this.downId = downMeterId;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        SubscriberFlowInfo flowInfo = (SubscriberFlowInfo) o;
+        return devId.equals(flowInfo.devId) &&
+                nniPort.equals(flowInfo.nniPort) &&
+                uniPort.equals(flowInfo.uniPort) &&
+                tagInfo.equals(flowInfo.tagInfo) &&
+                downId.equals(flowInfo.downId) &&
+                upId.equals(flowInfo.upId) &&
+                downBpInfo.equals(flowInfo.downBpInfo) &&
+                upBpInfo.equals(flowInfo.upBpInfo);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(devId, nniPort, uniPort, tagInfo, downId, upId, downBpInfo, upBpInfo);
+    }
+
+    @Override
+    public String toString() {
+        return com.google.common.base.MoreObjects.toStringHelper(this)
+                .add("devId", devId)
+                .add("nniPort", nniPort)
+                .add("uniPort", uniPort)
+                .add("tagInfo", tagInfo)
+                .add("downId", downId)
+                .add("upId", upId)
+                .add("downBpInfo", downBpInfo)
+                .add("upBpInfo", upBpInfo)
+                .toString();
+    }
+}