Merge remote-tracking branch 'origin/master' into dev-karaf-4.2.1

Change-Id: If611105632e508cb17352c6f5b1cbcfd85f716d1
diff --git a/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/api/Constants.java b/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/api/Constants.java
index 123cacc..ead7234 100644
--- a/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/api/Constants.java
+++ b/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/api/Constants.java
@@ -84,6 +84,7 @@
     public static final int PRIORITY_ARP_CONTROL_RULE = 40000;
     public static final int PRIORITY_ARP_REPLY_RULE = 40000;
     public static final int PRIORITY_ARP_REQUEST_RULE = 40000;
+    public static final int PRIORITY_ARP_FLOOD_RULE = 39000;
     public static final int PRIORITY_FORCED_ACL_RULE = 50000;
     public static final int PRIORITY_ICMP_PROBE_RULE = 50000;
 
@@ -94,9 +95,10 @@
     public static final int STAT_FLAT_OUTBOUND_TABLE = 10;
     public static final int VTAP_FLAT_OUTBOUND_TABLE = 11;
     public static final int VTAP_FLAT_OUTBOUND_MIRROR_TABLE = 12;
-    public static final int DHCP_ARP_TABLE = 5;
+    public static final int DHCP_TABLE = 5;
     public static final int FLAT_TABLE = 20;
     public static final int VTAG_TABLE = 30;
+    public static final int ARP_TABLE = 35;
     public static final int ACL_TABLE = 40;
     public static final int CT_TABLE = 41;
     public static final int JUMP_TABLE = 50;
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackAddAclCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackAddAclCommand.java
index 845c258..0a527e0 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackAddAclCommand.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackAddAclCommand.java
@@ -35,7 +35,7 @@
 
 import java.util.Optional;
 
-import static org.onosproject.openstacknetworking.api.Constants.DHCP_ARP_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.DHCP_TABLE;
 import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
 import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_FORCED_ACL_RULE;
 
@@ -121,7 +121,7 @@
                 sBuilder.build(),
                 treatment,
                 PRIORITY_FORCED_ACL_RULE,
-                DHCP_ARP_TABLE,
+                DHCP_TABLE,
                 true);
     }
 }
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackRemoveAclCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackRemoveAclCommand.java
index 37a6b9c..70a2347 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackRemoveAclCommand.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackRemoveAclCommand.java
@@ -35,7 +35,7 @@
 
 import java.util.Optional;
 
-import static org.onosproject.openstacknetworking.api.Constants.DHCP_ARP_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.DHCP_TABLE;
 import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
 import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_FORCED_ACL_RULE;
 
@@ -118,7 +118,7 @@
                 sBuilder.build(),
                 treatment,
                 PRIORITY_FORCED_ACL_RULE,
-                DHCP_ARP_TABLE,
+                DHCP_TABLE,
                 false);
     }
 }
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackFlowRuleManager.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackFlowRuleManager.java
index 2ea7400..0dc4db7 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackFlowRuleManager.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackFlowRuleManager.java
@@ -198,11 +198,18 @@
     protected void initializePipeline(DeviceId deviceId) {
         // for inbound table transition
         connectTables(deviceId, Constants.STAT_INBOUND_TABLE, Constants.VTAP_INBOUND_TABLE);
-        connectTables(deviceId, Constants.VTAP_INBOUND_TABLE, Constants.DHCP_ARP_TABLE);
+        connectTables(deviceId, Constants.VTAP_INBOUND_TABLE, Constants.DHCP_TABLE);
 
-        // for vTag and ACL table transition
-        connectTables(deviceId, Constants.DHCP_ARP_TABLE, Constants.VTAG_TABLE);
-        connectTables(deviceId, Constants.VTAG_TABLE, Constants.ACL_TABLE);
+        // for DHCP and vTag table transition
+        connectTables(deviceId, Constants.DHCP_TABLE, Constants.VTAG_TABLE);
+
+        // for vTag and ARP table transition
+        connectTables(deviceId, Constants.VTAG_TABLE, Constants.ARP_TABLE);
+
+        // for ARP and ACL table transition
+        connectTables(deviceId, Constants.ARP_TABLE, Constants.ACL_TABLE);
+
+        // for ACL and JUMP table transition
         connectTables(deviceId, Constants.ACL_TABLE, Constants.JUMP_TABLE);
 
         // for JUMP table transition
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackMetadataProxyHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackMetadataProxyHandler.java
index 995bb21..dd476de 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackMetadataProxyHandler.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackMetadataProxyHandler.java
@@ -77,7 +77,7 @@
 import java.util.Objects;
 import java.util.Set;
 
-import static org.onosproject.openstacknetworking.api.Constants.DHCP_ARP_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.DHCP_TABLE;
 import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_DHCP_RULE;
 import static org.onosproject.openstacknetworking.impl.OpenstackMetadataProxyHandler.Http.Type.RESPONSE;
 import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.hmacEncrypt;
@@ -572,7 +572,7 @@
                     selector,
                     treatment,
                     PRIORITY_DHCP_RULE,
-                    DHCP_ARP_TABLE,
+                    DHCP_TABLE,
                     install);
         }
     }
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingFloatingIpHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingFloatingIpHandler.java
index f1f1370..b2cb36e 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingFloatingIpHandler.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingFloatingIpHandler.java
@@ -788,9 +788,9 @@
                     break;
                 case OPENSTACK_INSTANCE_MIGRATION_ENDED:
 
-                    InstancePort revisedInstPort = swapStaleLocation(event.subject());
+                    InstancePort oldInstPort = swapStaleLocation(event.subject());
 
-                    fip = associatedFloatingIp(revisedInstPort, ips);
+                    fip = associatedFloatingIp(oldInstPort, ips);
 
                     if (fip == null) {
                         return;
@@ -805,7 +805,13 @@
                         throw new IllegalStateException(errorFormat);
                     }
 
-                    // If we only have one gateway, we simply do not remove any
+                    eventExecutor.execute(() -> {
+                        // We need to remove the old ComputeNodeToGateway rules from
+                        // original compute node
+                        setComputeNodeToGatewayHelper(oldInstPort, osNet, gateways, false);
+                    });
+
+                        // If we only have one gateway, we simply do not remove any
                     // flow rules from either gateway or compute node
                     if (gateways.size() == 1) {
                         return;
@@ -817,7 +823,7 @@
                     // it has been overwritten at port detention event
                     // if it is false, we will remove the rules
                     DeviceId newDeviceId = event.subject().deviceId();
-                    DeviceId oldDeviceId = revisedInstPort.deviceId();
+                    DeviceId oldDeviceId = oldInstPort.deviceId();
 
                     OpenstackNode oldGateway = getGwByComputeDevId(gateways, oldDeviceId);
                     OpenstackNode newGateway = getGwByComputeDevId(gateways, newDeviceId);
@@ -827,15 +833,10 @@
                     }
 
                     eventExecutor.execute(() -> {
-
-                        // We need to remove the old ComputeNodeToGateway rules from
-                        // original compute node
-                        setComputeNodeToGatewayHelper(revisedInstPort, osNet, gateways, false);
-
                         // Since DownstreamExternal rules should only be placed in
                         // corresponding gateway node, we need to remove old rule from
                         // the corresponding gateway node
-                        setDownstreamExternalRulesHelper(fip, osNet, revisedInstPort,
+                        setDownstreamExternalRulesHelper(fip, osNet, oldInstPort,
                                 externalPeerRouter, gateways, false);
                     });
                     break;
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingArpHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingArpHandler.java
index c1ea3cd..c78134d 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingArpHandler.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingArpHandler.java
@@ -74,15 +74,15 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.onosproject.openstacknetworking.api.Constants.ARP_BROADCAST_MODE;
 import static org.onosproject.openstacknetworking.api.Constants.ARP_PROXY_MODE;
+import static org.onosproject.openstacknetworking.api.Constants.ARP_TABLE;
 import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_ARP_MODE_STR;
 import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC_STR;
-import static org.onosproject.openstacknetworking.api.Constants.DHCP_ARP_TABLE;
 import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
 import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_ARP_CONTROL_RULE;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_ARP_FLOOD_RULE;
 import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_ARP_GATEWAY_RULE;
 import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_ARP_REPLY_RULE;
 import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_ARP_REQUEST_RULE;
-import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_ARP_SUBNET_RULE;
 import static org.onosproject.openstacknetworking.api.InstancePort.State.ACTIVE;
 import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.getPropertyValue;
 import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.swapStaleLocation;
@@ -323,7 +323,7 @@
                                 selector,
                                 treatment,
                                 PRIORITY_ARP_GATEWAY_RULE,
-                                DHCP_ARP_TABLE,
+                                ARP_TABLE,
                                 install
                         )
                 );
@@ -334,7 +334,7 @@
                         selector,
                         treatment,
                         PRIORITY_ARP_GATEWAY_RULE,
-                        DHCP_ARP_TABLE,
+                        ARP_TABLE,
                         install
                 );
             }
@@ -395,10 +395,13 @@
 
         OpenstackNode localNode = osNodeService.node(port.deviceId());
 
+        String segId = osNetworkService.segmentId(port.networkId());
+
         TrafficSelector selector = DefaultTrafficSelector.builder()
                 .matchEthType(EthType.EtherType.ARP.ethType().toShort())
                 .matchArpOp(ARP.OP_REQUEST)
                 .matchArpTpa(port.ipAddress().getIp4Address())
+                .matchTunnelId(Long.valueOf(segId))
                 .build();
 
         setRemoteArpTreatmentForVxlan(selector, port, localNode, install);
@@ -449,7 +452,7 @@
                 selector,
                 treatment,
                 PRIORITY_ARP_REPLY_RULE,
-                DHCP_ARP_TABLE,
+                ARP_TABLE,
                 install
         );
 
@@ -478,7 +481,7 @@
                         selector,
                         treatmentToRemote,
                         PRIORITY_ARP_REQUEST_RULE,
-                        DHCP_ARP_TABLE,
+                        ARP_TABLE,
                         install
                 );
             }
@@ -501,7 +504,7 @@
                         selector,
                         treatmentToRemote,
                         PRIORITY_ARP_REQUEST_RULE,
-                        DHCP_ARP_TABLE,
+                        ARP_TABLE,
                         install);
             }
         }
@@ -674,7 +677,7 @@
                     selector,
                     treatment,
                     PRIORITY_ARP_CONTROL_RULE,
-                    DHCP_ARP_TABLE,
+                    ARP_TABLE,
                     install
             );
         }
@@ -694,8 +697,8 @@
                     osNode.intgBridge(),
                     selector,
                     treatment,
-                    PRIORITY_ARP_SUBNET_RULE,
-                    DHCP_ARP_TABLE,
+                    PRIORITY_ARP_FLOOD_RULE,
+                    ARP_TABLE,
                     install
             );
         }
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingDhcpHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingDhcpHandler.java
index efd10d9..ca322ea 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingDhcpHandler.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingDhcpHandler.java
@@ -85,7 +85,7 @@
 import static org.onlab.packet.DHCP.MsgType.DHCPACK;
 import static org.onlab.packet.DHCP.MsgType.DHCPOFFER;
 import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC_STR;
-import static org.onosproject.openstacknetworking.api.Constants.DHCP_ARP_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.DHCP_TABLE;
 import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_DHCP_RULE;
 import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.COMPUTE;
 import static org.slf4j.LoggerFactory.getLogger;
@@ -577,7 +577,7 @@
                     selector,
                     treatment,
                     PRIORITY_DHCP_RULE,
-                    DHCP_ARP_TABLE,
+                    DHCP_TABLE,
                     install);
         }
     }
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingHandler.java
index 3ac981a..b348d49 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingHandler.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingHandler.java
@@ -24,6 +24,8 @@
 import org.osgi.service.component.annotations.ReferenceCardinality;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.VlanId;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.cfg.ConfigProperty;
 import org.onosproject.cluster.ClusterService;
 import org.onosproject.cluster.LeadershipService;
 import org.onosproject.cluster.NodeId;
@@ -56,12 +58,15 @@
 import org.slf4j.Logger;
 
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.ExecutorService;
 
 import static java.util.concurrent.Executors.newSingleThreadExecutor;
 import static org.onlab.util.Tools.groupedThreads;
 import static org.onosproject.openstacknetworking.api.Constants.ACL_TABLE;
