Added ability to poll flow counters in BMv2

Also fixed few minor things here and there.

Change-Id: Ib5e6a92de46870f52510cd6fad0cef8da022bb62
diff --git a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2FlowRuleProgrammable.java b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2FlowRuleProgrammable.java
index 3f7a3ec..7d78fe3 100644
--- a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2FlowRuleProgrammable.java
+++ b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2FlowRuleProgrammable.java
@@ -48,12 +48,15 @@
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Date;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 
+import static org.onosproject.net.flow.FlowEntry.FlowEntryState.ADDED;
+
 /**
  * Flow rule programmable device behaviour implementation for BMv2.
  */
@@ -65,7 +68,7 @@
 
     // There's no Bmv2 client method to poll flow entries from the device. Use a local store.
     // FIXME: this information should be distributed across instances of the cluster.
-    private static final ConcurrentMap<Triple<DeviceId, String, Bmv2MatchKey>, Pair<Long, FlowEntry>>
+    private static final ConcurrentMap<Triple<DeviceId, String, Bmv2MatchKey>, Pair<Long, TimestampedFlowRule>>
             ENTRIES_MAP = Maps.newConcurrentMap();
 
     // Cache model objects instead of parsing the JSON each time.
@@ -106,10 +109,28 @@
                 ENTRIES_MAP.forEach((key, value) -> {
                     if (key.getLeft() == deviceId && key.getMiddle() == table.name()
                             && value != null) {
+                        long entryId = value.getKey();
                         // Filter entries_map for this device and table.
-                        if (installedEntryIds.contains(value.getKey())) {
+                        if (installedEntryIds.contains(entryId)) {
                             // Entry is installed.
-                            entryList.add(value.getRight());
+                            long bytes = 0L;
+                            long packets = 0L;
+                            if (table.hasCounters()) {
+                                // Read counter values from device.
+                                try {
+                                    Pair<Long, Long> counterValue = deviceClient.readTableEntryCounter(table.name(),
+                                                                                                       entryId);
+                                    bytes = counterValue.getLeft();
+                                    packets = counterValue.getRight();
+                                } catch (Bmv2RuntimeException e) {
+                                    LOG.warn("Unable to get counter values for entry {} of table {} of device {}: {}",
+                                             entryId, table.name(), deviceId, e.toString());
+                                }
+                            }
+                            TimestampedFlowRule tsRule = value.getRight();
+                            FlowEntry entry = new DefaultFlowEntry(tsRule.rule(), ADDED,
+                                                                   tsRule.lifeInSeconds(), packets, bytes);
+                            entryList.add(entry);
                         } else {
                             // No such entry on device, can remove from local store.
                             ENTRIES_MAP.remove(key);
@@ -183,16 +204,14 @@
                                 // Tentatively delete entry before re-adding.
                                 // It might not exist on device due to inconsistencies.
                                 deviceClient.deleteTableEntry(bmv2Entry.tableName(), entryId);
+                                value = null;
                             } catch (Bmv2RuntimeException e) {
                                 // Silently drop exception as we can probably fix this by re-adding the entry.
                             }
                         }
                         // Add entry.
                         entryId = deviceClient.addTableEntry(bmv2Entry);
-                        // TODO: evaluate flow entry life, bytes and packets
-                        FlowEntry flowEntry = new DefaultFlowEntry(
-                                rule, FlowEntry.FlowEntryState.ADDED, 0, 0, 0);
-                        value = Pair.of(entryId, flowEntry);
+                        value = Pair.of(entryId, new TimestampedFlowRule(rule));
                     } else {
                         // Remove entry
                         if (value == null) {
@@ -258,4 +277,22 @@
     private enum Operation {
         APPLY, REMOVE
     }
+
+    private class TimestampedFlowRule {
+        private final FlowRule rule;
+        private final Date addedDate;
+
+        public TimestampedFlowRule(FlowRule rule) {
+            this.rule = rule;
+            this.addedDate = new Date();
+        }
+
+        public FlowRule rule() {
+            return rule;
+        }
+
+        public long lifeInSeconds() {
+            return (new Date().getTime() - addedDate.getTime()) / 1000;
+        }
+    }
 }
\ No newline at end of file
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelTable.java b/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelTable.java
index 2dc7e3f..618a49e 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelTable.java
+++ b/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/model/Bmv2ModelTable.java
@@ -115,7 +115,7 @@
      *
      * @return a boolean value
      */
-    public boolean hasCunters() {
+    public boolean hasCounters() {
         return hasCounters;
     }
 
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Client.java b/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Client.java
index d34aa9d..9dcc3ce 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Client.java
+++ b/protocols/bmv2/src/main/java/org/onosproject/bmv2/api/runtime/Bmv2Client.java
@@ -16,6 +16,7 @@
 
 package org.onosproject.bmv2.api.runtime;
 
+import org.apache.commons.lang3.tuple.Pair;
 import org.onlab.util.ImmutableByteSequence;
 
 import java.util.Collection;
@@ -132,4 +133,14 @@
      * @throws Bmv2RuntimeException if any error occurs
      */
     String getJsonConfigMd5() throws Bmv2RuntimeException;
+
+    /**
+     * Returns the counter values for a given table and entry.
+     *
+     * @param tableName a table name
+     * @param entryId an entry id
+     * @return a pair of long values, where the left value is the number of bytes and the right the number of packets
+     * @throws Bmv2RuntimeException if any error occurs
+     */
+    Pair<Long, Long> readTableEntryCounter(String tableName, long entryId) throws Bmv2RuntimeException;
 }
diff --git a/protocols/bmv2/src/main/java/org/onosproject/bmv2/ctl/Bmv2ThriftClient.java b/protocols/bmv2/src/main/java/org/onosproject/bmv2/ctl/Bmv2ThriftClient.java
index 66229b0..5e6cbd9 100644
--- a/protocols/bmv2/src/main/java/org/onosproject/bmv2/ctl/Bmv2ThriftClient.java
+++ b/protocols/bmv2/src/main/java/org/onosproject/bmv2/ctl/Bmv2ThriftClient.java
@@ -44,6 +44,7 @@
 import org.onosproject.bmv2.api.runtime.Bmv2ValidMatchParam;
 import org.onosproject.net.DeviceId;
 import org.p4.bmv2.thrift.BmAddEntryOptions;
+import org.p4.bmv2.thrift.BmCounterValue;
 import org.p4.bmv2.thrift.BmMatchParam;
 import org.p4.bmv2.thrift.BmMatchParamExact;
 import org.p4.bmv2.thrift.BmMatchParamLPM;
@@ -477,6 +478,24 @@
     }
 
     @Override
+    public Pair<Long, Long> readTableEntryCounter(String tableName, long entryId) throws Bmv2RuntimeException {
+
+        LOG.debug("Reading table entry counters... > deviceId={}, tableName={}, entryId={}",
+                  deviceId, tableName, entryId);
+
+        try {
+            BmCounterValue counterValue = standardClient.bm_mt_read_counter(CONTEXT_ID, tableName, entryId);
+            LOG.debug("Table entry counters retrieved! > deviceId={}, tableName={}, entryId={}, bytes={}, packets={}",
+                      deviceId, tableName, entryId, counterValue.bytes, counterValue.packets);
+            return Pair.of(counterValue.bytes, counterValue.packets);
+        } catch (TException e) {
+            LOG.debug("Exception while reading table counters: {} > deviceId={}, tableName={}, entryId={}",
+                      e.toString(), deviceId);
+            throw new Bmv2RuntimeException(e.getMessage(), e);
+        }
+    }
+
+    @Override
     public String getJsonConfigMd5() throws Bmv2RuntimeException {
 
         LOG.debug("Getting device config md5... > deviceId={}", deviceId);
diff --git a/protocols/bmv2/src/test/java/org/onosproject/bmv2/api/model/Bmv2ModelTest.java b/protocols/bmv2/src/test/java/org/onosproject/bmv2/api/model/Bmv2ModelTest.java
index 6550ee9..1c51b7e 100644
--- a/protocols/bmv2/src/test/java/org/onosproject/bmv2/api/model/Bmv2ModelTest.java
+++ b/protocols/bmv2/src/test/java/org/onosproject/bmv2/api/model/Bmv2ModelTest.java
@@ -52,7 +52,7 @@
     @Test
     public void testParse() throws Exception {
         Bmv2Model model = Bmv2Model.parse(json);
-        Bmv2Model model2 = Bmv2Model.parse(json);
+        Bmv2Model model2 = Bmv2Model.parse(json2);
 
         new EqualsTester()
                 .addEqualityGroup(model, model2)