Add support for all tables wildcard read in P4Runtime

This is required for targets that are not P4RT-compliant
and do not support table-specific wildcard reads.
The all tables wildcard read are activated via
tableWildcardReads driver property.

Change-Id: I675e6f876648ad7634ea0a13ecf44aa366739d3f
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeDriverProperties.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeDriverProperties.java
index ebc6eda..224e065 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeDriverProperties.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeDriverProperties.java
@@ -51,4 +51,9 @@
     // True if target supports reading and writing table entries.
     public static final String SUPPORT_DEFAULT_TABLE_ENTRY = "supportDefaultTableEntry";
     public static final boolean DEFAULT_SUPPORT_DEFAULT_TABLE_ENTRY = true;
+
+    // If true we read table entries from all tables with a single wildcard read.
+    // Otherwise, we submit a read request with wildcard read on a table basis.
+    public static final String TABLE_WILCARD_READS = "tableWildcardReads";
+    public static final boolean DEFAULT_TABLE_WILCARD_READS = false;
 }
\ No newline at end of file
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 e8204a6..2cb5b68 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
@@ -58,17 +58,20 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.locks.Lock;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DEFAULT_DELETE_BEFORE_UPDATE;
 import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DEFAULT_READ_COUNTERS_WITH_TABLE_ENTRIES;
 import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DEFAULT_READ_FROM_MIRROR;
 import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DEFAULT_SUPPORT_DEFAULT_TABLE_ENTRY;
 import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DEFAULT_SUPPORT_TABLE_COUNTERS;
+import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DEFAULT_TABLE_WILCARD_READS;
 import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DELETE_BEFORE_UPDATE;
 import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.READ_COUNTERS_WITH_TABLE_ENTRIES;
 import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.READ_FROM_MIRROR;
 import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.SUPPORT_DEFAULT_TABLE_ENTRY;
 import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.SUPPORT_TABLE_COUNTERS;
+import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.TABLE_WILCARD_READS;
 import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.APPLY;
 import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.REMOVE;
 import static org.onosproject.net.flow.FlowEntry.FlowEntryState.ADDED;
@@ -181,28 +184,51 @@
     private Collection<PiTableEntry> getAllTableEntriesFromDevice() {
         final P4RuntimeReadClient.ReadRequest request = client.read(
                 p4DeviceId, pipeconf);
-        // Read entries from all non-constant tables, including default ones.
-        pipelineModel.tables().stream()
-                .filter(t -> !t.isConstantTable())
-                .forEach(t -> {
-                    request.tableEntries(t.id());
-                    if (driverBoolProperty(SUPPORT_DEFAULT_TABLE_ENTRY,
-                                           DEFAULT_SUPPORT_DEFAULT_TABLE_ENTRY) &&
-                            t.constDefaultAction().isEmpty()) {
-                        request.defaultTableEntry(t.id());
-                    }
-                });
+        final boolean supportDefaultTableEntry = driverBoolProperty(
+                SUPPORT_DEFAULT_TABLE_ENTRY, DEFAULT_SUPPORT_DEFAULT_TABLE_ENTRY);
+        final boolean tableWildcardReads = driverBoolProperty(
+                TABLE_WILCARD_READS, DEFAULT_TABLE_WILCARD_READS);
+        if (!tableWildcardReads) {
+            // Read entries from all non-constant tables, including default ones.
+            pipelineModel.tables().stream()
+                    .filter(t -> !t.isConstantTable())
+                    .forEach(t -> {
+                        request.tableEntries(t.id());
+                        if (supportDefaultTableEntry && t.constDefaultAction().isEmpty()) {
+                            request.defaultTableEntry(t.id());
+                        }
+                    });
+        } else {
+            request.allTableEntries();
+            if (supportDefaultTableEntry) {
+                request.allDefaultTableEntries();
+            }
+        }
         final P4RuntimeReadClient.ReadResponse response = request.submitSync();
         if (!response.isSuccess()) {
             return null;
         }
-        return response.all(PiTableEntry.class).stream()
+        Stream<PiTableEntry> piTableEntries = response.all(PiTableEntry.class).stream()
                 // Device implementation might return duplicate entries. For
                 // example if reading only default ones is not supported and
                 // non-default entries are returned, by using distinct() we
                 // are robust against that possibility.
-                .distinct()
-                .collect(Collectors.toList());
+                .distinct();
+        if (tableWildcardReads) {
+            // When doing a wildcard read on all tables, the device might
+            // return table entries of tables not present in the pipeline
+            // model or constant (default) entries that are filtered out.
+            piTableEntries = piTableEntries.filter(te -> {
+                var piTableModel = pipelineModel.table(te.table());
+                    if (piTableModel.isEmpty() ||
+                            piTableModel.get().isConstantTable() ||
+                            (supportDefaultTableEntry && piTableModel.get().constDefaultAction().isPresent())) {
+                        return false;
+                    }
+                return true;
+            });
+        }
+        return piTableEntries.collect(Collectors.toList());
     }
 
     @Override