-import static org.onosproject.openstacknetworking.api.Constants.DHCP_ARP_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.ARP_BROADCAST_MODE;
+import static org.onosproject.openstacknetworking.api.Constants.ARP_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.DHCP_TABLE;
 import static org.onosproject.openstacknetworking.api.Constants.FLAT_TABLE;
 import static org.onosproject.openstacknetworking.api.Constants.FORWARDING_TABLE;
 import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
@@ -74,6 +79,7 @@
 import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_TUNNEL_TAG_RULE;
 import static org.onosproject.openstacknetworking.api.Constants.STAT_FLAT_OUTBOUND_TABLE;
 import static org.onosproject.openstacknetworking.api.Constants.VTAG_TABLE;
+import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.getPropertyValue;
 import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.swapStaleLocation;
 import static org.onosproject.openstacknetworking.util.RulePopulatorUtil.buildExtension;
 import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.COMPUTE;
@@ -89,6 +95,7 @@
 
     private final Logger log = getLogger(getClass());
 
+    private static final String ARP_MODE = "arpMode";
     private static final String ERR_SET_FLOWS_VNI = "Failed to set flows for %s: Failed to get VNI for %s";
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
@@ -107,6 +114,10 @@
     protected ClusterService clusterService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected ComponentConfigService configService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+
     protected LeadershipService leadershipService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
@@ -162,12 +173,22 @@
         NetworkType type = osNetworkService.network(instPort.networkId()).getNetworkType();
         switch (type) {
             case VXLAN:
-                setTunnelTagFlowRules(instPort, install);
+                setTunnelTagIpFlowRules(instPort, install);
                 setForwardingRulesForVxlan(instPort, install);
+
+                if (ARP_BROADCAST_MODE.equals(getArpMode())) {
+                    setTunnelTagArpFlowRules(instPort, install);
+                }
+
                 break;
             case VLAN:
-                setVlanTagFlowRules(instPort, install);
+                setVlanTagIpFlowRules(instPort, install);
                 setForwardingRulesForVlan(instPort, install);
+
+                if (ARP_BROADCAST_MODE.equals(getArpMode())) {
+                    setVlanTagArpFlowRules(instPort, install);
+                }
+
                 break;
             case FLAT:
                 setFlatJumpRules(instPort, install);
@@ -190,10 +211,20 @@
 
         switch (type) {
             case VXLAN:
-                setTunnelTagFlowRules(instPort, false);
+                setTunnelTagIpFlowRules(instPort, false);
+
+                if (ARP_BROADCAST_MODE.equals(getArpMode())) {
+                    setTunnelTagArpFlowRules(instPort, false);
+                }
+
                 break;
             case VLAN:
-                setVlanTagFlowRules(instPort, false);
+                setVlanTagIpFlowRules(instPort, false);
+
+                if (ARP_BROADCAST_MODE.equals(getArpMode())) {
+                    setVlanTagArpFlowRules(instPort, false);
+                }
+
                 break;
             case FLAT:
                 setFlatJumpRules(instPort, false);
@@ -219,7 +250,7 @@
                 selector.build(),
                 treatment.build(),
                 PRIORITY_FLAT_JUMP_UPSTREAM_RULE,
-                DHCP_ARP_TABLE,
+                DHCP_TABLE,
                 install);
 
         Network network = osNetworkService.network(port.networkId());
@@ -247,7 +278,7 @@
                 selector.build(),
                 treatment.build(),
                 PRIORITY_FLAT_JUMP_DOWNSTREAM_RULE,
-                DHCP_ARP_TABLE,
+                DHCP_TABLE,
                 install);
 
         selector = DefaultTrafficSelector.builder();
@@ -261,7 +292,7 @@
                 selector.build(),
                 treatment.build(),
                 PRIORITY_FLAT_JUMP_DOWNSTREAM_RULE,
-                DHCP_ARP_TABLE,
+                DHCP_TABLE,
                 install);
     }
 
@@ -446,18 +477,27 @@
                 });
     }
 
+    private void setTunnelTagArpFlowRules(InstancePort instPort, boolean install) {
+        setTunnelTagFlowRules(instPort, Ethernet.TYPE_ARP, install);
+    }
+
+    private void setTunnelTagIpFlowRules(InstancePort instPort, boolean install) {
+        setTunnelTagFlowRules(instPort, Ethernet.TYPE_IPV4, install);
+    }
+
     /**
      * Configures the flow rule which is for using VXLAN to tag the packet
      * based on the in_port number of a virtual instance.
-     * Note that this rule will be inserted in VNI table (table 0).
+     * Note that this rule will be inserted in vTag table.
      *
      * @param instPort instance port object
      * @param install install flag, add the rule if true, remove it otherwise
      */
