Support monitoring underlay network using openstack telemetry app

Change-Id: I84f8735a700a89f28124fe3a76fafab339e3dbc1
diff --git a/apps/openstacktelemetry/api/src/main/java/org/onosproject/openstacktelemetry/api/StatsFlowRuleAdminService.java b/apps/openstacktelemetry/api/src/main/java/org/onosproject/openstacktelemetry/api/StatsFlowRuleAdminService.java
index cba2a33..e8a4a38 100644
--- a/apps/openstacktelemetry/api/src/main/java/org/onosproject/openstacktelemetry/api/StatsFlowRuleAdminService.java
+++ b/apps/openstacktelemetry/api/src/main/java/org/onosproject/openstacktelemetry/api/StatsFlowRuleAdminService.java
@@ -42,11 +42,18 @@
     void createStatFlowRule(StatsFlowRule statFlowRule);
 
     /**
-     * Gets a set of flow infos.
+     * Gets a set of flow infos collected from overlay network.
      *
      * @return a set of flow infos
      */
-    Set<FlowInfo> getFlowInfos();
+    Set<FlowInfo> getOverlayFlowInfos();
+
+    /**
+     * Gets a set of flow infos collected from underlay network.
+     *
+     * @return a set of flow infos
+     */
+    Set<FlowInfo> getUnderlayFlowInfos();
 
     /**
      * Deletes stat flow rule.
diff --git a/apps/openstacktelemetry/app/src/main/java/org/onosproject/openstacktelemetry/impl/InfluxDbTelemetryManager.java b/apps/openstacktelemetry/app/src/main/java/org/onosproject/openstacktelemetry/impl/InfluxDbTelemetryManager.java
index 99a64564..e35d662 100644
--- a/apps/openstacktelemetry/app/src/main/java/org/onosproject/openstacktelemetry/impl/InfluxDbTelemetryManager.java
+++ b/apps/openstacktelemetry/app/src/main/java/org/onosproject/openstacktelemetry/impl/InfluxDbTelemetryManager.java
@@ -156,21 +156,17 @@
         BatchPoints batchPoints = BatchPoints.database(database).build();
 
         for (FlowInfo flowInfo: record.flowInfos()) {
-            Point point = Point
+            Point.Builder pointBuilder = Point
                     .measurement((measurement == null) ? record.measurement() : measurement)
                     .tag(FLOW_TYPE, String.valueOf(flowInfo.flowType()))
                     .tag(DEVICE_ID, flowInfo.deviceId().toString())
                     .tag(INPUT_INTERFACE_ID, String.valueOf(flowInfo.inputInterfaceId()))
                     .tag(OUTPUT_INTERFACE_ID, String.valueOf(flowInfo.outputInterfaceId()))
-                    .tag(VLAN_ID, flowInfo.vlanId().toString())
                     .tag(VXLAN_ID, String.valueOf(flowInfo.vxlanId()))
                     .tag(SRC_IP, flowInfo.srcIp().toString())
                     .tag(DST_IP, flowInfo.dstIp().toString())
-                    .tag(SRC_PORT, getTpPort(flowInfo.srcPort()))
                     .tag(DST_PORT, getTpPort(flowInfo.dstPort()))
                     .tag(PROTOCOL, String.valueOf(flowInfo.protocol()))
-                    .tag(SRC_MAC, flowInfo.srcMac().toString())
-                    .tag(DST_MAC, flowInfo.dstMac().toString())
                     .addField(STARTUP_TIME, flowInfo.statsInfo().startupTime())
                     .addField(FST_PKT_ARR_TIME, flowInfo.statsInfo().fstPktArrTime())
                     .addField(LST_PKT_OFFSET, flowInfo.statsInfo().lstPktOffset())
@@ -179,9 +175,21 @@
                     .addField(CURR_ACC_BYTES, flowInfo.statsInfo().currAccBytes())
                     .addField(CURR_ACC_PKTS, flowInfo.statsInfo().currAccPkts())
                     .addField(ERROR_PKTS, flowInfo.statsInfo().errorPkts())
-                    .addField(DROP_PKTS, flowInfo.statsInfo().dropPkts())
-                    .build();
-            batchPoints.point(point);
+                    .addField(DROP_PKTS, flowInfo.statsInfo().dropPkts());
+
+            if (flowInfo.vlanId() != null) {
+                pointBuilder.tag(VLAN_ID, flowInfo.vlanId().toString());
+            }
+
+            if (flowInfo.srcPort() != null) {
+                pointBuilder.tag(SRC_PORT, getTpPort(flowInfo.srcPort()));
+            }
+
+            if (flowInfo.dstPort() != null) {
+                pointBuilder.tag(DST_PORT, getTpPort(flowInfo.dstPort()));
+            }
+
+            batchPoints.point(pointBuilder.build());
         }
         producer.write(batchPoints);
     }
diff --git a/apps/openstacktelemetry/app/src/main/java/org/onosproject/openstacktelemetry/impl/StatsFlowRuleManager.java b/apps/openstacktelemetry/app/src/main/java/org/onosproject/openstacktelemetry/impl/StatsFlowRuleManager.java
index 79cb4c7..760fd86 100644
--- a/apps/openstacktelemetry/app/src/main/java/org/onosproject/openstacktelemetry/impl/StatsFlowRuleManager.java
+++ b/apps/openstacktelemetry/app/src/main/java/org/onosproject/openstacktelemetry/impl/StatsFlowRuleManager.java
@@ -34,11 +34,15 @@
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Host;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.device.PortStatistics;
+import org.onosproject.net.driver.Driver;
+import org.onosproject.net.driver.DriverService;
 import org.onosproject.net.flow.DefaultFlowRule;
 import org.onosproject.net.flow.DefaultTrafficSelector;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
@@ -49,8 +53,10 @@
 import org.onosproject.net.flow.FlowRuleService;
 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.IPCriterion;
 import org.onosproject.net.flow.criteria.IPProtocolCriterion;
+import org.onosproject.net.flow.criteria.PortCriterion;
 import org.onosproject.net.flow.criteria.TcpPortCriterion;
 import org.onosproject.net.flow.criteria.UdpPortCriterion;
 import org.onosproject.net.host.HostService;
@@ -68,6 +74,7 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.ArrayList;
 import java.util.Dictionary;
 import java.util.LinkedList;
 import java.util.List;
@@ -82,6 +89,7 @@
 import static org.onlab.packet.Ethernet.TYPE_IPV4;
 import static org.onlab.packet.IPv4.PROTOCOL_TCP;
 import static org.onlab.packet.IPv4.PROTOCOL_UDP;
+import static org.onosproject.net.Device.Type.SWITCH;
 import static org.onosproject.net.flow.criteria.Criterion.Type.IPV4_DST;
 import static org.onosproject.net.flow.criteria.Criterion.Type.IPV4_SRC;
 import static org.onosproject.net.flow.criteria.Criterion.Type.IP_PROTO;
@@ -96,6 +104,7 @@
 import static org.onosproject.openstacknetworking.api.Constants.VTAP_INBOUND_TABLE;
 import static org.onosproject.openstacknetworking.api.Constants.VTAP_OUTBOUND_TABLE;
 import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.COMPUTE;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.CONTROLLER;
 import static org.onosproject.openstacktelemetry.api.Constants.DEFAULT_DATA_POINT_SIZE;
 import static org.onosproject.openstacktelemetry.api.Constants.FLAT;
 import static org.onosproject.openstacktelemetry.api.Constants.OPENSTACK_TELEMETRY_APP_ID;
@@ -123,13 +132,22 @@
     private static final String EGRESS_STATS = "egressStats";
     private static final String PORT_STATS = "portStats";
 
+    private static final String MONITOR_OVERLAY = "monitorOverlay";
+    private static final String MONITOR_UNDERLAY = "monitorUnderlay";
+
+    private static final String OVS_DRIVER_NAME = "ovs";
+
     private static final boolean DEFAULT_REVERSE_PATH_STATS = false;
     private static final boolean DEFAULT_EGRESS_STATS = false;
     private static final boolean DEFAULT_PORT_STATS = true;
 
+    private static final boolean DEFAULT_MONITOR_OVERLAY = true;
+    private static final boolean DEFAULT_MONITOR_UNDERLAY = true;
+
     private static final String ARBITRARY_IP = "0.0.0.0/32";
     private static final int ARBITRARY_LENGTH = 32;
     private static final String ARBITRARY_MAC = "00:00:00:00:00:00";
+    private static final IpAddress NO_HOST_IP = IpAddress.valueOf("255.255.255.255");
     private static final MacAddress NO_HOST_MAC = MacAddress.valueOf(ARBITRARY_MAC);
     private static final int ARBITRARY_IN_INTF = 0;
     private static final int ARBITRARY_OUT_INTF = 0;
@@ -149,6 +167,9 @@
     protected DeviceService deviceService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DriverService driverService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ComponentConfigService componentConfigService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
@@ -180,7 +201,15 @@
             label = "A flag which indicates whether to collect port TX & RX stats.")
     private boolean portStats = DEFAULT_PORT_STATS;
 
-    private ApplicationId appId;
+    @Property(name = MONITOR_OVERLAY, boolValue = DEFAULT_MONITOR_OVERLAY,
+            label = "A flag which indicates whether to monitor overlay network port stats.")
+    private boolean monitorOverlay = DEFAULT_MONITOR_OVERLAY;
+
+    @Property(name = MONITOR_UNDERLAY, boolValue = DEFAULT_MONITOR_UNDERLAY,
+            label = "A flag which indicates whether to monitor underlay network port stats.")
+    private boolean monitorUnderlay = DEFAULT_MONITOR_UNDERLAY;
+
+    private ApplicationId telemetryAppId;
     private TelemetryCollector collector;
     private ScheduledFuture result;
 
@@ -195,7 +224,8 @@
 
     @Activate
     protected void activate() {
-        appId = coreService.registerApplication(OPENSTACK_TELEMETRY_APP_ID);
+        telemetryAppId = coreService.registerApplication(OPENSTACK_TELEMETRY_APP_ID);
+
         componentConfigService.registerProperties(getClass());
         start();
 
@@ -205,7 +235,7 @@
     @Deactivate
     protected void deactivate() {
         componentConfigService.unregisterProperties(getClass(), false);
-        flowRuleService.removeFlowRulesById(appId);
+        flowRuleService.removeFlowRulesById(telemetryAppId);
         stop();
 
         log.info("Stopped");
@@ -247,20 +277,77 @@
         setStatFlowRule(statsFlowRule, false);
     }
 
-    /**
-     * Gets a set of the flow infos.
-     *
-     * @return a set of flow infos
-     */
+
     @Override
