Do not insert or delete default action entries in P4Runtime

Spec says:
the default entry for a table is always set. It can be set at
compile-time by the P4 programmer - or defaults to NoAction (which is a
no-op) otherwise - and assuming it is not declared as const, can be
modified by the P4Runtime client. Because the default entry is always
set, we do not allow INSERT and DELETE updates on the default entry and
the P4Runtime server must return an INVALID_ARGUMENT error code if the
client attempts one.

With this patch we convert insert or delete operations into modify ones
(unless specified by a driver property, to support non-compliant devices).
For delete, we use the interpreter to suggest a default action that is
the same as the one when the pipeline was originally deployed.

Also, we introduce the capability of synchronizing the device mirror
with the device state.

Change-Id: I3758fc11780eb0f1cf4ed5a295bd98b54b182e29
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiPipelineInterpreter.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiPipelineInterpreter.java
index 4414931..9206f5c 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/model/PiPipelineInterpreter.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiPipelineInterpreter.java
@@ -126,6 +126,18 @@
     }
 
     /**
+     * If the given table allows for mutable default actions, this method
+     * returns an action instance to be used when ONOS tries to remove a
+     * different default action previously set.
+     *
+     * @param tableId table ID
+     * @return optional default action
+     */
+    default Optional<PiAction> getOriginalDefaultAction(PiTableId tableId) {
+        return Optional.empty();
+    }
+
+    /**
      * Signals that an error was encountered while executing the interpreter.
      */
     @Beta
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiTableModel.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiTableModel.java
index ba66c10..f7ba1bd 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/model/PiTableModel.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiTableModel.java
@@ -42,8 +42,9 @@
     PiTableType tableType();
 
     /**
-     * Returns the model of the action profile that implements this table. Meaningful if this table is of type {@link
-     * PiTableType#INDIRECT}, otherwise returns null.
+     * Returns the model of the action profile that implements this table.
+     * Meaningful if this table is of type {@link PiTableType#INDIRECT},
+     * otherwise returns null.
      *
      * @return action profile ID
      */
@@ -92,16 +93,19 @@
     Collection<PiActionModel> actions();
 
     /**
-     * Returns the model of the default action associated with this table, if any.
+     * Returns the model of the constant default action associated with this
+     * table, if any.
      *
      * @return optional default action model
      */
-    Optional<PiActionModel> defaultAction();
+    Optional<PiActionModel> constDefaultAction();
 
     /**
-     * Returns true if the default action has mutable parameters that can be changed at runtime, false otherwise.
+     * Returns true if the default action has mutable parameters that can be
+     * changed at runtime, false otherwise.
      *
-     * @return true if the default action has mutable parameters, false otherwise
+     * @return true if the default action has mutable parameters, false
+     * otherwise
      */
     boolean hasDefaultMutableParams();
 
@@ -114,8 +118,8 @@
     boolean isConstantTable();
 
     /**
-     * Returns the action model associated with the given ID, if present. If not present, it means that this table does
-     * not support such an action.
+     * Returns the action model associated with the given ID, if present. If not
+     * present, it means that this table does not support such an action.
      *
      * @param actionId action ID
      * @return optional action model
@@ -123,8 +127,9 @@
     Optional<PiActionModel> action(PiActionId actionId);
 
     /**
-     * Returns the match field model associated with the given ID, if present. If not present, it means that this table
-     * does not support such a match field.
+     * Returns the match field model associated with the given ID, if present.
+     * If not present, it means that this table does not support such a match
+     * field.
      *
      * @param matchFieldId match field ID
      * @return optional match field model
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 7b8797a..04b2528 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
@@ -38,15 +38,18 @@
     private final PiTableId tableId;
     private final PiMatchKey matchKey;
     private final PiTableAction tableAction;
+    private final boolean isDefaultAction;
     private final long cookie;
     private final int priority;
     private final double timeout;
 
     private PiTableEntry(PiTableId tableId, PiMatchKey matchKey,
-                         PiTableAction tableAction, long cookie, int priority, double timeout) {
+                         PiTableAction tableAction, boolean isDefaultAction,
+                         long cookie, int priority, double timeout) {
         this.tableId = tableId;
         this.matchKey = matchKey;
         this.tableAction = tableAction;
+        this.isDefaultAction = isDefaultAction;
         this.cookie = cookie;
         this.priority = priority;
         this.timeout = timeout;
@@ -63,6 +66,9 @@
 
     /**
      * Returns the match key of this table entry.
+     * <p>
+     * If {@link #isDefaultAction()} is {@code true} this method returns the
+     * empty match key ({@link PiMatchKey#EMPTY}).
      *
      * @return match key
      */