-    private void setTunnelTagFlowRules(InstancePort instPort, boolean install) {
+    private void setTunnelTagFlowRules(InstancePort instPort,
+                                       short ethType,
+                                       boolean install) {
         TrafficSelector selector = DefaultTrafficSelector.builder()
-                // TODO: need to handle IPv6 in near future
-                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchEthType(ethType)
                 .matchInPort(instPort.portNumber())
                 .build();
 
@@ -469,9 +509,9 @@
 
         TrafficTreatment.Builder tb = DefaultTrafficTreatment.builder()
                 .setTunnelId(getVni(instPort))
-                .transition(ACL_TABLE);
+                .transition(ARP_TABLE);
 
-        if (securityGroupService.isSecurityGroupEnabled()) {
+        if (securityGroupService.isSecurityGroupEnabled() && ethType == Ethernet.TYPE_IPV4) {
             tb.extension(ctTreatment, instPort.deviceId());
         }
 
@@ -485,18 +525,27 @@
                 install);
     }
 
+    private void setVlanTagIpFlowRules(InstancePort instPort, boolean install) {
+        setVlanTagFlowRules(instPort, Ethernet.TYPE_IPV4, install);
+    }
+
+    private void setVlanTagArpFlowRules(InstancePort instPort, boolean install) {
+        setVlanTagFlowRules(instPort, Ethernet.TYPE_ARP, install);
+    }
+
     /**
      * Configures the flow rule which is for using VLAN to tag the packet
      * based on the in_port number of a virtual instance.
-     * Note that this rule will be inserted in VNI table (table 0).
+     * Note that this rule will be inserted in vTag table.
      *
      * @param instPort instance port object
      * @param install install flag, add the rule if true, remove it otherwise
      */
-    private void setVlanTagFlowRules(InstancePort instPort, boolean install) {
+    private void setVlanTagFlowRules(InstancePort instPort,
+                                     short ethType,
+                                     boolean install) {
         TrafficSelector selector = DefaultTrafficSelector.builder()
-                // TODO: need to handle IPv6 in near future
-                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchEthType(ethType)
                 .matchInPort(instPort.portNumber())
                 .build();
 
@@ -637,6 +686,11 @@
         return Long.valueOf(osNet.getProviderSegID());
     }
 
+    private String getArpMode() {
+        Set<ConfigProperty> properties = configService.getProperties(OpenstackSwitchingArpHandler.class.getName());
+        return getPropertyValue(properties, ARP_MODE);
+    }
+
     /**
      * An internal instance port listener which listens the port events generated
      * from VM. The corresponding L2 forwarding rules will be generated and
diff --git a/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackFlowRuleManagerTest.java b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackFlowRuleManagerTest.java
index e3e449b..d1410b2 100644
--- a/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackFlowRuleManagerTest.java
+++ b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackFlowRuleManagerTest.java
@@ -45,7 +45,8 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.onosproject.openstacknetworking.api.Constants.ACL_TABLE;
-import static org.onosproject.openstacknetworking.api.Constants.DHCP_ARP_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.ARP_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.DHCP_TABLE;
 import static org.onosproject.openstacknetworking.api.Constants.FLAT_TABLE;
 import static org.onosproject.openstacknetworking.api.Constants.FORWARDING_TABLE;
 import static org.onosproject.openstacknetworking.api.Constants.JUMP_TABLE;
@@ -187,13 +188,14 @@
         fros = Sets.newConcurrentHashSet();
 
         target.initializePipeline(DEVICE_ID);
-        assertEquals("Flow Rule size was not match", 11, fros.size());
+        assertEquals("Flow Rule size was not match", 12, fros.size());
 
         Map<Integer, Integer> fromToTableMap = Maps.newConcurrentMap();
         fromToTableMap.put(STAT_INBOUND_TABLE, VTAP_INBOUND_TABLE);
-        fromToTableMap.put(VTAP_INBOUND_TABLE, DHCP_ARP_TABLE);
-        fromToTableMap.put(DHCP_ARP_TABLE, VTAG_TABLE);
-        fromToTableMap.put(VTAG_TABLE, ACL_TABLE);
+        fromToTableMap.put(VTAP_INBOUND_TABLE, DHCP_TABLE);
+        fromToTableMap.put(DHCP_TABLE, VTAG_TABLE);
+        fromToTableMap.put(VTAG_TABLE, ARP_TABLE);
+        fromToTableMap.put(ARP_TABLE, ACL_TABLE);
         fromToTableMap.put(ACL_TABLE, JUMP_TABLE);
         fromToTableMap.put(STAT_OUTBOUND_TABLE, VTAP_OUTBOUND_TABLE);
         fromToTableMap.put(VTAP_OUTBOUND_TABLE, FORWARDING_TABLE);
diff --git a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeListCommand.java b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeListCommand.java
index 92a1020..a4400f7 100644
--- a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeListCommand.java
+++ b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeListCommand.java
@@ -28,6 +28,7 @@
 import java.util.Comparator;
 import java.util.List;
 
+import static org.onosproject.openstacknode.util.OpenstackNodeUtil.getGwByComputeNode;
 import static org.onosproject.openstacknode.util.OpenstackNodeUtil.prettyJson;
 
 /**
@@ -38,7 +39,7 @@
         description = "Lists all nodes registered in OpenStack node service")
 public class OpenstackNodeListCommand extends AbstractShellCommand {
 
-    private static final String FORMAT = "%-20s%-15s%-24s%-24s%-20s%-20s%-15s%s";
+    private static final String FORMAT = "%-20s%-15s%-24s%-24s%-20s%-20s%-15s%-15s%-15s";
 
     @Override
     protected void doExecute() {
@@ -50,7 +51,7 @@
             print("%s", json(osNodes));
         } else {
             print(FORMAT, "Hostname", "Type", "Integration Bridge",
-                    "Management IP", "Data IP", "VLAN Intf", "Uplink Port", "State");
+                    "Management IP", "Data IP", "VLAN Intf", "Uplink Port", "State", "SelectedGw");
             for (OpenstackNode osNode : osNodes) {
                 print(FORMAT,
                         osNode.hostname(),
@@ -60,7 +61,8 @@
                         osNode.dataIp() != null ? osNode.dataIp() : "",
                         osNode.vlanIntf() != null ? osNode.vlanIntf() : "",
                         osNode.uplinkPort() != null ? osNode.uplinkPort() : "",
-                        osNode.state());
+                        osNode.state(),
+                        getGwByComputeNode(osNodeService.completeNodes(OpenstackNode.NodeType.GATEWAY), osNode));
             }
             print("Total %s nodes", osNodeService.nodes().size());
         }
diff --git a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DefaultOpenstackNodeHandler.java b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DefaultOpenstackNodeHandler.java
index 9a242bb..f3645d7 100644
--- a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DefaultOpenstackNodeHandler.java
+++ b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/impl/DefaultOpenstackNodeHandler.java
@@ -546,6 +546,12 @@
     }
 
     private void processOpenstackNodeRemoved(OpenstackNode osNode) {
+        OvsdbClientService client = getOvsdbClient(osNode, ovsdbPort, ovsdbController);
+        if (client == null) {
+            log.info("Failed to get ovsdb client");
+            return;
+        }
+
         //delete physical interfaces from the node
         removePhysicalInterface(osNode);
 
@@ -560,6 +566,17 @@
                 }
             });
         }
+
+        //delete tunnel bridge from the node
+        if (hasDpdkTunnelBridge(osNode)) {
+            client.dropBridge(TUNNEL_BRIDGE);
+        }
+
+        //delete integration bridge from the node
+        client.dropBridge(INTEGRATION_BRIDGE);
+
+        //disconnect ovsdb
+        client.disconnect();
     }
 
     /**
diff --git a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/util/OpenstackNodeUtil.java b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/util/OpenstackNodeUtil.java
index f19b8d5..b520b17 100644
--- a/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/util/OpenstackNodeUtil.java
+++ b/apps/openstacknode/app/src/main/java/org/onosproject/openstacknode/util/OpenstackNodeUtil.java
@@ -48,7 +48,11 @@
 import java.io.IOException;
 import java.security.cert.X509Certificate;
 import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
 
 import static org.onlab.util.Tools.get;
 
@@ -69,6 +73,7 @@
     private static final String ZERO = "0";
 
     private static final String DPDK_DEVARGS = "dpdk-devargs";
+    private static final String NOT_AVAILABLE = "N/A";
 
     /**
      * Prevents object installation from external.
@@ -290,6 +295,56 @@
     }
 
     /**
+     * Obtains the gateway node by openstack node. Note that the gateway
+     * node is determined by device's device identifier.
+     *
+     * @param gws                a collection of gateway nodes
+     * @param openstackNode      device identifier
+     * @return the hostname of selected gateway node
+     */
+    public static String getGwByComputeNode(Set<OpenstackNode> gws, OpenstackNode openstackNode) {
+        int numOfGw = gws.size();
+
+        if (numOfGw == 0) {
+            return NOT_AVAILABLE;
+        }
+
+        if (!openstackNode.type().equals(OpenstackNode.NodeType.COMPUTE)) {
+            return NOT_AVAILABLE;
+        }
+
+        int gwIndex = Math.abs(openstackNode.intgBridge().hashCode()) % numOfGw;
+
+        return getGwByIndex(gws, gwIndex).hostname();
+    }
+
+    /**
+     * Obtains gateway instance by giving index number.
+     *
+     * @param gws       a collection of gateway nodes
+     * @param index     index number
+     * @return gateway instance
+     */
+    private static OpenstackNode getGwByIndex(Set<OpenstackNode> gws, int index) {
+        Map<String, OpenstackNode> hashMap = new HashMap<>();
+        gws.forEach(gw -> hashMap.put(gw.hostname(), gw));
+        TreeMap<String, OpenstackNode> treeMap = new TreeMap<>(hashMap);
+        Iterator<String> iteratorKey = treeMap.keySet().iterator();
+
+        int intIndex = 0;
+        OpenstackNode gw = null;
+        while (iteratorKey.hasNext()) {
+            String key = iteratorKey.next();
+
+            if (intIndex == index) {
+                gw = treeMap.get(key);
+            }
+            intIndex++;
+        }
+        return gw;
+    }
+
+    /**
      * Builds up and a complete endpoint URL from gateway node.
      *
      * @param node gateway node
diff --git a/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/impl/OpenstackVtapManager.java b/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/impl/OpenstackVtapManager.java
index d9a2ae3..a2c1a0a 100644
--- a/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/impl/OpenstackVtapManager.java
+++ b/apps/openstackvtap/app/src/main/java/org/onosproject/openstackvtap/impl/OpenstackVtapManager.java
@@ -108,7 +108,7 @@
 import static org.onosproject.net.AnnotationKeys.PORT_NAME;
 import static org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_RESUBMIT_TABLE;
 import static org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_TUNNEL_DST;
-import static org.onosproject.openstacknetworking.api.Constants.DHCP_ARP_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.DHCP_TABLE;
 import static org.onosproject.openstacknetworking.api.Constants.FLAT_TABLE;
 import static org.onosproject.openstacknetworking.api.Constants.FORWARDING_TABLE;
 import static org.onosproject.openstacknetworking.api.Constants.VTAP_FLAT_OUTBOUND_GROUP_TABLE;
@@ -185,7 +185,7 @@
     private static final int PRIORITY_VTAP_OUTPUT_RULE = 1000;
     private static final int PRIORITY_VTAP_OUTPUT_DROP = 0;
 
-    private static final int INBOUND_NEXT_TABLE = DHCP_ARP_TABLE;
+    private static final int INBOUND_NEXT_TABLE = DHCP_TABLE;
     private static final int FLAT_OUTBOUND_NEXT_TABLE = FLAT_TABLE;
     private static final int OUTBOUND_NEXT_TABLE = FORWARDING_TABLE;
 
diff --git a/apps/openstackvtap/app/src/main/resources/app/view/openstackvtap/openstackvtap.js b/apps/openstackvtap/app/src/main/resources/app/view/openstackvtap/openstackvtap.js
index 9b20eb0..d38c849 100644
--- a/apps/openstackvtap/app/src/main/resources/app/view/openstackvtap/openstackvtap.js
+++ b/apps/openstackvtap/app/src/main/resources/app/view/openstackvtap/openstackvtap.js
@@ -219,68 +219,6 @@
         return div;
     }
 
-    function addInput(tbody, type, id, label, value) {
-        var tr = tbody.append('tr'),
-            lab;
-        if (typeof label === 'string') {
-            lab = label.replace(/_/g, ' ');
-        } else {
-            lab = label;
-        }
-
-        tr.append('td').attr('class', 'label').text(lab + ' :');
-
-        if (type == 'radio') {
-            var td = tr.append('td');
-            for(var index in value) {
-                if(index == 0) {
-                    td.append('input').classed( type + '-input', true)
-                                          .attr('type', type)
-                                          .attr('value', value[index])
-                                          .attr('name', label)
-                                          .attr('id', id)
-                                          .attr('checked', 'true');
-                } else {
-                    td.append('input').classed( type + '-input', true)
-                                          .attr('type', type)
-                                          .attr('name', label)
-                                          .attr('id', id)
-                                          .attr('value', value[index]);
-                }
-                td.append('span').text(value[index]);
-            }
-        } else {
-            tr.append('td').append('input').classed(type + '-input', true).attr('type', type)
-                .attr('id', id).attr('value', value);
-        }
-    }
-
-    function addButton(tr, callback, value) {
-        tr.append('td').append('input').classed('button-input', true).attr('type', 'button')
-                        .attr('value', value).on('click', callback);
-    }
-
-    function makeButton(callback, text, keyName) {
-        var cb = fs.isF(callback),
-            key = fs.isS(keyName);
-
-        function invoke() {
-            cb && cb();
-        }
-
-        return createDiv('vtap-button')
-            .text(text)
-            .on('click', invoke);
-    }
-
-    function createDiv(cls) {
-        var div = d3.select(document.createElement('div'));
-        if (cls) {
-            div.classed(cls, true);
-        }
-        return div;
-    }
-
     function displayVtap() {
         $log.debug("sendEvent openstackVtapIsActivatedRequest: ", selectedItem);
         wss.sendEvent(osvIsActReq, selectedItem);
diff --git a/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PortStatisticsDiscoveryImpl.java b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PortStatisticsDiscoveryImpl.java
index 2afbce0..8043ae7 100644
--- a/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PortStatisticsDiscoveryImpl.java
+++ b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PortStatisticsDiscoveryImpl.java
@@ -26,7 +26,7 @@
 import org.onosproject.net.driver.AbstractHandlerBehaviour;
 import org.onosproject.net.pi.model.PiCounterId;
 import org.onosproject.net.pi.model.PiPipeconf;
-import org.onosproject.net.pi.runtime.PiCounterCellData;
+import org.onosproject.net.pi.runtime.PiCounterCell;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
 import org.onosproject.net.pi.service.PiPipeconfService;
 import org.onosproject.p4runtime.api.P4RuntimeClient;
@@ -95,7 +95,7 @@
         });
 
         // Query the device.
-        Collection<PiCounterCellData> counterEntryResponse;
+        Collection<PiCounterCell> counterEntryResponse;
         try {
             counterEntryResponse = client.readCounterCells(counterCellIds, pipeconf).get();
         } catch (InterruptedException | ExecutionException e) {
@@ -105,24 +105,24 @@
         }
 
         // Process response.
-        counterEntryResponse.forEach(counterData -> {
-            if (counterData.cellId().counterType() != INDIRECT) {
-                log.warn("Invalid counter data type {}, skipping", counterData.cellId().counterType());
+        counterEntryResponse.forEach(counterCell -> {
+            if (counterCell.cellId().counterType() != INDIRECT) {
+                log.warn("Invalid counter data type {}, skipping", counterCell.cellId().counterType());
                 return;
             }
-            if (!portStatBuilders.containsKey(counterData.cellId().index())) {
-                log.warn("Unrecognized counter index {}, skipping", counterData);
+            if (!portStatBuilders.containsKey(counterCell.cellId().index())) {
+                log.warn("Unrecognized counter index {}, skipping", counterCell);
                 return;
             }
-            DefaultPortStatistics.Builder statsBuilder = portStatBuilders.get(counterData.cellId().index());
-            if (counterData.cellId().counterId().equals(INGRESS_COUNTER_ID)) {
-                statsBuilder.setPacketsReceived(counterData.packets());
-                statsBuilder.setBytesReceived(counterData.bytes());
-            } else if (counterData.cellId().counterId().equals(EGRESS_COUNTER_ID)) {
-                statsBuilder.setPacketsSent(counterData.packets());
-                statsBuilder.setBytesSent(counterData.bytes());
+            DefaultPortStatistics.Builder statsBuilder = portStatBuilders.get(counterCell.cellId().index());
+            if (counterCell.cellId().counterId().equals(INGRESS_COUNTER_ID)) {
+                statsBuilder.setPacketsReceived(counterCell.data().packets());
+                statsBuilder.setBytesReceived(counterCell.data().bytes());
+            } else if (counterCell.cellId().counterId().equals(EGRESS_COUNTER_ID)) {
+                statsBuilder.setPacketsSent(counterCell.data().packets());
+                statsBuilder.setBytesSent(counterCell.data().bytes());
             } else {
-                log.warn("Unrecognized counter ID {}, skipping", counterData);
+                log.warn("Unrecognized counter ID {}, skipping", counterCell);
             }
         });
 
diff --git a/cli/src/main/java/org/onosproject/cli/net/PipeconfCommand.java b/cli/src/main/java/org/onosproject/cli/net/PipeconfCommand.java
new file mode 100644
index 0000000..e70d7eb
--- /dev/null
+++ b/cli/src/main/java/org/onosproject/cli/net/PipeconfCommand.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2018-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.onosproject.cli.net;
+
+
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.driver.Behaviour;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.service.PiPipeconfService;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+
+/**
+ * Query registered pipeconfs.
+ */
+@Command(scope = "onos", name = "pipeconfs",
+        description = "Query registered pipeconfs.")
+public class PipeconfCommand extends AbstractShellCommand {
+
+    protected PiPipeconfService piPipeconfService;
+
+    @Option(name = "-s", aliases = "--short",
+            description = "Print more succinct output for each pipeconf",
+            required = false, multiValued = false)
+    private boolean shortOutput = false;
+
+    @Override
+    protected void doExecute() {
+        piPipeconfService = get(PiPipeconfService.class);
+
+        for (PiPipeconf piPipeconf : piPipeconfService.getPipeconfs()) {
+            if (shortOutput) {
+                print("id=%s", piPipeconf.id().toString());
+            } else {
+                print("id=%s, behaviors=%s, extensions=%s", piPipeconf.id().toString(),
+                      getBehaviors(piPipeconf), getExtensions(piPipeconf));
+            }
+        }
+    }
+
+    /**
+     * Get all behaviour of a pipeconf and converts a list of behaviour name to string.
+     *
+     * @param piPipeconf    query PiPipeconf
+     *
+     * @return string of behaviour name list
+     */
+    private String getBehaviors(PiPipeconf piPipeconf) {
+        Collection<Class<? extends Behaviour>> behaviours = piPipeconf.behaviours();
+        ArrayList<String> result = new ArrayList<>();
+
+        for (Class<? extends Behaviour> behaviour:behaviours) {
+            result.add(behaviour.getSimpleName());
+        }
+
+        return result.toString();
+    }
+
+    /**
+     * Get all extension of a pipeconf and converts a list of extension
+     * name to string.
+     *
+     * @param piPipeconf    query PiPipeconf
+     *
+     * @return string of extension name list
+     */
+    private String getExtensions(PiPipeconf piPipeconf) {
+        ArrayList<String> result = new ArrayList<>();
+
+        for (PiPipeconf.ExtensionType extensionType : PiPipeconf.ExtensionType.values()
+             ) {
+            if (piPipeconf.extension(extensionType).isPresent()) {
+                result.add(extensionType.name());
+            }
+        }
+
+        return result.toString();
+    }
+}
diff --git a/cli/src/main/java/org/onosproject/cli/net/TableStatisticsCommand.java b/cli/src/main/java/org/onosproject/cli/net/TableStatisticsCommand.java
index 9d3ec69..1077b52 100644
--- a/cli/src/main/java/org/onosproject/cli/net/TableStatisticsCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/net/TableStatisticsCommand.java
@@ -57,13 +57,13 @@
     String uri = null;
 
     private static final String FORMAT =
-            "   table=%s, active=%s, lookedup=%s, matched=%s";
+            "   table=%s, active=%s, lookedup=%s, matched=%s, maxsize=%s";
+    private static final String NA = "N/A";
 
     @Override
     protected void doExecute() {
         FlowRuleService flowService = get(FlowRuleService.class);
         DeviceService deviceService = get(DeviceService.class);
-
         SortedMap<Device, List<TableStatisticsEntry>> deviceTableStats =
                 getSortedTableStats(deviceService, flowService);
 
@@ -117,8 +117,9 @@
         print("deviceId=%s, tableCount=%d", d.id(), empty ? 0 : tableStats.size());
         if (!empty) {
             for (TableStatisticsEntry t : tableStats) {
-                print(FORMAT, t.tableId(), t.activeFlowEntries(),
-                      t.packetsLookedup(), t.packetsMatched());
+                print(FORMAT, t.table(), t.activeFlowEntries(),
+                        t.hasPacketsLookedup() ? t.packetsLookedup() : NA, t.packetsMatched(),
+                        t.hasMaxSize() ? t.maxSize() : NA);
             }
         }
     }
@@ -131,7 +132,7 @@
      * @return sorted table statistics list
      */
     protected SortedMap<Device, List<TableStatisticsEntry>> getSortedTableStats(DeviceService deviceService,
-                                                          FlowRuleService flowService) {
+                                                                                FlowRuleService flowService) {
         SortedMap<Device, List<TableStatisticsEntry>> deviceTableStats = new TreeMap<>(Comparators.ELEMENT_COMPARATOR);
         List<TableStatisticsEntry> tableStatsList;
         Iterable<Device> devices = uri == null ? deviceService.getDevices() :
@@ -144,4 +145,4 @@
         return deviceTableStats;
     }
 
-}
+}
\ No newline at end of file
diff --git a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index 99a458d..e67a8fb 100644
--- a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -718,6 +718,10 @@
         </command>
         -->
 