-    public Set<FlowInfo> getFlowInfos() {
+    public Map<String, Queue<FlowInfo>> getFlowInfoMap() {
+        return flowInfoMap;
+    }
+
+
+    @Override
+    public Set<FlowInfo> getUnderlayFlowInfos() {
+
+        Set<FlowInfo> flowInfos = Sets.newConcurrentHashSet();
+
+        for (Device device : getUnderlayDevices()) {
+
+            if (!isEdgeSwitch(device.id())) {
+                continue;
+            }
+
+            for (FlowEntry entry : flowRuleService.getFlowEntries(device.id())) {
+                FlowInfo.Builder fBuilder = new DefaultFlowInfo.DefaultBuilder();
+                TrafficSelector selector = entry.selector();
+                Criterion inPort = selector.getCriterion(Criterion.Type.IN_PORT);
+                Criterion dstIpCriterion = selector.getCriterion(Criterion.Type.IPV4_DST);
+                if (inPort != null && dstIpCriterion != null) {
+                    IpAddress srcIp = getIpAddress(device, (PortCriterion) inPort);
+                    IpAddress dstIp = ((IPCriterion) dstIpCriterion).ip().address();
+
+                    if (srcIp == null) {
+                        continue;
+                    }
+
+                    fBuilder.withFlowType(FLOW_TYPE_SONA)
+                            .withSrcIp(IpPrefix.valueOf(srcIp, ARBITRARY_LENGTH))
+                            .withDstIp(IpPrefix.valueOf(dstIp, ARBITRARY_LENGTH))
+                            .withSrcMac(getMacAddress(srcIp))
+                            .withDstMac(getMacAddress(dstIp))
+                            .withInputInterfaceId(getInterfaceId(srcIp))
+                            .withOutputInterfaceId(getInterfaceId(dstIp))
+                            .withDeviceId(entry.deviceId());
+
+                    StatsInfo.Builder sBuilder = new DefaultStatsInfo.DefaultBuilder();
+
+                    sBuilder.withStartupTime(System.currentTimeMillis())
+                            .withFstPktArrTime(System.currentTimeMillis())
+                            .withLstPktOffset((int) (REFRESH_INTERVAL * MILLISECONDS))
+                            .withCurrAccPkts((int) entry.packets())
+                            .withCurrAccBytes(entry.bytes())
+                            .withErrorPkts((short) 0)
+                            .withDropPkts((short) 0);
+
+                    fBuilder.withStatsInfo(sBuilder.build());
+
+                    FlowInfo flowInfo = mergeFlowInfo(fBuilder.build(), fBuilder, sBuilder);
+
+                    flowInfos.add(flowInfo);
+                }
+            }
+        }
+
+        return flowInfos;
+    }
+
+    @Override
+    public Set<FlowInfo> getOverlayFlowInfos() {
+
         Set<FlowInfo> flowInfos = Sets.newConcurrentHashSet();
 
         // obtain all flow rule entries installed by telemetry app
-        for (FlowEntry entry : flowRuleService.getFlowEntriesById(appId)) {
+        for (FlowEntry entry : flowRuleService.getFlowEntriesById(telemetryAppId)) {
             FlowInfo.Builder fBuilder = new DefaultFlowInfo.DefaultBuilder();
             TrafficSelector selector = entry.selector();
-
             IPCriterion srcIp = (IPCriterion) selector.getCriterion(IPV4_SRC);
             IPCriterion dstIp = (IPCriterion) selector.getCriterion(IPV4_DST);
             IPProtocolCriterion ipProtocol =
@@ -301,8 +388,6 @@
 
             StatsInfo.Builder sBuilder = new DefaultStatsInfo.DefaultBuilder();
 
-            // TODO: need to collect error and drop packets stats
-            // TODO: need to make the refresh interval configurable
             sBuilder.withStartupTime(System.currentTimeMillis())
                     .withFstPktArrTime(System.currentTimeMillis())
                     .withLstPktOffset((int) (REFRESH_INTERVAL * MILLISECONDS))
@@ -324,11 +409,11 @@
     }
 
     /**
-     * Gets a set of flow infos by referring to destination VM port.
+     * Gets a set of flow infos by referring to overlay destination VM port.
      *
      * @return flow infos
      */
-    private Set<FlowInfo> getDstPortBasedFlowInfos() {
+    private Set<FlowInfo> getOverlayDstPortBasedFlowInfos() {
         Set<FlowInfo> flowInfos = Sets.newConcurrentHashSet();
         Set<PortNumber> instPortNums = instPortService.instancePorts()
                                                 .stream()
@@ -348,8 +433,8 @@
 
             stats.forEach(s -> {
                 InstancePort instPort = getInstancePort(d, s.portNumber());
-                flowInfos.add(buildTxPortInfo(instPort, s));
-                flowInfos.add(buildRxPortInfo(instPort, s));
+                flowInfos.add(buildTxFlowInfoFromInstancePort(instPort, s));
+                flowInfos.add(buildRxFlowInfoFromInstancePort(instPort, s));
             });
         });
 
@@ -357,21 +442,151 @@
     }
 
     /**
-     * Obtains the flow info generated by TX port.
+     * Gets a set of flow infos by referring to underlay destination port.
+     *
+     * @return flow infos
+     */
+    private Set<FlowInfo> getUnderlayDstPortBasedFlowInfos() {
+        Set<FlowInfo> flowInfos = Sets.newConcurrentHashSet();
+
+        for (Device d : getUnderlayDevices()) {
+            List<PortStatistics> stats =
+                    new ArrayList<>(deviceService.getPortStatistics(d.id()));
+            stats.forEach(s -> {
+                Host host = hostService.getConnectedHosts(new ConnectPoint(d.id(), s.portNumber()))
+                        .stream().findFirst().orElse(null);
+                if (host != null) {
+                    flowInfos.add(buildTxFlowInfoFromHost(host, s));
+                    flowInfos.add(buildRxFlowInfoFromHost(host, s));
+                }
+            });
+        }
+
+        return flowInfos;
+    }
+
+    /**
+     * Obtains a set of device instances which construct underlay network.
+     *
+     * @return a set of device instances
+     */
+    private Set<Device> getUnderlayDevices() {
+
+        Set<Device> underlayDevices = Sets.newConcurrentHashSet();
+
+        Set<DeviceId> overlayDeviceIds = osNodeService.completeNodes()
+                .stream()
+                .filter(n -> n.type() != CONTROLLER)
+                .map(OpenstackNode::intgBridge)
+                .collect(Collectors.toSet());
+
+        for (Device d : deviceService.getAvailableDevices(SWITCH)) {
+            if (overlayDeviceIds.contains(d.id())) {
+                continue;
+            }
+
+            underlayDevices.add(d);
+        }
+
+        return underlayDevices;
+    }
+
+    /**
+     * Checks whether the given drivers contains OVS driver.
+     *
+     * @param drivers a set of drivers
+     * @return true if the given drivers contain any OVS driver, false otherwise
+     */
+    private boolean hasOvsDriver(List<Driver> drivers) {
+
+        for (Driver driver : drivers) {
+            if (OVS_DRIVER_NAME.equals(driver.name())) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Obtains the flow info generated by TX port from instance port.
      *
      * @param instPort instance port
      * @param stat port statistics
      * @return flow info
      */
-    private FlowInfo buildTxPortInfo(InstancePort instPort, PortStatistics stat) {
+    private FlowInfo buildTxFlowInfoFromInstancePort(InstancePort instPort,
+                                                     PortStatistics stat) {
+        return buildTxFlowInfo(instPort.ipAddress(), instPort.macAddress(),
+                                                     instPort.deviceId(), stat);
+    }
+
+    /**
+     * Obtains the flow info generated from RX port from instance port.
+     *
+     * @param instPort instance port
+     * @param stat port statistics
+     * @return flow info
+     */
+    private FlowInfo buildRxFlowInfoFromInstancePort(InstancePort instPort,
+                                                     PortStatistics stat) {
+        return buildRxFlowInfo(instPort.ipAddress(), instPort.macAddress(),
+                instPort.deviceId(), stat);
+    }
+
+    /**
+     * Obtains the flow info generated by TX port from host.
+     *
+     * @param host host
+     * @param stat port statistics
+     * @return flow info
+     */
+    private FlowInfo buildTxFlowInfoFromHost(Host host, PortStatistics stat) {
+        IpAddress ip = host.ipAddresses().stream().findFirst().orElse(null);
+
+        if (ip != null) {
+            return buildTxFlowInfo(ip, host.mac(), host.location().deviceId(), stat);
+        }
+        return null;
+    }
+
+    /**
+     * Obtains the flow info generated by RX @param host host.
+     *
+     * @param host host
+     * @param stat port statistics
+     * @return flow info
+     */
+    private FlowInfo buildRxFlowInfoFromHost(Host host, PortStatistics stat) {
+        IpAddress ip = host.ipAddresses().stream().findFirst().orElse(null);
+
+        if (ip != null) {
+            return buildRxFlowInfo(ip, host.mac(), host.location().deviceId(), stat);
+        }
+        return null;
+    }
+
+    /**
+     * Obtains the flow info generated from TX port.
+     *
+     * @param ipAddress         IP address
+     * @param macAddress        MAC address
+     * @param deviceId          device identifier
+     * @param stat              port statistics
+     * @return flow info
+     */
+    private FlowInfo buildTxFlowInfo(IpAddress ipAddress,
+                                     MacAddress macAddress,
+                                     DeviceId deviceId,
+                                     PortStatistics stat) {
         FlowInfo.Builder fBuilder = new DefaultFlowInfo.DefaultBuilder();
 
         fBuilder.withFlowType(FLOW_TYPE_SONA)
-                .withSrcIp(IpPrefix.valueOf(instPort.ipAddress(), ARBITRARY_LENGTH))
+                .withSrcIp(IpPrefix.valueOf(ipAddress, ARBITRARY_LENGTH))
                 .withDstIp(IpPrefix.valueOf(ARBITRARY_IP))
-                .withSrcMac(instPort.macAddress())
+                .withSrcMac(macAddress)
                 .withDstMac(NO_HOST_MAC)
-                .withDeviceId(instPort.deviceId())
+                .withDeviceId(deviceId)
                 .withInputInterfaceId(ARBITRARY_IN_INTF)
                 .withOutputInterfaceId(ARBITRARY_OUT_INTF)
                 .withVlanId(VlanId.vlanId());
@@ -391,21 +606,26 @@
     }
 
     /**
-     * Obtains the flow info generated by RX port.
+     * Obtains the flow info generated from RX port.
      *
-     * @param instPort instance port
+     * @param ipAddress         IP address
+     * @param macAddress        MAC address
+     * @param deviceId          Device identifier
      * @param stat port statistics
      * @return flow info
      */
-    private FlowInfo buildRxPortInfo(InstancePort instPort, PortStatistics stat) {
+    private FlowInfo buildRxFlowInfo(IpAddress ipAddress,
+                                     MacAddress macAddress,
+                                     DeviceId deviceId,
+                                     PortStatistics stat) {
         FlowInfo.Builder fBuilder = new DefaultFlowInfo.DefaultBuilder();
 
         fBuilder.withFlowType(FLOW_TYPE_SONA)
                 .withSrcIp(IpPrefix.valueOf(ARBITRARY_IP))
-                .withDstIp(IpPrefix.valueOf(instPort.ipAddress(), ARBITRARY_LENGTH))
+                .withDstIp(IpPrefix.valueOf(ipAddress, ARBITRARY_LENGTH))
                 .withSrcMac(NO_HOST_MAC)
-                .withDstMac(instPort.macAddress())
-                .withDeviceId(instPort.deviceId())
+                .withDstMac(macAddress)
+                .withDeviceId(deviceId)
                 .withInputInterfaceId(ARBITRARY_IN_INTF)
                 .withOutputInterfaceId(ARBITRARY_OUT_INTF)
                 .withVlanId(VlanId.vlanId());
@@ -439,6 +659,17 @@
                                 .findFirst().orElse(null);
     }
 
+    /**
+     * Installs a flow rule where the source table is fromTable, while destination
+     * table is toTable.
+     *
+     * @param deviceId          device identifier
+     * @param fromTable         source table
+     * @param toTable           destination table
+     * @param statsFlowRule     stats flow rule
+     * @param rulePriority      rule priority
+     * @param install           installation flag
+     */
     private void connectTables(DeviceId deviceId, int fromTable, int toTable,
                                StatsFlowRule statsFlowRule, int rulePriority,
                                boolean install) {
@@ -478,7 +709,7 @@
                 .withSelector(selectorBuilder.build())
                 .withTreatment(treatmentBuilder.build())
                 .withPriority(prefixLength)
-                .fromApp(appId)
+                .fromApp(telemetryAppId)
                 .makePermanent()
                 .forTable(fromTable)
                 .build();
@@ -635,7 +866,7 @@
     }
 
     /**
-     * Get Device ID which the VM is located.
+     * Gets Device ID which the VM is located.
      *
      * @param ipAddress IP Address of host
      * @return Device ID
@@ -651,7 +882,7 @@
     }
 
     /**
-     * Get VLAN ID with respect to IP Address.
+     * Gets VLAN ID with respect to IP Address.
      *
      * @param ipAddress IP Address of host
      * @return VLAN ID
@@ -665,7 +896,7 @@
     }
 
     /**
-     * Get Interface ID of Switch which is connected to a host.
+     * Gets Interface ID of Switch which is connected to a host.
      *
      * @param ipAddress IP Address of host
      * @return Interface ID of Switch
@@ -679,7 +910,7 @@
     }
 
     /**
-     * Get MAC Address of host.
+     * Gets MAC Address of host.
      *
      * @param ipAddress IP Address of host
      * @return MAC Address of host
@@ -693,6 +924,26 @@
         return NO_HOST_MAC;
     }
 
+    /**
+     * Gets IP address of the host which is attached to the given device and port.
+     *
+     * @param device    device
+     * @param inPort    IN port number
+     * @return IP address
+     */
+    private IpAddress getIpAddress(Device device, PortCriterion inPort) {
+
+        Host host = hostService.getConnectedHosts(device.id()).stream()
+                .filter(h -> h.location().port().equals(inPort.port()))
+                .findAny().orElse(null);
+
+        if (host != null) {
+            return host.ipAddresses().stream().findAny().get();
+        }
+
+        return NO_HOST_IP;
+    }
+
     private void enqFlowInfo(FlowInfo flowInfo) {
         String key = flowInfo.uniqueFlowInfoKey();
         Queue<FlowInfo> queue = flowInfoMap.get(key);
@@ -709,8 +960,15 @@
         }
     }
 
-    public Map<String, Queue<FlowInfo>> getFlowInfoMap() {
-        return flowInfoMap;
+    /**
+     * Checks whether the given device is edge switch or not.
+     *
+     * @param id device identifier
+     * @return true if the given device is edge switch, false otherwise
+     */
+    private boolean isEdgeSwitch(DeviceId id) {
+
+        return !hostService.getConnectedHosts(id).isEmpty();
     }
 
     /**
@@ -751,37 +1009,82 @@
             portStats = portStatsConfigured;
             log.info("Configured. Port stats flag is {}", portStats);
         }
+
+        Boolean monitorOverlayConfigured = getBooleanProperty(properties, MONITOR_OVERLAY);
+        if (monitorOverlayConfigured == null) {
+            monitorOverlay = DEFAULT_MONITOR_OVERLAY;
+            log.info("Monitor overlay flag is NOT " +
+                    "configured, default value is {}", monitorOverlay);
+        } else {
+            monitorOverlay = monitorOverlayConfigured;
+            log.info("Configured. Monitor overlay flag is {}", monitorOverlay);
+        }
+
+        Boolean monitorUnderlayConfigured = getBooleanProperty(properties, MONITOR_UNDERLAY);
+        if (monitorUnderlayConfigured == null) {
+            monitorUnderlay = DEFAULT_MONITOR_UNDERLAY;
+            log.info("Monitor underlay flag is NOT " +
+                    "configured, default value is {}", monitorUnderlay);
+        } else {
+            monitorUnderlay = monitorUnderlayConfigured;
+            log.info("Configured. Monitor underlay flag is {}", monitorUnderlay);
+        }
     }
 
     private class TelemetryCollector implements Runnable {
         @Override
         public void run() {
-            Set<FlowInfo> filteredFlowInfos = Sets.newConcurrentHashSet();
+            Set<FlowInfo> filteredOverlayFlowInfos = Sets.newConcurrentHashSet();
+            Set<FlowInfo> filteredUnderlayFlowInfos = Sets.newConcurrentHashSet();
 
             // we only let the master controller of the device where the
             // stats flow rules are installed send stats message
-            getFlowInfos().forEach(f -> {
-                if (checkSrcDstLocalMaster(f)) {
-                    filteredFlowInfos.add(f);
-                }
-            });
-
-            // we only let the master controller of the device where the port
-            // is located to send stats message
-            if (portStats) {
-                getDstPortBasedFlowInfos().forEach(f -> {
+            if (monitorOverlay) {
+                getOverlayFlowInfos().forEach(f -> {
                     if (checkSrcDstLocalMaster(f)) {
-                        filteredFlowInfos.add(f);
+                        filteredOverlayFlowInfos.add(f);
+                    }
+                });
+            }
+            if (monitorUnderlay) {
+                getUnderlayFlowInfos().forEach(f -> {
+                    if (checkSrcDstLocalMaster(f)) {
+                        filteredUnderlayFlowInfos.add(f);
                     }
                 });
             }
 
-            telemetryService.publish(filteredFlowInfos);
+            // we only let the master controller of the device where the port
+            // is located to send stats message
+            if (portStats) {
+                if (monitorOverlay) {
+                    getOverlayDstPortBasedFlowInfos().forEach(f -> {
+                        if (checkSrcDstLocalMaster(f)) {
+                            filteredOverlayFlowInfos.add(f);
+                        }
+                    });
+                }
 
-            // TODO: Refactor the following code to "TelemetryService" style.
-            filteredFlowInfos.forEach(flowInfo -> {
-                enqFlowInfo(flowInfo);
-            });
+                if (monitorUnderlay) {
+                    getUnderlayDstPortBasedFlowInfos().forEach(f -> {
+                        if (checkSrcDstLocalMaster(f)) {
+                            filteredUnderlayFlowInfos.add(f);
+                        }
+                    });
+                }
+            }
+
+
+            if (monitorOverlay) {
+                telemetryService.publish(filteredOverlayFlowInfos);
+
+                // TODO: Refactor the following code to "TelemetryService" style.
+                filteredOverlayFlowInfos.forEach(StatsFlowRuleManager.this::enqFlowInfo);
+            }
+
+            if (monitorUnderlay) {
+                telemetryService.publish(filteredUnderlayFlowInfos);
+            }
         }
 
         private boolean checkSrcDstLocalMaster(FlowInfo info) {
diff --git a/apps/openstacktelemetry/app/src/main/java/org/onosproject/openstacktelemetry/web/OpenstackTelemetryWebResource.java b/apps/openstacktelemetry/app/src/main/java/org/onosproject/openstacktelemetry/web/OpenstackTelemetryWebResource.java
index 419b789..34f8366 100644
--- a/apps/openstacktelemetry/app/src/main/java/org/onosproject/openstacktelemetry/web/OpenstackTelemetryWebResource.java
+++ b/apps/openstacktelemetry/app/src/main/java/org/onosproject/openstacktelemetry/web/OpenstackTelemetryWebResource.java
@@ -136,7 +136,7 @@
         log.info("GET BULK FLOW RULE");
 
         Set<FlowInfo> flowInfoSet;
-        flowInfoSet = statsFlowRuleService.getFlowInfos();
+        flowInfoSet = statsFlowRuleService.getOverlayFlowInfos();
 
         log.info("\n\n======================================================\n" +
                  "FlowInfo Set: \n{}" +