@@ -80,6 +86,16 @@
     }
 
     /**
+     * Returns true if this table entry contains the default action for this
+     * table, a.k.a. table-miss entry, false otherwise.
+     *
+     * @return boolean
+     */
+    public boolean isDefaultAction() {
+        return isDefaultAction;
+    }
+
+    /**
      * Returns the cookie of this table entry.
      *
      * @return cookie
@@ -89,8 +105,8 @@
     }
 
     /**
-     * Returns the priority of this table entry, if present. If the priority value is not present, then this table entry
-     * has no explicit priority.
+     * Returns the priority of this table entry, if present. If the priority
+     * value is not present, then this table entry has no explicit priority.
      *
      * @return optional priority
      */
@@ -99,8 +115,9 @@
     }
 
     /**
-     * Returns the timeout in seconds of this table entry, if present. If the timeout value is not present, then this
-     * table entry is meant to be permanent.
+     * Returns the timeout in seconds of this table entry, if present. If the
+     * timeout value is not present, then this table entry is meant to be
+     * permanent.
      *
      * @return optional timeout value in seconds
      */
@@ -121,25 +138,42 @@
                 Double.compare(that.timeout, timeout) == 0 &&
                 Objects.equal(tableId, that.tableId) &&
                 Objects.equal(matchKey, that.matchKey) &&
+                Objects.equal(isDefaultAction, that.isDefaultAction) &&
                 Objects.equal(tableAction, that.tableAction);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hashCode(tableId, matchKey, tableAction, priority, timeout);
+        return Objects.hashCode(tableId, matchKey, isDefaultAction, tableAction,
+                                priority, timeout);
     }
 
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(this)
                 .add("tableId", tableId)
-                .add("matchKey", matchKey)
-                .add("tableAction", tableAction)
+                .add("matchKey", isDefaultAction ? "DEFAULT-ACTION" : matchKey)
+                .add("tableAction", tableActionToString(tableAction))
                 .add("priority", priority == NO_PRIORITY ? "N/A" : String.valueOf(priority))
                 .add("timeout", timeout == NO_TIMEOUT ? "PERMANENT" : String.valueOf(timeout))
                 .toString();
     }
 