+        <command>
+            <action class="org.onosproject.cli.net.PipeconfCommand"/>
+        </command>
+
         <!--virtual network commands -->
         <command>
             <action class="org.onosproject.cli.net.vnet.TenantListCommand"/>
diff --git a/core/api/src/main/java/org/onosproject/net/flow/DefaultTableStatisticsEntry.java b/core/api/src/main/java/org/onosproject/net/flow/DefaultTableStatisticsEntry.java
index 53423d3..bf34437 100644
--- a/core/api/src/main/java/org/onosproject/net/flow/DefaultTableStatisticsEntry.java
+++ b/core/api/src/main/java/org/onosproject/net/flow/DefaultTableStatisticsEntry.java
@@ -24,10 +24,36 @@
 public final class DefaultTableStatisticsEntry implements TableStatisticsEntry {
 
     private final DeviceId deviceId;
-    private final int tableId;
+    private final TableId tableId;
     private final long activeFlowEntries;
     private final long packetsLookedupCount;
     private final long packetsMatchedCount;
+    private final long maxSize;
+    private static final Long NOT_PRESENT = (long) -1;
+
+    /**
+     * Default table statistics constructor.
+     *
+     * @param deviceId device identifier
+     * @param tableId index table identifier
+     * @param activeFlowEntries number of active flow entries in the table
+     * @param packetsLookedupCount number of packets looked up in table
+     * @param packetsMatchedCount number of packets that hit table
+     * @deprecated since 1.15, suggest using the Builder class.
+     */
+    @Deprecated
+    public DefaultTableStatisticsEntry(DeviceId deviceId,
+                                       int  tableId,
+                                       long activeFlowEntries,
+                                       long packetsLookedupCount,
+                                       long packetsMatchedCount) {
+        this.deviceId = checkNotNull(deviceId);
+        this.tableId = IndexTableId.of(tableId);
+        this.activeFlowEntries = activeFlowEntries;
+        this.packetsLookedupCount = packetsLookedupCount;
+        this.packetsMatchedCount = packetsMatchedCount;
+        this.maxSize = NOT_PRESENT;
+    }
 
     /**
      * Default table statistics constructor.
@@ -37,17 +63,20 @@
      * @param activeFlowEntries number of active flow entries in the table
      * @param packetsLookedupCount number of packets looked up in table
      * @param packetsMatchedCount number of packets that hit table
+     * @param maxSize maximum size of this table
      */
-    public DefaultTableStatisticsEntry(DeviceId deviceId,
-                                  int  tableId,
-                                  long activeFlowEntries,
-                                  long packetsLookedupCount,
-                                  long packetsMatchedCount) {
+    private DefaultTableStatisticsEntry(DeviceId deviceId,
+                                       TableId  tableId,
+                                       long activeFlowEntries,
+                                       long packetsLookedupCount,
+                                       long packetsMatchedCount,
+                                       long maxSize) {
         this.deviceId = checkNotNull(deviceId);
         this.tableId = tableId;
         this.activeFlowEntries = activeFlowEntries;
         this.packetsLookedupCount = packetsLookedupCount;
         this.packetsMatchedCount = packetsMatchedCount;
+        this.maxSize = maxSize;
     }
 
     @Override
@@ -61,6 +90,12 @@
 
     @Override
     public int tableId() {
+        return tableId.type() == TableId.Type.INDEX ? ((IndexTableId) tableId).id() : tableId.hashCode();
+    }
+
+    @Override
+    public TableId table() {
+        //TODO: this is a temporary method, should implement tableId() like this method.
         return tableId;
     }
 
@@ -83,4 +118,73 @@
     public DeviceId deviceId() {
         return deviceId;
     }
-}
+
+    @Override
+    public long maxSize() {
+        return maxSize;
+    }
+
+    @Override
+    public boolean hasPacketsLookedup() {
+        return packetsLookedupCount == NOT_PRESENT ? false : true;
+    }
+
+    @Override
+    public boolean hasMaxSize() {
+        return maxSize == NOT_PRESENT ? false : true;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static final class Builder {
+        private DeviceId deviceId;
+        private TableId tableId;
+        private Long activeFlowEntries;
+        private Long packetsMatchedCount;
+        private Long packetsLookedUpCount = NOT_PRESENT;
+        private Long maxSize = NOT_PRESENT;
+
+        public Builder withDeviceId(DeviceId deviceId) {
+            this.deviceId = deviceId;
+            return this;
+        }
+
+        public Builder withTableId(TableId tableId) {
+            this.tableId = tableId;
+            return this;
+        }
+
+        public Builder withActiveFlowEntries(long activeFlowEntries) {
+            this.activeFlowEntries = activeFlowEntries;
+            return this;
+        }
+
+        public Builder withPacketsLookedUpCount(long packetsLookedUpCount) {
+            this.packetsLookedUpCount = packetsLookedUpCount;
+            return this;
+        }
+
+        public Builder withPacketsMatchedCount(long packetsMatchedCount) {
+            this.packetsMatchedCount = packetsMatchedCount;
+            return this;
+        }
+
+        public Builder withMaxSize(long maxSize) {
+            this.maxSize = maxSize;
+            return this;
+        }
+
+        public TableStatisticsEntry build() {
+            checkNotNull(deviceId, "DeviceId cannot be null");
+            checkNotNull(tableId, "TableId cannot be null");
+            checkNotNull(activeFlowEntries, "ActiveFlowEntries cannot be null");
+            checkNotNull(packetsMatchedCount, "PacketsMatchedCount cannot be null");
+
+            return new DefaultTableStatisticsEntry(deviceId, tableId, activeFlowEntries, packetsLookedUpCount,
+                    packetsMatchedCount, maxSize);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/core/api/src/main/java/org/onosproject/net/flow/TableStatisticsEntry.java b/core/api/src/main/java/org/onosproject/net/flow/TableStatisticsEntry.java
index 505d535..1b39146 100644
--- a/core/api/src/main/java/org/onosproject/net/flow/TableStatisticsEntry.java
+++ b/core/api/src/main/java/org/onosproject/net/flow/TableStatisticsEntry.java
@@ -30,13 +30,21 @@
     DeviceId  deviceId();
 
     /**
-     * Returns the table number.
+     * Returns the integer table id.
      *
-     * @return table number
+     * @return integer table id
      */
+    @Deprecated
     int  tableId();
 
     /**
+     * Returns the table id.
+     *
+     * @return table id
+     */
+    TableId table();
+
+    /**
      * Returns the number of active flow entries in this table.
      *
      * @return the number of active flow entries
@@ -56,4 +64,26 @@
      * @return the number of packets that successfully matched in the table
      */
     long packetsMatched();
-}
+
+    /**
+     * Returns the maximum size of this table.
+     *
+     * @return the maximum size of this table
+     */
+    long maxSize();
+
+    /**
+     * To check whether packetLookedUp is present in this TableStatisticsEntry.
+     *
+     * @return true if packetLookedUp is present, otherwise false;
+     */
+    boolean hasPacketsLookedup();
+
+    /**
+     * To check whether maxSize is present in this TableStatisticsEntry.
+     *
+     * @return true if maxSize is present, otherwise false;
+     */
+    boolean hasMaxSize();
+
+}
\ No newline at end of file
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/DefaultPiPipeconf.java b/core/api/src/main/java/org/onosproject/net/pi/model/DefaultPiPipeconf.java
index 7d9dd28..cff8248 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/model/DefaultPiPipeconf.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/DefaultPiPipeconf.java
@@ -17,11 +17,15 @@
 package org.onosproject.net.pi.model;
 
 import com.google.common.collect.ImmutableMap;
+import org.apache.commons.io.IOUtils;
 import org.onosproject.net.driver.Behaviour;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Map;
 import java.util.Optional;
@@ -37,14 +41,16 @@
 
     private final PiPipeconfId id;
     private final PiPipelineModel pipelineModel;
+    private final long fingerprint;
     private final Map<Class<? extends Behaviour>, Class<? extends Behaviour>> behaviours;
     private final Map<ExtensionType, URL> extensions;
 
-    private DefaultPiPipeconf(PiPipeconfId id, PiPipelineModel pipelineModel,
+    private DefaultPiPipeconf(PiPipeconfId id, PiPipelineModel pipelineModel, long fingerprint,
                               Map<Class<? extends Behaviour>, Class<? extends Behaviour>> behaviours,
                               Map<ExtensionType, URL> extensions) {
         this.id = id;
         this.pipelineModel = pipelineModel;
+        this.fingerprint = fingerprint;
         this.behaviours = behaviours;
         this.extensions = extensions;
     }
@@ -60,6 +66,11 @@
     }
 
     @Override
+    public long fingerprint() {
+        return fingerprint;
+    }
+
+    @Override
     public Collection<Class<? extends Behaviour>> behaviours() {
         return behaviours.keySet();
     }
@@ -175,8 +186,23 @@
         public PiPipeconf build() {
             checkNotNull(id);
             checkNotNull(pipelineModel);
-            return new DefaultPiPipeconf(id, pipelineModel, behaviourMapBuilder.build(), extensionMapBuilder.build());
+
+            Map<ExtensionType, URL> extensions = extensionMapBuilder.build();
+            return new DefaultPiPipeconf(id, pipelineModel, generateFingerprint(extensions),
+                                         behaviourMapBuilder.build(), extensions);
         }
 
+        private long generateFingerprint(Map<ExtensionType, URL> extensions) {
+            Collection<Integer> hashArray = new ArrayList<>();
+            for (Map.Entry<ExtensionType, URL> pair : extensions.entrySet()) {
+                try {
+                    hashArray.add(Arrays.hashCode(ByteBuffer.wrap(IOUtils.toByteArray(
+                            pair.getValue().openStream())).array()));
+                } catch (IOException e) {
+                    throw new IllegalArgumentException(e);
+                }
+            }
+            return Arrays.hashCode(hashArray.toArray());
+        }
     }
 }
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiPipeconf.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiPipeconf.java
index 5fedaf7..e3664e1 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/model/PiPipeconf.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiPipeconf.java
@@ -45,6 +45,13 @@
     PiPipelineModel pipelineModel();
 
     /**
+     * Returns the fingerprint of pipeconf.
+     *
+     * @return a fingerprint
+     */
+    long fingerprint();
+
+    /**
      * Returns all pipeline-specific behaviour interfaces defined by this configuration.
      *
      * @return a collection of behaviours
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCell.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCell.java
new file mode 100644
index 0000000..9508cbc
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCell.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2017-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.onosproject.net.pi.runtime;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+
+/**
+ * Counter cell of a protocol-independent pipeline.
+ */
+@Beta
+public final class PiCounterCell {
+
+    private final PiCounterCellId cellId;
+    private final PiCounterCellData counterData;
+
+    /**
+     * Creates a new counter cell for the given cell identifier and counter cell data.
+     *
+     * @param cellId  counter cell identifier
+     * @param piCounterCellData  counter cell data
+     */
+    public PiCounterCell(PiCounterCellId cellId, PiCounterCellData piCounterCellData) {
+        this.cellId = cellId;
+        this.counterData = piCounterCellData;
+    }
+
+    /**
+     * Creates a new counter cell for the given cell identifier, number of packets and bytes.
+     *
+     * @param cellId  counter cell identifier
+     * @param packets  number of packets
+     * @param bytes  number of bytes
+     */
+    public PiCounterCell(PiCounterCellId cellId, long packets, long bytes) {
+        this.cellId = cellId;
+        this.counterData = new PiCounterCellData(packets, bytes);
+    }
+
+    /**
+     * Returns the cell identifier.
+     *
+     * @return cell identifier
+     */
+    public PiCounterCellId cellId() {
+        return cellId;
+    }
+
+    /**
+     * Returns the data contained by this cell.
+     *
+     * @return counter cell data
+     */
+    public PiCounterCellData data() {
+        return counterData;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof PiCounterCell)) {
+            return false;
+        }
+        PiCounterCell that = (PiCounterCell) o;
+        return Objects.equal(cellId, that.cellId) &&
+                Objects.equal(counterData, that.counterData);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(cellId, counterData);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("cellId", cellId)
+                .add("counterData", counterData)
+                .toString();
+    }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCellData.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCellData.java
index f0aada5..b640970 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCellData.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCellData.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017-present Open Networking Foundation
+ * Copyright 2018-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.
@@ -23,37 +23,26 @@
 /**
  * Data of a counter cell of a protocol-independent pipeline.
  */
