ONOS-7001 Support for direct counters

Currently Bmv2 returns UNKNOWN error when reading direct counters.

Change-Id: I834d7b5a8627181c6888500545e1bdbfe9af8dc1
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/DefaultP4Interpreter.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/DefaultP4Interpreter.java
index 6029590..da56bfc 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/DefaultP4Interpreter.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/DefaultP4Interpreter.java
@@ -43,6 +43,8 @@
 import org.onosproject.net.pi.runtime.PiActionId;
 import org.onosproject.net.pi.runtime.PiActionParam;
 import org.onosproject.net.pi.runtime.PiActionParamId;
+import org.onosproject.net.pi.runtime.PiCounterId;
+import org.onosproject.net.pi.runtime.PiCounterType;
 import org.onosproject.net.pi.runtime.PiHeaderFieldId;
 import org.onosproject.net.pi.runtime.PiPacketMetadata;
 import org.onosproject.net.pi.runtime.PiPacketMetadataId;
@@ -74,6 +76,7 @@
     // e.g. in a dedicated onos/pipeconf directory, along with any related P4 source code.
 
     public static final String TABLE0 = "table0";
+    public static final String TABLE0_COUNTER = "table0_counter";
     public static final String SEND_TO_CPU = "send_to_cpu";
     public static final String PORT = "port";
     public static final String DROP = "_drop";
@@ -81,12 +84,17 @@
     public static final String EGRESS_PORT = "egress_port";
     public static final String INGRESS_PORT = "ingress_port";
 
+    private static final PiTableId TABLE0_ID = PiTableId.of(TABLE0);
+
     protected static final PiHeaderFieldId ETH_DST_ID = PiHeaderFieldId.of("ethernet", "dstAddr");
     protected static final PiHeaderFieldId ETH_SRC_ID = PiHeaderFieldId.of("ethernet", "srcAddr");
     protected static final PiHeaderFieldId ETH_TYPE_ID = PiHeaderFieldId.of("ethernet", "etherType");
 
     private static final ImmutableBiMap<Integer, PiTableId> TABLE_MAP = ImmutableBiMap.of(
-            0, PiTableId.of(TABLE0));
+            0, TABLE0_ID);
+
+    private static final ImmutableBiMap<PiTableId, PiCounterId> TABLE_COUNTER_MAP = ImmutableBiMap.of(
+            TABLE0_ID, PiCounterId.of(TABLE0_COUNTER, PiCounterType.DIRECT));
 
     private boolean targetAttributesInitialized = false;
 
@@ -189,6 +197,11 @@
     }
 
     @Override