diff --git a/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeReadClient.java b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeReadClient.java
index b99aef3..dc1380c 100644
--- a/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeReadClient.java
+++ b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeReadClient.java
@@ -99,6 +99,20 @@
         ReadRequest defaultTableEntry(Iterable<PiTableId> tableIds);
 
         /**
+         * Requests to read all table entries from all tables.
+         *
+         * @return this
+         */
+        ReadRequest allTableEntries();
+
+        /**
+         * Requests to read all default table entries from all tables.
+         *
+         * @return this
+         */
+        ReadRequest allDefaultTableEntries();
+
+        /**
          * Requests to read all action profile groups from the given action
          * profile.
          *
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/ReadRequestImpl.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/ReadRequestImpl.java
index 6858800..9ca268c 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/ReadRequestImpl.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/client/ReadRequestImpl.java
@@ -157,6 +157,28 @@
     }
 
     @Override
+    public P4RuntimeReadClient.ReadRequest allTableEntries() {
+        try {
+            doTableEntry(null, false);
+        } catch (InternalRequestException e) {
+            log.warn("Unable to read entries for all tables from {}: {}",
+                     client.deviceId(), e.getMessage());
+        }
+        return this;
+    }
+
+    @Override
+    public P4RuntimeReadClient.ReadRequest allDefaultTableEntries() {
+        try {
+            doTableEntry(null, true);
+        } catch (InternalRequestException e) {
+            log.warn("Unable to read default entries for all tables from {}: {}",
+                     client.deviceId(), e.getMessage());
+        }
+        return this;
+    }
+
+    @Override
     public P4RuntimeReadClient.ReadRequest actionProfileGroups(PiActionProfileId actionProfileId) {
         try {
             requestMsg.addEntities(
@@ -270,10 +292,16 @@
 
     private void doTableEntry(PiTableId piTableId, boolean defaultEntries)
             throws InternalRequestException {
-        checkNotNull(piTableId);
-        final var builder = P4RuntimeOuterClass.TableEntry.newBuilder()
-                .setTableId(p4TableId(piTableId))
-                .setIsDefaultAction(defaultEntries);
+
+        final var builder = P4RuntimeOuterClass.TableEntry.newBuilder();
+
+        builder.setIsDefaultAction(defaultEntries);
+        if (piTableId == null) {
+            builder.setCounterData(P4RuntimeOuterClass.CounterData.getDefaultInstance());
+            builder.setMeterConfig(P4RuntimeOuterClass.MeterConfig.getDefaultInstance());
+        } else {
+            builder.setTableId(p4TableId(piTableId));
+        }
         if (tableHasCounters(piTableId)) {
             builder.setCounterData(P4RuntimeOuterClass.CounterData
                                            .getDefaultInstance());