+
 @Beta
 public final class PiCounterCellData {
 
-    private final PiCounterCellId cellId;
     private final long packets;
     private final long bytes;
 
     /**
-     * Creates a new counter cell data for the given cell identifier, number of packets and bytes.
+     * Creates a new counter cell data for the given number of packets and bytes.
      *
-     * @param cellId  counter cell identifier
-     * @param packets number of packets
-     * @param bytes   number of bytes
+     * @param packets  number of packets
+     * @param bytes  number of bytes
      */
-    public PiCounterCellData(PiCounterCellId cellId, long packets, long bytes) {
-        this.cellId = cellId;
+    public PiCounterCellData(long packets, long bytes) {
         this.packets = packets;
         this.bytes = bytes;
     }
 
     /**
-     * Returns the cell identifier.
-     *
-     * @return cell identifier
-     */
-    public PiCounterCellId cellId() {
-        return cellId;
-    }
-
-    /**
-     * Returns the packet count value contained by this cell.
+     * Returns the packet count value contained by this counter data.
      *
      * @return number of packets
      */
@@ -62,7 +51,7 @@
     }
 
     /**
-     * Returns the byte count value contained by this cell.
+     * Returns the byte count value contained by this counter data.
      *
      * @return number of bytes
      */
@@ -80,19 +69,17 @@
         }
         PiCounterCellData that = (PiCounterCellData) o;
         return packets == that.packets &&
-                bytes == that.bytes &&
-                Objects.equal(cellId, that.cellId);
+                bytes == that.bytes;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hashCode(cellId, packets, bytes);
+        return Objects.hashCode(packets, bytes);
     }
 
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(this)
-                .add("cellId", cellId)
                 .add("packets", packets)
                 .add("bytes", bytes)
                 .toString();
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableEntry.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableEntry.java
index f8a1460..ac73bbe 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableEntry.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableEntry.java
@@ -42,10 +42,11 @@
     private final long cookie;
     private final int priority;
     private final double timeout;
+    private final PiCounterCellData counterData;
 
     private PiTableEntry(PiTableId tableId, PiMatchKey matchKey,
                          PiTableAction tableAction, boolean isDefaultAction,
-                         long cookie, int priority, double timeout) {
+                         long cookie, int priority, double timeout, PiCounterCellData data) {
         this.tableId = tableId;
         this.matchKey = matchKey;
         this.tableAction = tableAction;
@@ -53,6 +54,7 @@
         this.cookie = cookie;
         this.priority = priority;
         this.timeout = timeout;
+        this.counterData = data;
     }
 
     /**
@@ -125,6 +127,18 @@
         return timeout == NO_TIMEOUT ? Optional.empty() : Optional.of(timeout);
     }
 
+    /**
+     * Returns the data of the counter cell associated with this table entry.
+     * This method is meaningful only if the table entry was read from the
+     * infrastructure device and the table has direct counters, otherwise
+     * returns null.
+     *
+     * @return counter cell data
+     */
+    public PiCounterCellData counter() {
+        return counterData;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {
@@ -197,6 +211,7 @@
         private long cookie = 0;
         private int priority = NO_PRIORITY;
         private double timeout = NO_TIMEOUT;
+        private PiCounterCellData counterData;
 
         private Builder() {
             // Hides constructor.
@@ -272,6 +287,17 @@
         }
 
         /**
+         * Sets the counter cell data of this table entry.
+         *
+         * @param data counter cell data
+         * @return this
+         */
+        public Builder withCounterCellData(PiCounterCellData data) {
+            this.counterData = checkNotNull(data, "Counter cell data cannot be null");
+            return this;
+        }
+
+        /**
          * Builds the table entry.
          *
          * @return a new table entry
@@ -281,7 +307,7 @@
             checkNotNull(matchKey);
             final boolean isDefaultAction = matchKey.equals(PiMatchKey.EMPTY);
             return new PiTableEntry(tableId, matchKey, tableAction,
-                                    isDefaultAction, cookie, priority, timeout);
+                                    isDefaultAction, cookie, priority, timeout, counterData);
         }
     }
 }
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiCounterCellDataTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiCounterCellDataTest.java
index 6676470..bfb8782 100644
--- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiCounterCellDataTest.java
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiCounterCellDataTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017-present Open Networking Foundation
+ * Copyright 2018-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.
@@ -18,48 +18,25 @@
 
 import com.google.common.testing.EqualsTester;
 import org.junit.Test;
-import org.onosproject.net.pi.model.PiActionId;
-import org.onosproject.net.pi.model.PiTableId;
 
 import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
-import static org.onosproject.net.pi.runtime.PiConstantsTest.DROP;
 
 /**
- * Unit tests for PiCounterCellData class.
+ * Unit tests for PiCounterData class.
  */
 public class PiCounterCellDataTest {
 
-    private static final PiTableEntry PI_TABLE_ENTRY_1 = PiTableEntry.builder()
-            .forTable(PiTableId.of("T10"))
-            .withCookie(0xac)
-            .withPriority(10)
-            .withAction(PiAction.builder().withId(PiActionId.of(DROP)).build())
-            .withTimeout(100)
-            .build();
-    private static final PiTableEntry PI_TABLE_ENTRY_2 = PiTableEntry.builder()
-            .forTable(PiTableId.of("T20"))
-            .withCookie(0xac)
-            .withPriority(10)
-            .withAction(PiAction.builder().withId(PiActionId.of(DROP)).build())
-            .withTimeout(1000)
-            .build();
-
-    private static final PiCounterCellId PI_COUNTER_CELL_ID_1 =
-            PiCounterCellId.ofDirect(PI_TABLE_ENTRY_1);
-    private static final PiCounterCellId PI_COUNTER_CELL_ID_2 =
-            PiCounterCellId.ofDirect(PI_TABLE_ENTRY_2);
-
     private static final long PACKETS_1 = 10;
     private static final long PACKETS_2 = 20;
     private static final long BYTES_1 = 100;
     private static final long BYTES_2 = 200;
 
-    private static final PiCounterCellData PI_COUNTER_CELL_DATA_1 =
-            new PiCounterCellData(PI_COUNTER_CELL_ID_1, PACKETS_1, BYTES_1);
-    private static final PiCounterCellData SAME_AS_PI_COUNTER_CELL_DATA_1 =
-            new PiCounterCellData(PI_COUNTER_CELL_ID_1, PACKETS_1, BYTES_1);
-    private static final PiCounterCellData PI_COUNTER_CELL_DATA_2 =
-            new PiCounterCellData(PI_COUNTER_CELL_ID_2, PACKETS_2, BYTES_2);
+    private static final PiCounterCellData PI_COUNTER_DATA_1 =
+            new PiCounterCellData(PACKETS_1, BYTES_1);
+    private static final PiCounterCellData SAME_AS_PI_COUNTER_DATA_1 =
+            new PiCounterCellData(PACKETS_1, BYTES_1);
+    private static final PiCounterCellData PI_COUNTER_DATA_2 =
+            new PiCounterCellData(PACKETS_2, BYTES_2);
 
     /**
      * Checks that the PiCounterCellData class is immutable.
@@ -75,8 +52,8 @@
     @Test
     public void testEquals() {
         new EqualsTester()
-                .addEqualityGroup(PI_COUNTER_CELL_DATA_1, SAME_AS_PI_COUNTER_CELL_DATA_1)
-                .addEqualityGroup(PI_COUNTER_CELL_DATA_2)
+                .addEqualityGroup(PI_COUNTER_DATA_1, SAME_AS_PI_COUNTER_DATA_1)
+                .addEqualityGroup(PI_COUNTER_DATA_2)
                 .testEquals();
     }
 }
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiCounterCellTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiCounterCellTest.java
new file mode 100644
index 0000000..ab65efe
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiCounterCellTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2017-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.onosproject.net.pi.runtime;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiTableId;
+
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+import static org.onosproject.net.pi.runtime.PiConstantsTest.DROP;
+
+/**
+ * Unit tests for PiCounterCell class.
+ */
+public class PiCounterCellTest {
+
+    private static final PiTableEntry PI_TABLE_ENTRY_1 = PiTableEntry.builder()
+            .forTable(PiTableId.of("T10"))
+            .withCookie(0xac)
+            .withPriority(10)
+            .withAction(PiAction.builder().withId(PiActionId.of(DROP)).build())
+            .withTimeout(100)
+            .build();
+    private static final PiTableEntry PI_TABLE_ENTRY_2 = PiTableEntry.builder()
+            .forTable(PiTableId.of("T20"))
+            .withCookie(0xac)
+            .withPriority(10)
+            .withAction(PiAction.builder().withId(PiActionId.of(DROP)).build())
+            .withTimeout(1000)
+            .build();
+
+    private static final PiCounterCellId PI_COUNTER_CELL_ID_1 =
+            PiCounterCellId.ofDirect(PI_TABLE_ENTRY_1);
+    private static final PiCounterCellId PI_COUNTER_CELL_ID_2 =
+            PiCounterCellId.ofDirect(PI_TABLE_ENTRY_2);
+
+    private static final long PACKETS_1 = 10;
+    private static final long PACKETS_2 = 20;
+    private static final long BYTES_1 = 100;
+    private static final long BYTES_2 = 200;
+
+    private static final PiCounterCell PI_COUNTER_CELL_1 =
+            new PiCounterCell(PI_COUNTER_CELL_ID_1, PACKETS_1, BYTES_1);
+    private static final PiCounterCell SAME_AS_PI_COUNTER_CELL_1 =
+            new PiCounterCell(PI_COUNTER_CELL_ID_1, PACKETS_1, BYTES_1);
+    private static final PiCounterCell PI_COUNTER_CELL_2 =
+            new PiCounterCell(PI_COUNTER_CELL_ID_2, PACKETS_2, BYTES_2);
+
+    /**
+     * Checks that the PiCounterCell class is immutable.
+     */
+    @Test
+    public void testImmutability() {
+        assertThatClassIsImmutable(PiCounterCell.class);
+    }
+
+    /**
+     * Checks the operation of equals(), hashCode() and toString() methods.
+     */
+    @Test
+    public void testEquals() {
+        new EqualsTester()
+                .addEqualityGroup(PI_COUNTER_CELL_1, SAME_AS_PI_COUNTER_CELL_1)
+                .addEqualityGroup(PI_COUNTER_CELL_2)
+                .testEquals();
+    }
+}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/TableStatisticsEntryCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/TableStatisticsEntryCodec.java
index efd1d2c..f1af62b 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/TableStatisticsEntryCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/TableStatisticsEntryCodec.java
@@ -33,7 +33,7 @@
         checkNotNull(entry, "Table Statistics entry cannot be null");
 
         final ObjectNode result = context.mapper().createObjectNode()
-                .put("tableId", entry.tableId())
+                .put("tableId", entry.table().toString())
                 .put("deviceId", entry.deviceId().toString())
                 .put("activeEntries", entry.activeFlowEntries())
                 .put("packetsLookedUp", entry.packetsLookedup())
@@ -43,4 +43,3 @@
     }
 
 }
-
diff --git a/core/net/src/main/java/org/onosproject/net/pi/impl/PiPipeconfManager.java b/core/net/src/main/java/org/onosproject/net/pi/impl/PiPipeconfManager.java
index b6a5dbe..fa10d33 100644
--- a/core/net/src/main/java/org/onosproject/net/pi/impl/PiPipeconfManager.java
+++ b/core/net/src/main/java/org/onosproject/net/pi/impl/PiPipeconfManager.java
@@ -186,7 +186,7 @@
                 deviceId, BasicDeviceConfig.class);
         if (basicDeviceConfig == null) {
             log.warn("Unable to get basic device config for {}, " +
-                             "aborting pipeconf driver merge");
+                             "aborting pipeconf driver merge", deviceId);
             return null;
         }
         String baseDriverName = basicDeviceConfig.driver();
