[ONOS-7500] Supports PiTableEntry with no action

Change-Id: I92a38b184d4ded539297f1d99e1405eea014bda0
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 a836e15..18b31f2 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
@@ -255,7 +255,6 @@
         public PiTableEntry build() {
             checkNotNull(tableId);
             checkNotNull(matchKey);
-            checkNotNull(tableAction);
             return new PiTableEntry(tableId, matchKey, tableAction, cookie, priority, timeout);
         }
     }
diff --git a/core/net/src/main/java/org/onosproject/net/pi/impl/PiFlowRuleTranslatorImpl.java b/core/net/src/main/java/org/onosproject/net/pi/impl/PiFlowRuleTranslatorImpl.java
index c637144..796891d 100644
--- a/core/net/src/main/java/org/onosproject/net/pi/impl/PiFlowRuleTranslatorImpl.java
+++ b/core/net/src/main/java/org/onosproject/net/pi/impl/PiFlowRuleTranslatorImpl.java
@@ -125,6 +125,10 @@
                 .withMatchKey(piMatchKey)
                 .withAction(piTableAction);
 
+        if (piTableAction != null) {
+            tableEntryBuilder.withAction(piTableAction);
+        }
+
         if (needPriority) {
             tableEntryBuilder.withPriority(rule.priority());
         }
@@ -202,18 +206,15 @@
             }
         }
 
-        if (piTableAction == null) {
-            // No PiInstruction, no interpreter. It's time to give up.
-            throw new PiTranslationException(
-                    "Unable to translate treatment, neither an interpreter or a "
-                            + "protocol-independent instruction were provided.");
-        }
-
         return piTableAction;
     }
 
     private static PiTableAction typeCheckAction(PiTableAction piTableAction, PiTableModel table)
             throws PiTranslationException {
+        if (piTableAction == null) {
+            // skip check if null
+            return null;
+        }
         switch (piTableAction.type()) {
             case ACTION:
                 return checkPiAction((PiAction) piTableAction, table);
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 9d46413..647b9cd 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
@@ -49,6 +49,7 @@
 import java.util.Collections;
 import java.util.List;
 
+import static com.google.common.base.Preconditions.checkNotNull;
 import static java.lang.String.format;
 import static org.onlab.util.ImmutableByteSequence.copyFrom;
 import static org.onosproject.p4runtime.ctl.P4RuntimeUtils.assertPrefixLen;
@@ -236,7 +237,9 @@
         }
 
         // Table action.
-        tableEntryMsgBuilder.setAction(encodePiTableAction(piTableEntry.action(), browser));
+        if (piTableEntry.action() != null) {
+            tableEntryMsgBuilder.setAction(encodePiTableAction(piTableEntry.action(), browser));
+        }
 
         // Field matches.
         if (piTableEntry.matchKey().equals(PiMatchKey.EMPTY)) {
@@ -267,7 +270,9 @@
         piTableEntryBuilder.withCookie(tableEntryMsg.getControllerMetadata());
 
         // Table action.
-        piTableEntryBuilder.withAction(decodeTableActionMsg(tableEntryMsg.getAction(), browser));
+        if (tableEntryMsg.hasAction()) {
+            piTableEntryBuilder.withAction(decodeTableActionMsg(tableEntryMsg.getAction(), browser));
+        }
 
         // Timeout.
         // FIXME: how to decode table entry messages with timeout, given that the timeout value is lost after encoding?
@@ -428,7 +433,7 @@
 
     static TableAction encodePiTableAction(PiTableAction piTableAction, P4InfoBrowser browser)
             throws P4InfoBrowser.NotFoundException, EncodeException {
-
+        checkNotNull(piTableAction, "Cannot encode null PiTableAction");
         TableAction.Builder tableActionMsgBuilder = TableAction.newBuilder();
 
         switch (piTableAction.type()) {
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 f735d25..eebfb6b 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
@@ -115,6 +115,19 @@
             .withCookie(2)
             .build();
 
+    private final PiTableEntry piTableEntryWithoutAction = PiTableEntry
+            .builder()
+            .forTable(tableId)
+            .withMatchKey(PiMatchKey.builder()
+                                  .addFieldMatch(new PiTernaryFieldMatch(ethDstAddrFieldId, ethAddr, ofOnes(6)))
+                                  .addFieldMatch(new PiTernaryFieldMatch(ethSrcAddrFieldId, ethAddr, ofOnes(6)))
+                                  .addFieldMatch(new PiTernaryFieldMatch(inPortFieldId, portValue, ofOnes(2)))
+                                  .addFieldMatch(new PiTernaryFieldMatch(ethTypeFieldId, portValue, ofOnes(2)))
+                                  .build())
+            .withPriority(1)
+            .withCookie(2)
+            .build();
+
     private final PiTableEntry piTableEntryWithGroupAction = PiTableEntry
             .builder()
             .forTable(ecmpTableId)
@@ -218,4 +231,34 @@
         int actionProfileGroupId = tableEntryMsg.getAction().getActionProfileGroupId();
         assertThat(actionProfileGroupId, is(1));
     }
+
+    @Test
+    public void testEncodeWithNoAction() throws Exception {
+        Collection<TableEntry> result = encode(Lists.newArrayList(piTableEntryWithoutAction), defaultPipeconf);
+        assertThat(result, hasSize(1));
+
+        TableEntry tableEntryMsg = result.iterator().next();
+
+        Collection<PiTableEntry> decodedResults = decode(Lists.newArrayList(tableEntryMsg), defaultPipeconf);
+        PiTableEntry decodedPiTableEntry = decodedResults.iterator().next();
+
+        // Test equality for decoded entry.
+        new EqualsTester()
+                .addEqualityGroup(piTableEntryWithoutAction, decodedPiTableEntry)
+                .testEquals();
+
+        // Table ID.
+        int p4InfoTableId = browser.tables().getByName(tableId.id()).getPreamble().getId();
+        int encodedTableId = tableEntryMsg.getTableId();
+        assertThat(encodedTableId, is(p4InfoTableId));
+
+        // Ternary match.
+        byte[] encodedTernaryMatchValue = tableEntryMsg.getMatch(0).getTernary().getValue().toByteArray();
+        assertThat(encodedTernaryMatchValue, is(ethAddr.asArray()));
+
+        // no action
+        assertThat(tableEntryMsg.hasAction(), is(false));
+
+        // TODO: improve, assert other field match types (ternary, LPM)
+    }
 }