+    public Optional<PiCounterId> mapTableCounter(PiTableId piTableId) {
+        return Optional.ofNullable(TABLE_COUNTER_MAP.get(piTableId));
+    }
+
+    @Override
     public Collection<PiPacketOperation> mapOutboundPacket(OutboundPacket packet)
             throws PiInterpreterException {
         TrafficTreatment treatment = packet.treatment();
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/DefaultP4PortStatisticsDiscovery.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/DefaultP4PortStatisticsDiscovery.java
index 2946304..b127855 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/DefaultP4PortStatisticsDiscovery.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/DefaultP4PortStatisticsDiscovery.java
@@ -24,6 +24,7 @@
 import org.onosproject.net.pi.runtime.PiCounterCellData;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
 import org.onosproject.net.pi.runtime.PiCounterId;
+import org.onosproject.net.pi.runtime.PiIndirectCounterCellId;
 
 import java.util.Collection;
 import java.util.Collections;
@@ -32,6 +33,8 @@
 import java.util.concurrent.ExecutionException;
 import java.util.stream.Collectors;
 
+import static org.onosproject.net.pi.runtime.PiCounterType.INDIRECT;
+
 /**
  * Implementation of a PortStatisticsBehaviour that can be used for any P4 program based on default.p4 (i.e. those
  * under onos/tools/test/p4src).
@@ -39,8 +42,8 @@
 public class DefaultP4PortStatisticsDiscovery extends AbstractP4RuntimeHandlerBehaviour
         implements PortStatisticsDiscovery {
 
-    private static final PiCounterId INGRESS_COUNTER_ID = PiCounterId.of("ingress_port_counter");
-    private static final PiCounterId EGRESS_COUNTER_ID = PiCounterId.of("egress_port_counter");
+    private static final PiCounterId INGRESS_COUNTER_ID = PiCounterId.of("ingress_port_counter", INDIRECT);
+    private static final PiCounterId EGRESS_COUNTER_ID = PiCounterId.of("egress_port_counter", INDIRECT);
 
     @Override
     public Collection<PortStatistics> discoverPortStatistics() {
@@ -54,14 +57,14 @@
         deviceService.getPorts(deviceId)
                 .forEach(p -> portStatBuilders.put(p.number().toLong(),
                                                    DefaultPortStatistics.builder()
-                                                           .setPort((int) p.number().toLong())
+                                                           .setPort(p.number())
                                                            .setDeviceId(deviceId)));
 
         Set<PiCounterCellId> counterCellIds = Sets.newHashSet();
         portStatBuilders.keySet().forEach(p -> {
             // Counter cell/index = port number.
-            counterCellIds.add(PiCounterCellId.of(INGRESS_COUNTER_ID, p));
-            counterCellIds.add(PiCounterCellId.of(EGRESS_COUNTER_ID, p));
+            counterCellIds.add(PiIndirectCounterCellId.of(INGRESS_COUNTER_ID, p));
+            counterCellIds.add(PiIndirectCounterCellId.of(EGRESS_COUNTER_ID, p));
         });
 
         Collection<PiCounterCellData> counterEntryResponse;
@@ -73,20 +76,25 @@
             return Collections.emptyList();
         }
 
-        counterEntryResponse.forEach(counterEntry -> {
-            if (!portStatBuilders.containsKey(counterEntry.cellId().index())) {
-                log.warn("Unrecognized counter index {}, skipping", counterEntry);
+        counterEntryResponse.forEach(counterData -> {
+            if (counterData.cellId().type() != INDIRECT) {
+                log.warn("Invalid counter data type {}, skipping", counterData.cellId().type());
                 return;
             }
-            DefaultPortStatistics.Builder statsBuilder = portStatBuilders.get(counterEntry.cellId().index());
-            if (counterEntry.cellId().counterId().equals(INGRESS_COUNTER_ID)) {
-                statsBuilder.setPacketsReceived(counterEntry.packets());
-                statsBuilder.setBytesReceived(counterEntry.bytes());
-            } else if (counterEntry.cellId().counterId().equals(EGRESS_COUNTER_ID)) {
-                statsBuilder.setPacketsSent(counterEntry.packets());
-                statsBuilder.setBytesSent(counterEntry.bytes());
+            PiIndirectCounterCellId indCellId = (PiIndirectCounterCellId) counterData.cellId();
+            if (!portStatBuilders.containsKey(indCellId.index())) {
+                log.warn("Unrecognized counter index {}, skipping", counterData);
+                return;
+            }
+            DefaultPortStatistics.Builder statsBuilder = portStatBuilders.get(indCellId.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());
             } else {
-                log.warn("Unrecognized counter ID {}, skipping", counterEntry);
+                log.warn("Unrecognized counter ID {}, skipping", counterData);
             }
         });
 
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 ad9329a..50b808e 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
@@ -19,6 +19,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import io.grpc.StatusRuntimeException;
 import org.onosproject.net.flow.DefaultFlowEntry;
 import org.onosproject.net.flow.FlowEntry;
 import org.onosproject.net.flow.FlowRule;
@@ -26,6 +27,10 @@
 import org.onosproject.net.pi.model.PiPipelineInterpreter;
 import org.onosproject.net.pi.model.PiPipelineModel;
 import org.onosproject.net.pi.model.PiTableModel;
+import org.onosproject.net.pi.runtime.PiCounterCellData;
+import org.onosproject.net.pi.runtime.PiCounterCellId;
+import org.onosproject.net.pi.runtime.PiCounterId;
+import org.onosproject.net.pi.runtime.PiDirectCounterCellId;
 import org.onosproject.net.pi.runtime.PiFlowRuleTranslationService;
 import org.onosproject.net.pi.runtime.PiTableEntry;
 import org.onosproject.net.pi.runtime.PiTableId;
@@ -36,6 +41,8 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.locks.Lock;
@@ -72,6 +79,13 @@
     // TODO: can remove this check as soon as the BMv2 bug when reading ECMP entries is fixed.
     private boolean ignoreDeviceWhenGet = true;
 
+    /*
+    If true, we read all direct counters of a table with one request. Otherwise, send as many request as the number of
+    table entries.
+     */
+    // TODO: set to true as soon as the feature is implemented in P4Runtime.
+    private boolean readAllDirectCounters = false;
+
     // Needed to synchronize operations over the same table entry.
     private static final ConcurrentMap<P4RuntimeTableEntryReference, Lock> ENTRY_LOCKS = Maps.newConcurrentMap();
 
@@ -132,33 +146,70 @@
 
             Collection<PiTableEntry> installedEntries;
             try {
+                // TODO: optimize by dumping entries and counters in parallel, from ALL tables with the same request.
                 installedEntries = client.dumpTable(piTableId, pipeconf).get();
             } catch (InterruptedException | ExecutionException e) {
-                log.error("Exception while dumping table {} of {}", piTableId, deviceId, e);
+                if (!(e.getCause() instanceof StatusRuntimeException)) {
+                    // gRPC errors are logged in the client.
+                    log.error("Exception while dumping table {} of {}", piTableId, deviceId, e);
+                }
                 return Collections.emptyList();
             }
 
+            Map<PiTableEntry, PiCounterCellData> counterCellMap;
+            try {
+                if (interpreter.mapTableCounter(piTableId).isPresent()) {
+                    PiCounterId piCounterId = interpreter.mapTableCounter(piTableId).get();
+                    Collection<PiCounterCellData> cellDatas;
+                    if (readAllDirectCounters) {
+                        cellDatas = client.readAllCounterCells(Collections.singleton(piCounterId), pipeconf).get();
+                    } else {
+                        Set<PiCounterCellId> cellIds = installedEntries.stream()
+                                .map(entry -> PiDirectCounterCellId.of(piCounterId, entry))
+                                .collect(Collectors.toSet());
+                        cellDatas = client.readCounterCells(cellIds, pipeconf).get();
+                    }
+                    counterCellMap = cellDatas.stream()
+                            .collect(Collectors.toMap(c -> ((PiDirectCounterCellId) c.cellId()).tableEntry(), c -> c));
+                } else {
+                    counterCellMap = Collections.emptyMap();
+                }
+                installedEntries = client.dumpTable(piTableId, pipeconf).get();
+            } catch (InterruptedException | ExecutionException e) {
+                if (!(e.getCause() instanceof StatusRuntimeException)) {
+                    // gRPC errors are logged in the client.
+                    log.error("Exception while reading counters of table {} of {}", piTableId, deviceId, e);
+                }
+                counterCellMap = Collections.emptyMap();
+            }
+
             for (PiTableEntry installedEntry : installedEntries) {
 
-                P4RuntimeTableEntryReference entryRef = new P4RuntimeTableEntryReference(deviceId, piTableId,
+                P4RuntimeTableEntryReference entryRef = new P4RuntimeTableEntryReference(deviceId,
+                                                                                         piTableId,
                                                                                          installedEntry.matchKey());
 
-                P4RuntimeFlowRuleWrapper frWrapper = ENTRY_STORE.get(entryRef);
-
-
-                if (frWrapper == null) {
+                if (!ENTRY_STORE.containsKey(entryRef)) {
                     // Inconsistent entry
                     inconsistentEntries.add(installedEntry);
                     continue; // next one.
                 }
 
-                // TODO: implement table entry counter retrieval.
+                P4RuntimeFlowRuleWrapper frWrapper = ENTRY_STORE.get(entryRef);
+
                 long bytes = 0L;
                 long packets = 0L;
+                if (counterCellMap.containsKey(installedEntry)) {
+                    PiCounterCellData counterCellData = counterCellMap.get(installedEntry);
+                    bytes = counterCellData.bytes();
+                    packets = counterCellData.packets();
+                }
 
-                FlowEntry entry = new DefaultFlowEntry(frWrapper.rule(), ADDED, frWrapper.lifeInSeconds(),
-                                                       packets, bytes);
-                resultBuilder.add(entry);
+                resultBuilder.add(new DefaultFlowEntry(frWrapper.rule(),
+                                                       ADDED,
+                                                       frWrapper.lifeInSeconds(),
+                                                       packets,
+                                                       bytes));
             }
         }