diff --git a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
index 0e684fe..0fd964e 100644
--- a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
+++ b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
@@ -231,6 +231,7 @@
 import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
 import org.onosproject.net.pi.runtime.PiActionParam;
 import org.onosproject.net.pi.runtime.PiControlMetadata;
+import org.onosproject.net.pi.runtime.PiCounterCell;
 import org.onosproject.net.pi.runtime.PiCounterCellData;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
 import org.onosproject.net.pi.runtime.PiEntity;
@@ -692,6 +693,7 @@
                     PiActionGroupMemberId.class,
                     PiActionParam.class,
                     PiControlMetadata.class,
+                    PiCounterCell.class,
                     PiCounterCellData.class,
                     PiCounterCellId.class,
                     PiEntity.class,
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2GroupHandler.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2GroupHandler.java
index c5204ce..2521daf 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2GroupHandler.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2GroupHandler.java
@@ -692,6 +692,7 @@
         MacAddress dstMac = readEthDstFromSelector(nextObj.meta());
         if (dstMac != null && dstMac.isMulticast()) {
             processL2MulticastNextObjective(nextObj);
+            return;
         }
 
         List<GroupInfo> groupInfos = prepareL2InterfaceGroup(nextObj, assignedVlan);
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java
index d0acdee..5a233ef 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java
@@ -31,6 +31,7 @@
 import org.onosproject.net.pi.model.PiPipelineModel;
 import org.onosproject.net.pi.model.PiTableId;
 import org.onosproject.net.pi.model.PiTableModel;
+import org.onosproject.net.pi.runtime.PiCounterCell;
 import org.onosproject.net.pi.runtime.PiCounterCellData;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
 import org.onosproject.net.pi.runtime.PiTableEntry;
@@ -89,11 +90,11 @@
     private static final String SUPPORT_TABLE_COUNTERS = "supportTableCounters";
     private static final boolean DEFAULT_SUPPORT_TABLE_COUNTERS = true;
 
-    // If true, we read all direct counters of a table with one request.
-    // Otherwise, we send as many requests as the number of table entries.
-    private static final String READ_ALL_DIRECT_COUNTERS = "tableReadAllDirectCounters";
-    // FIXME: set to true as soon as the feature is implemented in P4Runtime.
-    private static final boolean DEFAULT_READ_ALL_DIRECT_COUNTERS = false;
+    // If true, assumes that the device returns table entry message populated
+    // with direct counter values. If false, we issue a second P4Runtime request
+    // to read the direct counter values.
+    private static final String READ_COUNTERS_WITH_TABLE_ENTRIES = "tableReadCountersWithTableEntries";
+    private static final boolean DEFAULT_READ_COUNTERS_WITH_TABLE_ENTRIES = true;
 
     // For default entries, P4Runtime mandates that only MODIFY messages are
     // allowed. If true, treats default entries as normal table entries,
@@ -153,7 +154,6 @@
 
         // Synchronize mirror with the device state.
         syncMirror(deviceEntries);
-        // TODO: ONOS-7596 read counters with table entries
         final Map<PiTableEntry, PiCounterCellData> counterCellMap =
                 readEntryCounters(deviceEntries);
         // Forge flow entries with counter values.
@@ -461,25 +461,22 @@
             return Collections.emptyMap();
         }
 
-        Collection<PiCounterCellData> cellDatas;
-
-        if (driverBoolProperty(READ_ALL_DIRECT_COUNTERS,
-                               DEFAULT_READ_ALL_DIRECT_COUNTERS)) {
-            // FIXME: read counters when dumping table entries ONOS-7596
-            cellDatas = Collections.emptyList();
+        if (driverBoolProperty(READ_COUNTERS_WITH_TABLE_ENTRIES,
+                               DEFAULT_READ_COUNTERS_WITH_TABLE_ENTRIES)) {
+            return tableEntries.stream().collect(Collectors.toMap(c -> c, PiTableEntry::counter));
         } else {
+            Collection<PiCounterCell> cells;
             Set<PiCounterCellId> cellIds = tableEntries.stream()
                     // Ignore counter for default entry.
                     .filter(e -> !e.isDefaultAction())
                     .filter(e -> tableHasCounter(e.table()))
                     .map(PiCounterCellId::ofDirect)
                     .collect(Collectors.toSet());
-            cellDatas = getFutureWithDeadline(client.readCounterCells(cellIds, pipeconf),
+            cells = getFutureWithDeadline(client.readCounterCells(cellIds, pipeconf),
                                               "reading table counters", Collections.emptyList());
+            return cells.stream()
+                    .collect(Collectors.toMap(c -> c.cellId().tableEntry(), PiCounterCell::data));
         }
-        return cellDatas.stream()
-                .collect(Collectors.toMap(c -> c.cellId().tableEntry(), c -> c));
-
     }
 
     private boolean tableHasCounter(PiTableId tableId) {
diff --git a/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/PortStatisticsDiscoveryImpl.java b/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/PortStatisticsDiscoveryImpl.java
index ec8d5cf..0e39230 100644
--- a/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/PortStatisticsDiscoveryImpl.java
+++ b/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/PortStatisticsDiscoveryImpl.java
@@ -28,7 +28,7 @@
 import org.onosproject.net.driver.AbstractHandlerBehaviour;
 import org.onosproject.net.pi.model.PiCounterId;
 import org.onosproject.net.pi.model.PiPipeconf;
-import org.onosproject.net.pi.runtime.PiCounterCellData;
+import org.onosproject.net.pi.runtime.PiCounterCell;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
 import org.onosproject.net.pi.service.PiPipeconfService;
 import org.onosproject.p4runtime.api.P4RuntimeClient;
@@ -112,7 +112,7 @@
             counterCellIds.add(PiCounterCellId.ofIndirect(egressCounterId(), p));
         });
 
-        Collection<PiCounterCellData> counterEntryResponse;
+        Collection<PiCounterCell> counterEntryResponse;
         try {
             counterEntryResponse = client.readCounterCells(counterCellIds, pipeconf).get();
         } catch (InterruptedException | ExecutionException e) {
@@ -121,25 +121,25 @@
             return Collections.emptyList();
         }
 