+    private String tableActionToString(PiTableAction tableAction) {
+        if (tableAction == null) {
+            return "null";
+        }
+        switch (tableAction.type()) {
+            case ACTION_GROUP_ID:
+                return "GROUP:" + ((PiActionGroupId) tableAction).id();
+            case GROUP_MEMBER_ID:
+                return "GROUP_MEMBER:" + ((PiActionGroupMemberId) tableAction).id();
+            case ACTION:
+            default:
+                return tableAction.toString();
+        }
+    }
+
     /**
      * Returns a table entry builder.
      *
@@ -244,7 +278,9 @@
         public PiTableEntry build() {
             checkNotNull(tableId);
             checkNotNull(matchKey);
-            return new PiTableEntry(tableId, matchKey, tableAction, cookie, priority, timeout);
+            final boolean isDefaultAction = matchKey.equals(PiMatchKey.EMPTY);
+            return new PiTableEntry(tableId, matchKey, tableAction,
+                                    isDefaultAction, cookie, priority, timeout);
         }
     }
 }
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 4175489..85a87fe 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
@@ -18,6 +18,7 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
 import com.google.common.util.concurrent.Striped;
 import org.onlab.util.SharedExecutors;
 import org.onosproject.drivers.p4runtime.mirror.P4RuntimeTableMirror;
@@ -26,8 +27,10 @@
 import org.onosproject.net.flow.FlowEntry;
 import org.onosproject.net.flow.FlowRule;
 import org.onosproject.net.flow.FlowRuleProgrammable;
+import org.onosproject.net.pi.model.PiPipelineInterpreter;
 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.PiCounterCellData;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
 import org.onosproject.net.pi.runtime.PiTableEntry;
@@ -47,6 +50,7 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.locks.Lock;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import static com.google.common.collect.Lists.newArrayList;
 import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.APPLY;
@@ -91,6 +95,12 @@
     // FIXME: set to true as soon as the feature is implemented in P4Runtime.
     private static final boolean DEFAULT_READ_ALL_DIRECT_COUNTERS = false;
 
+    // For default entries, P4Runtime mandates that only MODIFY messages are
+    // allowed. If true, treats default entries as normal table entries,
+    // e.g. inserting them first.
+    private static final String TABLE_DEFAULT_AS_ENTRY = "tableDefaultAsEntry";
+    private static final boolean DEFAULT_TABLE_DEFAULT_AS_ENTRY = false;
+
     // Needed to synchronize operations over the same table entry.
     private static final Striped<Lock> ENTRY_LOCKS = Striped.lock(30);
 
@@ -125,37 +135,36 @@
         final ImmutableList.Builder<FlowEntry> result = ImmutableList.builder();
         final List<PiTableEntry> inconsistentEntries = Lists.newArrayList();
 
-        // Read table entries.
-        // TODO: ONOS-7596 read counters with table entries
-        final Collection<PiTableEntry> installedEntries = getFutureWithDeadline(
-                client.dumpAllTables(pipeconf), "dumping all tables",
-                Collections.emptyList())
-                // Filter out entries from constant table.
-                .stream()
+        // Read table entries, including default ones.
+        final Collection<PiTableEntry> deviceEntries = Stream.concat(
+                streamEntries(), streamDefaultEntries())
+                // Ignore entries from constant tables.
                 .filter(e -> !tableIsConstant(e.table()))
                 .collect(Collectors.toList());
 
-        if (installedEntries.isEmpty()) {
+        if (deviceEntries.isEmpty()) {
             return Collections.emptyList();
         }
 
-        // Read table direct counters (if any).
+        // Synchronize mirror with the device state.
+        syncMirror(deviceEntries);
+        // Read table direct counters for non default-entries (if any).
+        // TODO: ONOS-7596 read counters with table entries
         final Map<PiTableEntry, PiCounterCellData> counterCellMap =
-                readEntryCounters(installedEntries);
-
+                readEntryCounters(deviceEntries);
         // Forge flow entries with counter values.
-        for (PiTableEntry installedEntry : installedEntries) {
-
+        for (PiTableEntry entry : deviceEntries) {
             final FlowEntry flowEntry = forgeFlowEntry(
-                    installedEntry, counterCellMap.get(installedEntry));
-
+                    entry, counterCellMap.get(entry));
             if (flowEntry == null) {
                 // Entry is on device but unknown to translation service or
                 // device mirror. Inconsistent. Mark for removal.
                 // TODO: make this behaviour configurable
                 // In some cases it's fine for the device to have rules
-                // that were not installed by us.
-                inconsistentEntries.add(installedEntry);
+                // that were not installed by us, e.g. original default entry.
+                if (!isOriginalDefaultEntry(entry)) {
+                    inconsistentEntries.add(entry);
+                }
             } else {
                 result.add(flowEntry);
             }
@@ -170,6 +179,34 @@
         return result.build();
     }
 
+    private Stream<PiTableEntry> streamEntries() {
+        return getFutureWithDeadline(
+                client.dumpAllTables(pipeconf), "dumping all tables",
+                Collections.emptyList())
+                .stream();
+    }
+
+    private Stream<PiTableEntry> streamDefaultEntries() {
+        // Ignore tables with constant default action.
+        final Set<PiTableId> defaultTables = pipelineModel.tables()
+                .stream()
+                .filter(table -> !table.constDefaultAction().isPresent())
+                .map(PiTableModel::id)
+                .collect(Collectors.toSet());
+        return defaultTables.isEmpty() ? Stream.empty()
+                : getFutureWithDeadline(
+                client.dumpTables(defaultTables, true, pipeconf),
+                "dumping default table entries",
+                Collections.emptyList())
+                .stream();
+    }
+
+    private void syncMirror(Collection<PiTableEntry> entries) {
+        Map<PiTableEntryHandle, PiTableEntry> handleMap = Maps.newHashMap();
+        entries.forEach(e -> handleMap.put(PiTableEntryHandle.of(deviceId, e), e));
+        tableMirror.sync(deviceId, handleMap);
+    }
+
     @Override
     public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
         return processFlowRules(rules, APPLY);
@@ -234,7 +271,7 @@
     private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules,
                                                   Operation driverOperation) {
 
-        if (!setupBehaviour()) {
+        if (!setupBehaviour() || rules.isEmpty()) {
             return Collections.emptyList();
         }
 
@@ -277,43 +314,66 @@
      * Applies the given entry to the device, and returns true if the operation
      * was successful, false otherwise.
      */