-        counterEntryResponse.forEach(counterData -> {
-            if (counterData.cellId().counterType() != INDIRECT) {
-                log.warn("Invalid counter data type {}, skipping", counterData.cellId().counterType());
+        counterEntryResponse.forEach(counterCell -> {
+            if (counterCell.cellId().counterType() != INDIRECT) {
+                log.warn("Invalid counter data type {}, skipping", counterCell.cellId().counterType());
                 return;
             }
-            PiCounterCellId indCellId = counterData.cellId();
+            PiCounterCellId indCellId = counterCell.cellId();
             if (!portStatBuilders.containsKey(indCellId.index())) {
-                log.warn("Unrecognized counter index {}, skipping", counterData);
+                log.warn("Unrecognized counter index {}, skipping", counterCell);
                 return;
             }
             DefaultPortStatistics.Builder statsBuilder = portStatBuilders.get(indCellId.index());
-            if (counterData.cellId().counterId().equals(ingressCounterId())) {
-                statsBuilder.setPacketsReceived(counterData.packets());
-                statsBuilder.setBytesReceived(counterData.bytes());
-            } else if (counterData.cellId().counterId().equals(egressCounterId())) {
-                statsBuilder.setPacketsSent(counterData.packets());
-                statsBuilder.setBytesSent(counterData.bytes());
+            if (counterCell.cellId().counterId().equals(ingressCounterId())) {
+                statsBuilder.setPacketsReceived(counterCell.data().packets());
+                statsBuilder.setBytesReceived(counterCell.data().bytes());
+            } else if (counterCell.cellId().counterId().equals(egressCounterId())) {
+                statsBuilder.setPacketsSent(counterCell.data().packets());
+                statsBuilder.setBytesSent(counterCell.data().bytes());
             } else {
-                log.warn("Unrecognized counter ID {}, skipping", counterData);
+                log.warn("Unrecognized counter ID {}, skipping", counterCell);
             }
 
         });
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricPipeliner.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricPipeliner.java
index 7cd154b..7b3006d 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricPipeliner.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricPipeliner.java
@@ -242,6 +242,9 @@
         }
 
         FlowRuleOperations ops = buildFlowRuleOps(objective, flowRules);
+        if (ops == null) {
+            return;
+        }
         flowRuleService.apply(ops);
 
         flowRules.forEach(flow -> {
diff --git a/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeClient.java b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeClient.java
index 8c1d7d9..f30ec28 100644
--- a/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeClient.java
+++ b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeClient.java
@@ -25,7 +25,7 @@
 import org.onosproject.net.pi.runtime.PiActionGroup;
 import org.onosproject.net.pi.runtime.PiActionGroupMember;
 import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
-import org.onosproject.net.pi.runtime.PiCounterCellData;
+import org.onosproject.net.pi.runtime.PiCounterCell;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
 import org.onosproject.net.pi.runtime.PiMeterCellConfig;
 import org.onosproject.net.pi.runtime.PiMeterCellId;
@@ -178,7 +178,7 @@
      * @param pipeconf   pipeconf
      * @return list of counter data
      */
-    CompletableFuture<List<PiCounterCellData>> readAllCounterCells(
+    CompletableFuture<List<PiCounterCell>> readAllCounterCells(
             Set<PiCounterId> counterIds, PiPipeconf pipeconf);
 
     /**
@@ -189,7 +189,7 @@
      * @param pipeconf pipeconf
      * @return list of counter data
      */
-    CompletableFuture<List<PiCounterCellData>> readCounterCells(
+    CompletableFuture<List<PiCounterCell>> readCounterCells(
             Set<PiCounterCellId> cellIds, PiPipeconf pipeconf);
 
     /**
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/CounterEntryCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/CounterEntryCodec.java
index e883821..6c29062 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/CounterEntryCodec.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/CounterEntryCodec.java
@@ -20,7 +20,7 @@
 import org.onosproject.net.pi.model.PiCounterType;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.model.PiTableId;
-import org.onosproject.net.pi.runtime.PiCounterCellData;
+import org.onosproject.net.pi.runtime.PiCounterCell;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
 import org.onosproject.net.pi.runtime.PiTableEntry;
 import org.slf4j.Logger;
@@ -135,8 +135,8 @@
      * @param pipeconf pipeconf
      * @return collection of PI counter cell data
      */
-    static List<PiCounterCellData> decodeCounterEntities(List<Entity> entities,
-                                                         PiPipeconf pipeconf) {
+    static List<PiCounterCell> decodeCounterEntities(List<Entity> entities,
+                                                     PiPipeconf pipeconf) {
 
         final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
 
@@ -248,9 +248,9 @@
         }
     }
 
-    private static PiCounterCellData decodeCounterEntity(Entity entity,
-                                                         PiPipeconf pipeconf,
-                                                         P4InfoBrowser browser)
+    private static PiCounterCell decodeCounterEntity(Entity entity,
+                                                     PiPipeconf pipeconf,
+                                                     P4InfoBrowser browser)
             throws EncodeException, P4InfoBrowser.NotFoundException {
 
         CounterData counterData;
@@ -276,8 +276,8 @@
                     entity.getEntityCase().name()));
         }
 
-        return new PiCounterCellData(piCellId,
-                                     counterData.getPacketCount(),
-                                     counterData.getByteCount());
+        return new PiCounterCell(piCellId,
+                                 counterData.getPacketCount(),
+                                 counterData.getByteCount());
     }
 }
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
index c291db4..a06d67e 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
@@ -43,7 +43,7 @@
 import org.onosproject.net.pi.runtime.PiActionGroup;
 import org.onosproject.net.pi.runtime.PiActionGroupMember;
 import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
-import org.onosproject.net.pi.runtime.PiCounterCellData;
+import org.onosproject.net.pi.runtime.PiCounterCell;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
 import org.onosproject.net.pi.runtime.PiMeterCellConfig;
 import org.onosproject.net.pi.runtime.PiMeterCellId;
@@ -281,15 +281,15 @@
     }
 
     @Override
-    public CompletableFuture<List<PiCounterCellData>> readCounterCells(Set<PiCounterCellId> cellIds,
-                                                                       PiPipeconf pipeconf) {
+    public CompletableFuture<List<PiCounterCell>> readCounterCells(Set<PiCounterCellId> cellIds,
+                                                                   PiPipeconf pipeconf) {
         return supplyInContext(() -> doReadCounterCells(Lists.newArrayList(cellIds), pipeconf),
                                "readCounterCells-" + cellIds.hashCode());
     }
 
     @Override
-    public CompletableFuture<List<PiCounterCellData>> readAllCounterCells(Set<PiCounterId> counterIds,
-                                                                          PiPipeconf pipeconf) {
+    public CompletableFuture<List<PiCounterCell>> readAllCounterCells(Set<PiCounterId> counterIds,
+                                                                      PiPipeconf pipeconf) {
         return supplyInContext(() -> doReadAllCounterCells(Lists.newArrayList(counterIds), pipeconf),
                                "readAllCounterCells-" + counterIds.hashCode());
     }
@@ -408,6 +408,11 @@
             return null;
         }
 
+        ForwardingPipelineConfig.Cookie pipeconfCookie = ForwardingPipelineConfig.Cookie
+                .newBuilder()
+                .setCookie(pipeconf.fingerprint())
+                .build();
+
         // FIXME: This is specific to PI P4Runtime implementation.
         P4Config.P4DeviceConfig p4DeviceConfigMsg = P4Config.P4DeviceConfig
                 .newBuilder()
@@ -420,6 +425,7 @@
                 .newBuilder()
                 .setP4Info(p4Info)
                 .setP4DeviceConfig(p4DeviceConfigMsg.toByteString())
+                .setCookie(pipeconfCookie)
                 .build();
     }
 
@@ -428,6 +434,8 @@
         GetForwardingPipelineConfigRequest request = GetForwardingPipelineConfigRequest
                 .newBuilder()
                 .setDeviceId(p4DeviceId)
+                .setResponseType(GetForwardingPipelineConfigRequest
+                                         .ResponseType.COOKIE_ONLY)
                 .build();
 
         GetForwardingPipelineConfigResponse resp;
@@ -445,33 +453,14 @@
             }
             return false;
         }
-
-        ForwardingPipelineConfig expectedConfig = getPipelineConfig(
-                pipeconf, deviceData);
-
-        if (expectedConfig == null) {
-            return false;
-        }
-        if (!resp.hasConfig()) {
+        if (!resp.getConfig().hasCookie()) {
             log.warn("{} returned GetForwardingPipelineConfigResponse " +
-                             "with 'config' field unset",
+                             "with 'cookie' field unset",
                      deviceId);
             return false;
         }
-        if (resp.getConfig().getP4DeviceConfig().isEmpty()
-                && !expectedConfig.getP4DeviceConfig().isEmpty()) {
-            // Don't bother with a warn or error since we don't really allow
-            // updating the pipeline to a different one. So the P4Info should be
-            // enough for us.
-            log.debug("{} returned GetForwardingPipelineConfigResponse " +
-                              "with empty 'p4_device_config' field, " +
-                              "equality will be based only on P4Info",
-                      deviceId);
-            return resp.getConfig().getP4Info().equals(
-                    expectedConfig.getP4Info());
-        } else {
-            return resp.getConfig().equals(expectedConfig);
-        }
+
+        return resp.getConfig().getCookie().getCookie() == pipeconf.fingerprint();
     }
 
     private boolean doSetPipelineConfig(PiPipeconf pipeconf, ByteBuffer deviceData) {
@@ -570,6 +559,7 @@
                                 TableEntry.newBuilder()
                                         .setTableId(tableId)
                                         .setIsDefaultAction(defaultEntries)
+                                        .setCounterData(P4RuntimeOuterClass.CounterData.getDefaultInstance())
                                         .build())
                         .build())
                 .build());
@@ -662,21 +652,21 @@
         isClientMaster.set(isMaster);
     }
 
-    private List<PiCounterCellData> doReadAllCounterCells(
+    private List<PiCounterCell> doReadAllCounterCells(
             List<PiCounterId> counterIds, PiPipeconf pipeconf) {
         return doReadCounterEntities(
                 CounterEntryCodec.readAllCellsEntities(counterIds, pipeconf),
                 pipeconf);
     }
 
-    private List<PiCounterCellData> doReadCounterCells(
+    private List<PiCounterCell> doReadCounterCells(
             List<PiCounterCellId> cellIds, PiPipeconf pipeconf) {
         return doReadCounterEntities(
                 CounterEntryCodec.encodePiCounterCellIds(cellIds, pipeconf),
                 pipeconf);
     }
 
-    private List<PiCounterCellData> doReadCounterEntities(
+    private List<PiCounterCell> doReadCounterEntities(
             List<Entity> counterEntities, PiPipeconf pipeconf) {
 
         if (counterEntities.size() == 0) {
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/TableEntryEncoder.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/TableEntryEncoder.java
index d5d909f..7e2df98 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/TableEntryEncoder.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/TableEntryEncoder.java
@@ -29,6 +29,7 @@
 import org.onosproject.net.pi.runtime.PiActionGroupId;
 import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
 import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.net.pi.runtime.PiCounterCellData;
 import org.onosproject.net.pi.runtime.PiExactFieldMatch;
 import org.onosproject.net.pi.runtime.PiFieldMatch;
 import org.onosproject.net.pi.runtime.PiLpmFieldMatch;
@@ -40,6 +41,7 @@
 import org.slf4j.Logger;
 import p4.config.v1.P4InfoOuterClass;
 import p4.v1.P4RuntimeOuterClass.Action;
+import p4.v1.P4RuntimeOuterClass.CounterData;
 import p4.v1.P4RuntimeOuterClass.FieldMatch;
 import p4.v1.P4RuntimeOuterClass.TableAction;
 import p4.v1.P4RuntimeOuterClass.TableEntry;
@@ -249,6 +251,11 @@
             }
         }
 
+        // Counter.
+        if (piTableEntry.counter() != null) {
+            tableEntryMsgBuilder.setCounterData(encodeCounter(piTableEntry.counter()));
+        }
+
         return tableEntryMsgBuilder.build();
     }
 
@@ -281,6 +288,9 @@
         // Match key for field matches.
         piTableEntryBuilder.withMatchKey(decodeFieldMatchMsgs(tableEntryMsg.getMatchList(), tableInfo, browser));
 
+        // Counter.
+        piTableEntryBuilder.withCounterCellData(decodeCounter(tableEntryMsg.getCounterData()));
+
         return piTableEntryBuilder.build();
     }
 
@@ -505,4 +515,13 @@
         }
         return PiAction.builder().withId(id).withParameters(params).build();
     }
-}
+
+    static CounterData encodeCounter(PiCounterCellData piCounterCellData) {
+        return CounterData.newBuilder().setPacketCount(piCounterCellData.packets())
+                .setByteCount(piCounterCellData.bytes()).build();
+    }
+
+    static PiCounterCellData decodeCounter(CounterData counterData) {
+        return new PiCounterCellData(counterData.getPacketCount(), counterData.getByteCount());
+    }
+}
\ No newline at end of file
diff --git a/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TableEntryEncoderTest.java b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TableEntryEncoderTest.java
index 0799108..d9e5f9a 100644
--- a/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TableEntryEncoderTest.java
+++ b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TableEntryEncoderTest.java
@@ -32,11 +32,13 @@
 import org.onosproject.net.pi.runtime.PiAction;
 import org.onosproject.net.pi.runtime.PiActionGroupId;
 import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.net.pi.runtime.PiCounterCellData;
 import org.onosproject.net.pi.runtime.PiExactFieldMatch;
 import org.onosproject.net.pi.runtime.PiMatchKey;
 import org.onosproject.net.pi.runtime.PiTableEntry;
 import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
 import p4.v1.P4RuntimeOuterClass.Action;
+import p4.v1.P4RuntimeOuterClass.CounterData;
 import p4.v1.P4RuntimeOuterClass.TableEntry;
 
 import java.net.URL;
@@ -72,6 +74,9 @@
     private static final String ETHER_TYPE = "etherType";
     private static final String ECMP_GROUP_ID = "ecmp_group_id";
 
+    private static final long PACKETS = 10;
+    private static final long BYTES = 100;
+
     private final Random rand = new Random();
     private final URL p4InfoUrl = this.getClass().getResource("/test.p4info");
 
@@ -94,6 +99,7 @@
     private final PiActionId outActionId = PiActionId.of(SET_EGRESS_PORT);
     private final PiTableId tableId = PiTableId.of(TABLE_0);
     private final PiTableId ecmpTableId = PiTableId.of(TABLE_ECMP);
+    private final PiCounterCellData counterCellData = new PiCounterCellData(PACKETS, BYTES);
 
     private final PiTableEntry piTableEntry = PiTableEntry
             .builder()
@@ -111,6 +117,7 @@
                                 .build())
             .withPriority(1)
             .withCookie(2)
+            .withCounterCellData(counterCellData)
             .build();
 
     private final PiTableEntry piTableEntryWithoutAction = PiTableEntry
@@ -124,6 +131,7 @@
                                   .build())
             .withPriority(1)
             .withCookie(2)
+            .withCounterCellData(counterCellData)
             .build();
 
     private final PiTableEntry piTableEntryWithGroupAction = PiTableEntry
@@ -135,6 +143,7 @@
             .withAction(PiActionGroupId.of(1))
             .withPriority(1)
             .withCookie(2)
+            .withCounterCellData(counterCellData)
             .build();
 
     public TableEntryEncoderTest() throws ImmutableByteSequence.ByteSequenceTrimException {
@@ -198,6 +207,12 @@
         byte[] encodedActionParam = actionMsg.getParams(0).getValue().toByteArray();
         assertThat(encodedActionParam, is(portValue.asArray()));
 
+        // Counter
+        CounterData counterData = tableEntryMsg.getCounterData();
+        PiCounterCellData encodedCounterData = new PiCounterCellData(counterData.getPacketCount(),
+                                                                     counterData.getByteCount());
+        assertThat(encodedCounterData, is(counterCellData));
+
         // TODO: improve, assert other field match types (ternary, LPM)
     }
 
@@ -257,6 +272,12 @@
         // no action
         assertThat(tableEntryMsg.hasAction(), is(false));
 