-    private boolean applyEntry(PiTableEntryHandle handle,
+    private boolean applyEntry(final PiTableEntryHandle handle,
                                PiTableEntry piEntryToApply,
-                               FlowRule ruleToApply,
-                               Operation driverOperation) {
+                               final FlowRule ruleToApply,
+                               final Operation driverOperation) {
         // Depending on the driver operation, and if a matching rule exists on
         // the device, decide which P4 Runtime write operation to perform for
         // this entry.
         final TimedEntry<PiTableEntry> piEntryOnDevice = tableMirror.get(handle);
         final WriteOperationType p4Operation;
+        final WriteOperationType storeOperation;
+
+        final boolean defaultAsEntry = driverBoolProperty(
+                TABLE_DEFAULT_AS_ENTRY, DEFAULT_TABLE_DEFAULT_AS_ENTRY);
+        final boolean ignoreSameEntryUpdate = driverBoolProperty(
+                IGNORE_SAME_ENTRY_UPDATE, DEFAULT_IGNORE_SAME_ENTRY_UPDATE);
+        final boolean deleteBeforeUpdate = driverBoolProperty(
+                DELETE_BEFORE_UPDATE, DEFAULT_DELETE_BEFORE_UPDATE);
         if (driverOperation == APPLY) {
             if (piEntryOnDevice == null) {
-                // Entry is first-timer.
-                p4Operation = INSERT;
+                // Entry is first-timer, INSERT or MODIFY if default action.
+                p4Operation = !piEntryToApply.isDefaultAction() || defaultAsEntry
+                        ? INSERT : MODIFY;
+                storeOperation = p4Operation;
             } else {
-                if (driverBoolProperty(IGNORE_SAME_ENTRY_UPDATE,
-                                       DEFAULT_IGNORE_SAME_ENTRY_UPDATE)
-                        && piEntryToApply.action().equals(piEntryOnDevice.entry().action())) {
+                if (ignoreSameEntryUpdate &&
+                        piEntryToApply.action().equals(piEntryOnDevice.entry().action())) {
                     log.debug("Ignoring re-apply of existing entry: {}", piEntryToApply);
                     p4Operation = null;
-                } else if (driverBoolProperty(DELETE_BEFORE_UPDATE,
-                                              DEFAULT_DELETE_BEFORE_UPDATE)) {
+                } else if (deleteBeforeUpdate && !piEntryToApply.isDefaultAction()) {
                     // Some devices return error when updating existing
                     // entries. If requested, remove entry before
-                    // re-inserting the modified one.
+                    // re-inserting the modified one, except the default action
+                    // entry, that cannot be removed.
                     applyEntry(handle, piEntryOnDevice.entry(), null, REMOVE);
                     p4Operation = INSERT;
                 } else {
                     p4Operation = MODIFY;
                 }
+                storeOperation = p4Operation;
             }
         } else {
-            p4Operation = DELETE;
+            if (piEntryToApply.isDefaultAction()) {
+                // Cannot remove default action. Instead we should use the
+                // original defined by the interpreter (if any).
+                piEntryToApply = getOriginalDefaultEntry(piEntryToApply.table());
+                if (piEntryToApply == null) {
+                    return false;
+                }
+                p4Operation = MODIFY;
+            } else {
+                p4Operation = DELETE;
+            }
+            // Still want to delete the default entry from the mirror and
+            // translation store.
+            storeOperation = DELETE;
         }
 
         if (p4Operation != null) {
             if (writeEntry(piEntryToApply, p4Operation)) {
-                updateStores(handle, piEntryToApply, ruleToApply, p4Operation);
+                updateStores(handle, piEntryToApply, ruleToApply, storeOperation);
                 return true;
             } else {
                 return false;
@@ -324,6 +384,34 @@
         }
     }
 
+    private PiTableEntry getOriginalDefaultEntry(PiTableId tableId) {
+        final PiPipelineInterpreter interpreter = getInterpreter();
+        if (interpreter == null) {
+            log.warn("Missing interpreter for {}, cannot get default action",
+                     deviceId);
+            return null;
+        }
+        if (!interpreter.getOriginalDefaultAction(tableId).isPresent()) {
+            log.warn("Interpreter of {} doesn't define a default action for " +
+                             "table {}, cannot produce default action entry",
+                     deviceId, tableId);
+            return null;
+        }
+        return PiTableEntry.builder()
+                .forTable(tableId)
+                .withAction(interpreter.getOriginalDefaultAction(tableId).get())
+                .build();
+    }
+
+    private boolean isOriginalDefaultEntry(PiTableEntry entry) {
+        if (!entry.isDefaultAction()) {
+            return false;
+        }
+        final PiTableEntry originalDefaultEntry = getOriginalDefaultEntry(entry.table());
+        return originalDefaultEntry != null &&
+                originalDefaultEntry.action().equals(entry.action());
+    }
+
     /**
      * Performs a write operation on the device.
      */
@@ -331,17 +419,9 @@
                                WriteOperationType p4Operation) {
         final CompletableFuture<Boolean> future = client.writeTableEntries(
                 newArrayList(entry), p4Operation, pipeconf);
-        final Boolean success = getFutureWithDeadline(
-                future, "performing table " + p4Operation.name(), null);
-        if (success == null) {
-            // Error logged by getFutureWithDeadline();
-            return false;
-        }
-        if (!success) {
-            log.warn("Unable to {} table entry in {}: {}",
-                     p4Operation.name(), deviceId, entry);
-        }
-        return success;
+        // If false, errors logged by internal calls.
+        return getFutureWithDeadline(
+                future, "performing table " + p4Operation.name(), false);
     }
 
     private void updateStores(PiTableEntryHandle handle,
@@ -380,6 +460,7 @@
             cellDatas = Collections.emptyList();
         } else {
             Set<PiCounterCellId> cellIds = tableEntries.stream()
+                    .filter(e -> !e.isDefaultAction())
                     .filter(e -> tableHasCounter(e.table()))
                     .map(PiCounterCellId::ofDirect)
                     .collect(Collectors.toSet());
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/AbstractDistributedP4RuntimeMirror.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/AbstractDistributedP4RuntimeMirror.java
index b2da5fc..c5eb4c0 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/AbstractDistributedP4RuntimeMirror.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/AbstractDistributedP4RuntimeMirror.java
@@ -37,6 +37,8 @@
 
 import java.util.Collection;
 import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
 
 import static com.google.common.base.Preconditions.checkNotNull;
@@ -131,11 +133,49 @@
         mirrorMap.remove(handle);
     }
 
-    private void removeAll(DeviceId deviceId) {
+    @Override
+    public void sync(DeviceId deviceId, Map<H, E> deviceState) {
         checkNotNull(deviceId);
-        Collection<H> handles = mirrorMap.keySet().stream()
+        Set<Map.Entry<H, TimedEntry<E>>> localEntries = getEntriesForDevice(deviceId);
+        final AtomicInteger removeCount = new AtomicInteger(0);
+        final AtomicInteger updateCount = new AtomicInteger(0);
+        localEntries.forEach(e -> {
+            final H handle = e.getKey();
+            final E storedValue = e.getValue().entry();
+            if (!deviceState.containsKey(handle)) {
+                log.debug("Removing mirror entry for {}: {}", deviceId, storedValue);
+                removeCount.incrementAndGet();
+            } else {
+                final E deviceValue = deviceState.get(handle);
+                if (!deviceValue.equals(storedValue)) {
+                    log.debug("Updating mirror entry for {}: {}-->{}",
+                             deviceId, storedValue, deviceValue);
+                    put(handle, deviceValue);
+                    updateCount.incrementAndGet();
+                }
+            }
+        });
+        if (removeCount.get() + updateCount.get() > 0) {
+            log.info("Synchronized mirror entries for {}: {} removed, {} updated",
+                     deviceId, removeCount, updateCount);
+        }
+    }
+
+    private Collection<H> getHandlesForDevice(DeviceId deviceId) {
+        return mirrorMap.keySet().stream()
                 .filter(h -> h.deviceId().equals(deviceId))
                 .collect(Collectors.toList());
+    }
+
+    private Set<Map.Entry<H, TimedEntry<E>>> getEntriesForDevice(DeviceId deviceId) {
+        return mirrorMap.entrySet().stream()
+                .filter(e -> e.getKey().deviceId().equals(deviceId))
+                .collect(Collectors.toSet());
+    }
+
+    private void removeAll(DeviceId deviceId) {
+        checkNotNull(deviceId);
+        Collection<H> handles = getHandlesForDevice(deviceId);
         handles.forEach(mirrorMap::remove);
     }
 
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/P4RuntimeMirror.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/P4RuntimeMirror.java
index ab18c9d..d1c9cdd 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/P4RuntimeMirror.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/mirror/P4RuntimeMirror.java
@@ -22,6 +22,7 @@
 import org.onosproject.net.pi.runtime.PiHandle;
 
 import java.util.Collection;
+import java.util.Map;
 
 /**
  * Service to keep track of the device state for a given class of PI entities.
@@ -71,4 +72,12 @@
      * @param handle handle
      */
     void remove(H handle);
+
+    /**
+     * Synchronizes the state of the given device ID with the given handle map.
+     *
+     * @param deviceId  device ID
+     * @param handleMap handle map
+     */
+    void sync(DeviceId deviceId, Map<H, E> handleMap);
 }
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricInterpreter.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricInterpreter.java
index 3115584..2d5b280 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricInterpreter.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricInterpreter.java
@@ -147,6 +147,14 @@
                     .put(FabricConstants.HDR_ICMP_ICMP_CODE, Criterion.Type.ICMPV6_CODE)
                     .build();
 
+    private static final PiAction NOACTION = PiAction.builder().withId(
+            FabricConstants.NO_ACTION).build();
+
+    private static final ImmutableMap<PiTableId, PiAction> DEFAULT_ACTIONS =
+            ImmutableMap.<PiTableId, PiAction>builder()
+                    .put(FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4, NOACTION)
+                    .build();
+
     @Override
     public Optional<PiMatchFieldId> mapCriterionType(Criterion.Type type) {
         return Optional.ofNullable(CRITERION_MAP.get(type));
@@ -277,6 +285,11 @@
     }
 
     @Override
+    public Optional<PiAction> getOriginalDefaultAction(PiTableId tableId) {
+        return Optional.ofNullable(DEFAULT_ACTIONS.get(tableId));
+    }
+
+    @Override
     public Optional<Integer> mapLogicalPortNumber(PortNumber port) {
         if (!port.equals(CONTROLLER)) {
             return Optional.empty();
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 886a39d..562f0ae 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
@@ -136,15 +136,18 @@
             PiPipeconf pipeconf);
 
     /**
-     * Dumps all entries currently installed in the given table, for the given
-     * pipeconf.
+     * Dumps all entries currently installed in the given tables, for the given
+     * pipeconf. If defaultEntries is set to true only the default action
+     * entries will be returned, otherwise non-default entries will be
+     * considered.
      *
-     * @param tableId  table identifier
+     * @param tableIds  table identifiers
+     * @param defaultEntries true to read default entries, false for non-default
      * @param pipeconf pipeconf currently deployed on the device
      * @return completable future of a collection of table entries
      */
-    CompletableFuture<Collection<PiTableEntry>> dumpTable(
-            PiTableId tableId, PiPipeconf pipeconf);
+    CompletableFuture<Collection<PiTableEntry>> dumpTables(
+            Set<PiTableId> tableIds, boolean defaultEntries, PiPipeconf pipeconf);
 
     /**
      * Dumps entries from all tables, for the given pipeconf.
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 0bd45d3..e020ed5 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
@@ -20,6 +20,7 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
 import com.google.protobuf.ByteString;
 import com.google.protobuf.InvalidProtocolBufferException;
 import io.grpc.Context;
@@ -264,13 +265,15 @@
     }
 
     @Override
-    public CompletableFuture<Collection<PiTableEntry>> dumpTable(PiTableId piTableId, PiPipeconf pipeconf) {
-        return supplyInContext(() -> doDumpTable(piTableId, pipeconf), "dumpTable-" + piTableId);
+    public CompletableFuture<Collection<PiTableEntry>> dumpTables(
+            Set<PiTableId> piTableIds, boolean defaultEntries, PiPipeconf pipeconf) {
+        return supplyInContext(() -> doDumpTables(piTableIds, defaultEntries, pipeconf),
+                               "dumpTables-" + piTableIds.hashCode());
     }
 
     @Override
     public CompletableFuture<Collection<PiTableEntry>> dumpAllTables(PiPipeconf pipeconf) {
-        return supplyInContext(() -> doDumpTable(null, pipeconf), "dumpAllTables");
+        return supplyInContext(() -> doDumpTables(null, false, pipeconf), "dumpAllTables");
     }
 
     @Override
@@ -515,43 +518,53 @@
         return write(updateMsgs, piTableEntries, opType, "table entry");
     }
 
-    private Collection<PiTableEntry> doDumpTable(PiTableId piTableId, PiPipeconf pipeconf) {
+    private Collection<PiTableEntry> doDumpTables(
+            Set<PiTableId> piTableIds, boolean defaultEntries, PiPipeconf pipeconf) {
 
-        log.debug("Dumping table {} from {} (pipeconf {})...", piTableId, deviceId, pipeconf.id());
+        log.debug("Dumping tables {} from {} (pipeconf {})...",
+                  piTableIds, deviceId, pipeconf.id());
 
-        int tableId;
-        if (piTableId == null) {
+        Set<Integer> tableIds = Sets.newHashSet();
+        if (piTableIds == null) {
             // Dump all tables.
-            tableId = 0;
+            tableIds.add(0);
         } else {
             P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
             if (browser == null) {
                 log.warn("Unable to get a P4Info browser for pipeconf {}", pipeconf);
                 return Collections.emptyList();
             }
-            try {
-                tableId = browser.tables().getByName(piTableId.id()).getPreamble().getId();
-            } catch (P4InfoBrowser.NotFoundException e) {
-                log.warn("Unable to dump table: {}", e.getMessage());
-                return Collections.emptyList();
-            }
+            piTableIds.forEach(piTableId -> {
+                try {
+                    tableIds.add(browser.tables().getByName(piTableId.id()).getPreamble().getId());
+                } catch (P4InfoBrowser.NotFoundException e) {
+                    log.warn("Unable to dump table {}: {}", piTableId, e.getMessage());
+                }
+            });
         }
 
-        ReadRequest requestMsg = ReadRequest.newBuilder()
-                .setDeviceId(p4DeviceId)
-                .addEntities(Entity.newBuilder()
-                                     .setTableEntry(TableEntry.newBuilder()
-                                                            .setTableId(tableId)
-                                                            .build())
-                                     .build())
-                .build();
+        if (tableIds.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        ReadRequest.Builder requestMsgBuilder = ReadRequest.newBuilder()
+                .setDeviceId(p4DeviceId);
+        tableIds.forEach(tableId -> requestMsgBuilder.addEntities(
+                Entity.newBuilder()
+                        .setTableEntry(
+                                TableEntry.newBuilder()
+                                        .setTableId(tableId)
+                                        .setIsDefaultAction(defaultEntries)
+                                        .build())
+                        .build())
+                .build());
 
         Iterator<ReadResponse> responses;
         try {
-            responses = blockingStub.read(requestMsg);
+            responses = blockingStub.read(requestMsgBuilder.build());
         } catch (StatusRuntimeException e) {
             checkGrpcException(e);
-            log.warn("Unable to dump table {} from {}: {}", piTableId, deviceId, e.getMessage());
+            log.warn("Unable to dump tables from {}: {}", deviceId, e.getMessage());
             return Collections.emptyList();
         }
 
@@ -564,7 +577,8 @@
                 .map(Entity::getTableEntry)
                 .collect(Collectors.toList());
 
-        log.debug("Retrieved {} entries from table {} on {}...", tableEntryMsgs.size(), piTableId, deviceId);
+        log.debug("Retrieved {} entries from {} tables on {}...",
+                  tableEntryMsgs.size(), tableIds.size(), deviceId);
 
         return TableEntryEncoder.decode(tableEntryMsgs, pipeconf);
     }
@@ -1034,8 +1048,8 @@
         if (errors.isEmpty()) {
             final String description = ex.getStatus().getDescription();
             log.warn("Unable to {} {} {}(s) on {}: {}",
-                 opType.name(), writeEntities.size(), entryType, deviceId,
-                 ex.getStatus().getCode().name(),
+                     opType.name(), writeEntities.size(), entryType, deviceId,
+                     ex.getStatus().getCode().name(),
                      description == null ? "" : " - " + description);
             return;
         }
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 505b4cb..53fcf2a 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
@@ -263,7 +263,9 @@
         piTableEntryBuilder.forTable(PiTableId.of(tableInfo.getPreamble().getName()));
 
         // Priority.
-        piTableEntryBuilder.withPriority(tableEntryMsg.getPriority());
+        if (tableEntryMsg.getPriority() > 0) {
+            piTableEntryBuilder.withPriority(tableEntryMsg.getPriority());
+        }
 
         // Controller metadata (cookie)
         piTableEntryBuilder.withCookie(tableEntryMsg.getControllerMetadata());
diff --git a/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4TableModel.java b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4TableModel.java
index 77f55ec..5afad85 100644
--- a/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4TableModel.java
+++ b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4TableModel.java
@@ -48,7 +48,7 @@
     private final boolean supportAging;
     private final ImmutableMap<PiMatchFieldId, PiMatchFieldModel> matchFields;
     private final ImmutableMap<PiActionId, PiActionModel> actions;
-    private final PiActionModel defaultAction;
+    private final PiActionModel constDefaultAction;
     private final boolean hasDefaultMutableParams;
     private final boolean isConstTable;
 
@@ -58,7 +58,7 @@
                  ImmutableMap<PiMeterId, PiMeterModel> meters, boolean supportAging,
                  ImmutableMap<PiMatchFieldId, PiMatchFieldModel> matchFields,
                  ImmutableMap<PiActionId, PiActionModel> actions,
-                 PiActionModel defaultAction, boolean hasDefaultMutableParams,
+                 PiActionModel constDefaultAction, boolean hasDefaultMutableParams,
                  boolean isConstTable) {
         this.id = id;
         this.tableType = tableType;
@@ -69,7 +69,7 @@
         this.supportAging = supportAging;
         this.matchFields = matchFields;
         this.actions = actions;
-        this.defaultAction = defaultAction;
+        this.constDefaultAction = constDefaultAction;
         this.hasDefaultMutableParams = hasDefaultMutableParams;
         this.isConstTable = isConstTable;
     }
@@ -120,8 +120,8 @@
     }
 
     @Override
-    public Optional<PiActionModel> defaultAction() {
-        return Optional.ofNullable(defaultAction);
+    public Optional<PiActionModel> constDefaultAction() {
+        return Optional.ofNullable(constDefaultAction);
     }
 
     @Override
@@ -148,7 +148,7 @@
     public int hashCode() {
         return Objects.hash(id, tableType, actionProfile, maxSize, counters,
                             meters, supportAging, matchFields, actions,
-                            defaultAction, hasDefaultMutableParams);
+                            constDefaultAction, hasDefaultMutableParams);
     }
 
     @Override
@@ -169,7 +169,7 @@
                 && Objects.equals(this.supportAging, other.supportAging)
                 && Objects.equals(this.matchFields, other.matchFields)
                 && Objects.equals(this.actions, other.actions)
-                && Objects.equals(this.defaultAction, other.defaultAction)
+                && Objects.equals(this.constDefaultAction, other.constDefaultAction)
                 && Objects.equals(this.hasDefaultMutableParams, other.hasDefaultMutableParams);
     }
 }
diff --git a/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4InfoParserTest.java b/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4InfoParserTest.java
index e23a49c..3a803d4 100644
--- a/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4InfoParserTest.java
+++ b/protocols/p4runtime/model/src/test/java/org/onosproject/p4runtime/model/P4InfoParserTest.java
@@ -193,7 +193,7 @@
                    wcmpTableModel.actions(), IsIterableContainingInAnyOrder.containsInAnyOrder(
                         setEgressPortAction, noAction));
 
-        PiActionModel table0DefaultAction = table0Model.defaultAction().orElse(null);
+        PiActionModel table0DefaultAction = table0Model.constDefaultAction().orElse(null);
 
         new EqualsTester().addEqualityGroup(table0DefaultAction, dropAction).testEquals();