+        // Counter
+        CounterData counterData = tableEntryMsg.getCounterData();
+        PiCounterCellData encodedCounterData = new PiCounterCellData(counterData.getPacketCount(),
+                                                                     counterData.getByteCount());
+        assertThat(encodedCounterData, is(counterCellData));
+
         // TODO: improve, assert other field match types (ternary, LPM)
     }
 }
diff --git a/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/OpenFlowRuleProvider.java b/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/OpenFlowRuleProvider.java
index bc06fe6..22b5246 100644
--- a/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/OpenFlowRuleProvider.java
+++ b/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/OpenFlowRuleProvider.java
@@ -51,6 +51,7 @@
 import org.onosproject.net.flow.FlowRuleProviderRegistry;
 import org.onosproject.net.flow.FlowRuleProviderService;
 import org.onosproject.net.flow.TableStatisticsEntry;
+import org.onosproject.net.flow.IndexTableId;
 import org.onosproject.net.provider.AbstractProvider;
 import org.onosproject.net.provider.ProviderId;
 import org.onosproject.net.statistic.DefaultLoad;
@@ -711,11 +712,25 @@
                                                           OFTableStatsEntry ofEntry) {
             TableStatisticsEntry entry = null;
             if (ofEntry != null) {
-                entry = new DefaultTableStatisticsEntry(deviceId,
-                                                        ofEntry.getTableId().getValue(),
-                                                        ofEntry.getActiveCount(),
-                                                        ofEntry.getLookupCount().getValue(),
-                                                        ofEntry.getMatchedCount().getValue());
+                IndexTableId tid = IndexTableId.of(ofEntry.getTableId().getValue());
+
+                try {
+                    entry = DefaultTableStatisticsEntry.builder()
+                            .withDeviceId(deviceId)
+                            .withTableId(tid)
+                            .withActiveFlowEntries(ofEntry.getActiveCount())
+                            .withPacketsLookedUpCount(ofEntry.getLookupCount().getValue())
+                            .withPacketsMatchedCount(ofEntry.getMatchedCount().getValue())
+                            .withMaxSize(ofEntry.getMaxEntries()).build();
+                } catch (UnsupportedOperationException e) {
+                    // The exception "UnsupportedOperationException" is thrown by "getMaxEntries()".
+                    entry = DefaultTableStatisticsEntry.builder()
+                            .withDeviceId(deviceId)
+                            .withTableId(tid)
+                            .withActiveFlowEntries(ofEntry.getActiveCount())
+                            .withPacketsLookedUpCount(ofEntry.getLookupCount().getValue())
+                            .withPacketsMatchedCount(ofEntry.getMatchedCount().getValue()).build();
+                }
             }
 
             return entry;
@@ -777,4 +792,4 @@
         }
     }
 
-}
+}
\ No newline at end of file
diff --git a/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProvider.java b/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProvider.java
index c6a0ae9..56afd93 100644
--- a/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProvider.java
+++ b/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProvider.java
@@ -113,6 +113,7 @@
     private static final String UNKNOWN = "unknown";
     private static final int REST_TIMEOUT_SEC = 5;
     private static final int EXECUTOR_THREAD_POOL_SIZE = 8;
+    private static final int DEVICE_POLL_SEC = 30;
     private final Logger log = getLogger(getClass());
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
@@ -148,6 +149,9 @@
     private final SharedScheduledExecutorService portStatisticsExecutor =
             SharedScheduledExecutors.getPoolThreadExecutor();
 
+    private final SharedScheduledExecutorService deviceConnectionExecutor =
+            SharedScheduledExecutors.getPoolThreadExecutor();
+    private ScheduledFuture<?> devicePollTask;
     private final List<ConfigFactory> factories = ImmutableList.of(
             new ConfigFactory<DeviceId, RestDeviceConfig>(SubjectFactories.DEVICE_SUBJECT_FACTORY,
                                                           RestDeviceConfig.class,
@@ -175,6 +179,7 @@
         netCfgService.addListener(configListener);
         executor.execute(RestDeviceProvider.this::createAndConnectDevices);
         scheduledTask = schedulePolling();
+        devicePollTask = scheduleDevicePolling();
         log.info("Started");
     }
 
@@ -206,6 +211,7 @@
         factories.forEach(netCfgService::unregisterConfigFactory);
         scheduledTask.cancel(true);
         executor.shutdown();
+        devicePollTask.cancel(true);
         log.info("Stopped");
     }
 
@@ -221,23 +227,91 @@
 
     @Override
     public void roleChanged(DeviceId deviceId, MastershipRole newRole) {
-        // TODO: This will be implemented later.
+        log.debug("Received role {} request for device {}", newRole, deviceId);
+        RestSBDevice device = controller.getDevice(deviceId);
+        if (device != null && testDeviceConnection(device)) {
+            providerService.receivedRoleReply(deviceId, newRole, newRole);
+        } else {
+            log.warn("Device not present or available {}", deviceId);
+            providerService.receivedRoleReply(deviceId, newRole, MastershipRole.NONE);
+        }
     }
 
     @Override
     public boolean isReachable(DeviceId deviceId) {
         RestSBDevice restDevice = controller.getDevice(deviceId);
-        if (restDevice == null) {
-            restDevice = controller.getProxySBDevice(deviceId);
-            if (restDevice == null) {
-                log.debug("the requested device id: " +
-                                  deviceId.toString() +
-                                  "  is not associated to any REST or REST " +
-                                  "proxy Device");
-                return false;
+        return restDevice != null ? restDevice.isActive() : false;
+    }
+
+    private ScheduledFuture scheduleDevicePolling() {
+        return deviceConnectionExecutor.scheduleWithFixedDelay(() -> {
+            try {
+                controller.getDevices().values().stream().forEach(restSBDevice -> {
+                            DeviceId deviceId = restSBDevice.deviceId();
+                            if (deviceService.getDevice(deviceId) != null) {
+                                boolean connected = testDeviceConnection(restSBDevice);
+                                restSBDevice.setActive(connected);
+                                if (deviceService.isAvailable(deviceId) && (!connected)) {
+                                    providerService.deviceDisconnected(deviceId);
+                                } else if (!deviceService.isAvailable(deviceId) && connected) {
+                                    DeviceDescription devDesc = getDesc(restSBDevice);
+                                    checkNotNull(devDesc, "deviceDescription cannot be null");
+                                    providerService.deviceConnected(
+                                            deviceId, mergeAnn(deviceId, devDesc));
+                                }
+                            }
+                        }
+                );
+            } catch (Exception e) {
+                log.error("Exception at schedule Device polling", e);
+            }
+        }, 1, DEVICE_POLL_SEC, TimeUnit.SECONDS);
+    }
+
+    private DeviceDescription getDesc(RestSBDevice restSBDev) {
+        DeviceId deviceId = restSBDev.deviceId();
+        if (restSBDev.isProxy()) {
+            Driver driver = driverService.getDriver(restSBDev.manufacturer().get(),
+                    restSBDev.hwVersion().get(),
+                    restSBDev.swVersion().get());
+
+            if (driver != null && driver.hasBehaviour(DevicesDiscovery.class)) {
+
+                //Creates the driver to communicate with the server
+                DevicesDiscovery devicesDiscovery =
+                        devicesDiscovery(restSBDev, driver);
+                return devicesDiscovery.deviceDetails(deviceId);
+            } else {
+                log.warn("Driver not found for {}", restSBDev);
+                return null;
             }
         }
-        return restDevice.isActive();
+        ChassisId cid = new ChassisId();
+        String ipAddress = restSBDev.ip().toString();
+        SparseAnnotations annotations = DefaultAnnotations.builder()
+                .set(IPADDRESS, ipAddress)
+                .set(AnnotationKeys.PROTOCOL, REST.toUpperCase())
+                .build();
+        String manufacturer = UNKNOWN;
+        String hwVersion = UNKNOWN;
+        String swVersion = UNKNOWN;
+        String serialNumber = UNKNOWN;
+
+        Device device = deviceService.getDevice(deviceId);
+        if (device != null) {
+            manufacturer = device.manufacturer();
+            hwVersion = device.hwVersion();
+            swVersion = device.swVersion();
+            serialNumber = device.serialNumber();
+        }
+
+        return new DefaultDeviceDescription(
+                deviceId.uri(),
+                Device.Type.SWITCH,
+                manufacturer, hwVersion,
+                swVersion, serialNumber,
+                cid,
+                annotations);
     }
 
     private void deviceAdded(RestSBDevice restSBDev) {
@@ -272,6 +346,7 @@
             }
         } else {
             DeviceId deviceId = restSBDev.deviceId();
+
             if (driver != null && driver.hasBehaviour(DevicesDiscovery.class)) {
                 restSBDev.setActive(true);
                 DevicesDiscovery devicesDiscovery = devicesDiscovery(restSBDev, driver);
@@ -285,23 +360,10 @@
                     providerService.updatePorts(deviceId, deviceDiscovery.discoverPortDetails());
                 }
             } else {
-                ChassisId cid = new ChassisId();
-                String ipAddress = restSBDev.ip().toString();
-                SparseAnnotations annotations = DefaultAnnotations.builder()
-                        .set(IPADDRESS, ipAddress)
-                        .set(AnnotationKeys.PROTOCOL, REST.toUpperCase())
-                        .build();
-                DeviceDescription deviceDescription = new DefaultDeviceDescription(
-                        deviceId.uri(),
-                        Device.Type.SWITCH,
-                        UNKNOWN, UNKNOWN,
-                        UNKNOWN, UNKNOWN,
-                        cid,
-                        annotations);
+                DeviceDescription deviceDescription = getDesc(restSBDev);
                 restSBDev.setActive(true);
                 providerService.deviceConnected(deviceId, deviceDescription);
             }
-
             checkAndUpdateDevice(deviceId);
         }
     }
@@ -371,6 +433,7 @@
 
     private void deviceRemoved(DeviceId deviceId) {
         checkNotNull(deviceId, ISNOTNULL);
+        log.debug("Device removed called for {}", deviceId);
         providerService.deviceDisconnected(deviceId);
         controller.getProxiedDevices(deviceId).forEach(device -> {
             controller.removeProxiedDevice(device);
@@ -383,6 +446,8 @@
     private void createAndConnectDevices() {
         Set<DeviceId> deviceSubjects =
                 netCfgService.getSubjects(DeviceId.class, RestDeviceConfig.class);
+        log.debug("Connecting and configuring devices with received configuration:{}",
+                deviceSubjects);
         connectDevices(deviceSubjects.stream()
                 .filter(deviceId -> deviceService.getDevice(deviceId) == null)
                 .map(deviceId -> {
@@ -510,10 +575,14 @@
 
             ExecutorService bg = SharedExecutors.getSingleThreadExecutor();
             if (event.type() == CONFIG_REMOVED) {
+                log.debug("Config {} event for rest device provider for {}",
+                        event.type(), event.prevConfig().get().subject());
                 DeviceId did = (DeviceId) event.subject();
                 bg.execute(() -> deviceRemoved(did));
             } else {
-                // CONFIG_ADDED or CONFIG_UPDATED
+                //CONFIG_ADDED or CONFIG_UPDATED
+                log.debug("Config {} event for rest device provider for {}",
+                        event.type(), event.config().get().subject());
                 RestDeviceConfig cfg = (RestDeviceConfig) event.config().get();
                 RestSBDevice restSBDevice = toInactiveRestSBDevice(cfg);
                 bg.execute(() -> connectDevice(restSBDevice));
diff --git a/web/api/src/main/java/org/onosproject/rest/resources/MetersWebResource.java b/web/api/src/main/java/org/onosproject/rest/resources/MetersWebResource.java
index 6fbe1b3..814b351 100644
--- a/web/api/src/main/java/org/onosproject/rest/resources/MetersWebResource.java
+++ b/web/api/src/main/java/org/onosproject/rest/resources/MetersWebResource.java
@@ -183,7 +183,11 @@
         final Meter tmpMeter = meterService.getMeter(did, mid);
         if (tmpMeter != null) {
             final MeterRequest meterRequest = meterToMeterRequest(tmpMeter, "REMOVE");
-            meterService.withdraw(meterRequest, tmpMeter.id());
+            if (meterRequest != null) {
+                meterService.withdraw(meterRequest, tmpMeter.id());
+            }
+        } else {
+            log.warn("Meter {}, is not present", tmpMeter);
         }
         return Response.noContent().build();
     }