Modified ECMP demo app for Tofino

Change-Id: Ie3b7d33a95563e8392b1775cc7b3033bb4b4466a
diff --git a/apps/pi-demo/common/src/main/java/org/onosproject/pi/demo/app/common/AbstractUpgradableFabricApp.java b/apps/pi-demo/common/src/main/java/org/onosproject/pi/demo/app/common/AbstractUpgradableFabricApp.java
index f77763e..04cfc7a 100644
--- a/apps/pi-demo/common/src/main/java/org/onosproject/pi/demo/app/common/AbstractUpgradableFabricApp.java
+++ b/apps/pi-demo/common/src/main/java/org/onosproject/pi/demo/app/common/AbstractUpgradableFabricApp.java
@@ -89,6 +89,7 @@
 
     private static final int NUM_LEAFS = 2;
     private static final int NUM_SPINES = 2;
+    protected static final int HASHED_LINKS = 2;
     private static final int FLOW_PRIORITY = 100;
 
     private static final int CLEANUP_SLEEP = 2000;
@@ -416,7 +417,7 @@
                 newFlowRules.addAll(generateSpineRules(deviceId, hosts, topo));
             }
         } catch (FlowRuleGeneratorException e) {
-            log.warn("Exception while executing flow rule generator: {}", e.toString());
+            log.warn("Exception while executing flow rule generator: {}", e.getMessage());
             return;
         }
 
diff --git a/apps/pi-demo/ecmp/src/main/java/org/onosproject/pi/demo/app/ecmp/EcmpFabricApp.java b/apps/pi-demo/ecmp/src/main/java/org/onosproject/pi/demo/app/ecmp/EcmpFabricApp.java
index 3aabca8..4cac11b 100644
--- a/apps/pi-demo/ecmp/src/main/java/org/onosproject/pi/demo/app/ecmp/EcmpFabricApp.java
+++ b/apps/pi-demo/ecmp/src/main/java/org/onosproject/pi/demo/app/ecmp/EcmpFabricApp.java
@@ -21,35 +21,35 @@
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.felix.scr.annotations.Component;
 import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.bmv2.model.Bmv2PipelineModelParser;
 import org.onosproject.driver.pipeline.DefaultSingleTablePipeline;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.Path;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
 import org.onosproject.net.behaviour.Pipeliner;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
 import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.flow.criteria.Criterion;
 import org.onosproject.net.flow.criteria.PiCriterion;
+import org.onosproject.net.pi.model.DefaultPiPipeconf;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.model.PiPipeconfId;
+import org.onosproject.net.pi.model.PiPipelineInterpreter;
 import org.onosproject.net.pi.runtime.PiAction;
 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.PiHeaderFieldId;
 import org.onosproject.net.pi.runtime.PiTableAction;
-import org.onosproject.pi.demo.app.common.AbstractUpgradableFabricApp;
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.Host;
-import org.onosproject.net.Path;
-import org.onosproject.net.Port;
-import org.onosproject.net.PortNumber;
-import org.onosproject.net.flow.DefaultTrafficSelector;
-import org.onosproject.net.flow.DefaultTrafficTreatment;
-import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.TrafficTreatment;
-import org.onosproject.net.pi.model.DefaultPiPipeconf;
-import org.onosproject.net.pi.model.PiPipeconf;
-import org.onosproject.net.pi.model.PiPipeconfId;
-import org.onosproject.net.pi.model.PiPipelineInterpreter;
 import org.onosproject.net.topology.DefaultTopologyVertex;
 import org.onosproject.net.topology.Topology;
 import org.onosproject.net.topology.TopologyGraph;
-import org.onosproject.bmv2.model.Bmv2PipelineModelParser;
+import org.onosproject.pi.demo.app.common.AbstractUpgradableFabricApp;
 
 import java.net.URL;
 import java.util.Collection;
@@ -59,6 +59,7 @@
 import java.util.Set;
 import java.util.stream.Collectors;
 
+import static java.lang.String.format;
 import static java.util.stream.Collectors.toSet;
 import static org.onlab.packet.EthType.EtherType.IPV4;
 import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.BMV2_JSON;
@@ -74,8 +75,8 @@
 
     private static final String APP_NAME = "org.onosproject.pi-ecmp-fabric";
     private static final String PIPECONF_ID = "pi-demo-ecmp";
-    private static final URL P4INFO_URL = EcmpFabricApp.class.getResource("/ecmp.p4info");
-    private static final URL JSON_URL = EcmpFabricApp.class.getResource("/ecmp.json");
+    private static final URL P4INFO_URL = EcmpFabricApp.class.getResource("/ecmp_14.p4info");
+    private static final URL JSON_URL = EcmpFabricApp.class.getResource("/ecmp_14.json");
 
     private static final PiPipeconf ECMP_PIPECONF = DefaultPiPipeconf.builder()
             .withId(new PiPipeconfId(PIPECONF_ID))
@@ -220,15 +221,19 @@
     }
 
     private Pair<PiTableAction, List<FlowRule>> provisionEcmpPiTableAction(DeviceId deviceId,
-                                                                            Set<PortNumber> fabricPorts)
+                                                                           Set<PortNumber> fabricPorts)
             throws FlowRuleGeneratorException {
 
         // Install ECMP group table entries that map from hash values to actual fabric ports...
         int groupId = groupIdOf(deviceId, fabricPorts);
-        int groupSize = fabricPorts.size();
+        if (fabricPorts.size() != HASHED_LINKS) {
+            throw new FlowRuleGeneratorException(format(
+                    "Invalid number of fabric ports for %s, expected %d but found %d",
+                    deviceId, HASHED_LINKS, fabricPorts.size()));
+        }
         Iterator<PortNumber> portIterator = fabricPorts.iterator();
         List<FlowRule> rules = Lists.newArrayList();
-        for (short i = 0; i < groupSize; i++) {
+        for (short i = 0; i < HASHED_LINKS; i++) {
             FlowRule rule = flowRuleBuilder(deviceId, EcmpInterpreter.ECMP_GROUP_TABLE)
                     .withSelector(
                             buildEcmpTrafficSelector(groupId, i))
@@ -240,19 +245,17 @@
             rules.add(rule);
         }
 
-        PiTableAction piTableAction = buildEcmpPiTableAction(groupId, groupSize);
+        PiTableAction piTableAction = buildEcmpPiTableAction(groupId);
 
         return Pair.of(piTableAction, rules);
     }
 
-    private PiTableAction buildEcmpPiTableAction(int groupId, int groupSize) {
+    private PiTableAction buildEcmpPiTableAction(int groupId) {
 
         return PiAction.builder()
                 .withId(PiActionId.of(ECMP_GROUP_ACTION_NAME))
                 .withParameter(new PiActionParam(PiActionParamId.of(GROUP_ID),
                                                  ImmutableByteSequence.copyFrom(groupId)))
-                .withParameter(new PiActionParam(PiActionParamId.of(GROUP_SIZE),
-                                                 ImmutableByteSequence.copyFrom(groupSize)))
                 .build();
     }
 
diff --git a/apps/pi-demo/ecmp/src/main/java/org/onosproject/pi/demo/app/ecmp/EcmpInterpreter.java b/apps/pi-demo/ecmp/src/main/java/org/onosproject/pi/demo/app/ecmp/EcmpInterpreter.java
index d58ff16..94896aa 100644
--- a/apps/pi-demo/ecmp/src/main/java/org/onosproject/pi/demo/app/ecmp/EcmpInterpreter.java
+++ b/apps/pi-demo/ecmp/src/main/java/org/onosproject/pi/demo/app/ecmp/EcmpInterpreter.java
@@ -60,21 +60,25 @@
  */
 public class EcmpInterpreter extends AbstractHandlerBehaviour implements PiPipelineInterpreter {
 
+    // standard_metadata for Bmv2, ig_intr_md for Tofino
+    // TODO: extract header name from model (first column of table0)
+    private static final String STD_METADATA_HEADER_NAME = "ig_intr_md";
+
     protected static final String ECMP_METADATA_HEADER_NAME = "ecmp_metadata_t";
     protected static final String ECMP_GROUP_ACTION_NAME = "ecmp_group";
     protected static final String GROUP_ID = "group_id";
     protected static final String SELECTOR = "selector";
-    protected static final String GROUP_SIZE = "groupSize";
     protected static final String ECMP_GROUP_TABLE = "ecmp_group_table";
     protected static final String TABLE0 = "table0";
     private static final String SEND_TO_CPU = "send_to_cpu";
     private static final String PORT = "port";
-    private static final String DROP = "drop";
+    private static final String DROP = "_drop";
     private static final String SET_EGRESS_PORT = "set_egress_port";
     private static final String EGRESS_PORT = "egress_port";
+    private static final String INGRESS_PORT = "ingress_port";
     private static final int PORT_NUMBER_BIT_WIDTH = 9;
 
-    private static final PiHeaderFieldId IN_PORT_ID = PiHeaderFieldId.of("standard_metadata", "ingress_port");
+    private static final PiHeaderFieldId IN_PORT_ID = PiHeaderFieldId.of(STD_METADATA_HEADER_NAME, "ingress_port");
     private static final PiHeaderFieldId ETH_DST_ID = PiHeaderFieldId.of("ethernet", "dstAddr");
     private static final PiHeaderFieldId ETH_SRC_ID = PiHeaderFieldId.of("ethernet", "srcAddr");
     private static final PiHeaderFieldId ETH_TYPE_ID = PiHeaderFieldId.of("ethernet", "etherType");
@@ -91,8 +95,6 @@
             0, PiTableId.of(TABLE0),
             1, PiTableId.of(ECMP_GROUP_TABLE));
 
-    public static final String INGRESS_PORT = "ingress_port";
-
     @Override
     public Optional<Integer> mapPiTableId(PiTableId piTableId) {
         return Optional.ofNullable(TABLE_MAP.inverse().get(piTableId));
@@ -117,7 +119,7 @@
                 Instructions.OutputInstruction outInstruction = (Instructions.OutputInstruction) instruction;
                 PortNumber port = outInstruction.port();
                 if (!port.isLogical()) {
-                    PiAction.builder()
+                    return PiAction.builder()
                             .withId(PiActionId.of(SET_EGRESS_PORT))
                             .withParameter(new PiActionParam(PiActionParamId.of(PORT),
                                                              ImmutableByteSequence.copyFrom(port.toLong())))
@@ -176,7 +178,7 @@
                 throw new PiInterpreterException("Logical port not supported: " +
                                                          outInst.port());
             } else if (outInst.port().equals(FLOOD)) {
-                //Since ecmp.p4 does not support flood for each port of the device
+                // Since ecmp.p4 does not support flood for each port of the device
                 // create a packet operation to send the packet out of that specific port
                 for (Port port : handler().get(DeviceService.class).getPorts(packet.sendThrough())) {
                     builder.add(createPiPacketOperation(packet.data(), port.number().toLong()));
@@ -206,7 +208,7 @@
         try {
             portValue = ImmutableByteSequence.fit(portValue, PORT_NUMBER_BIT_WIDTH);
         } catch (ImmutableByteSequence.ByteSequenceTrimException e) {
-            throw new PiInterpreterException("Port number too big: {}" +
+            throw new PiInterpreterException("Port number too big:" +
                                                      portNumber + " causes " + e.getMessage());
         }
         return PiPacketMetadata.builder()
@@ -230,10 +232,10 @@
 
         if (packetMetadata.isPresent()) {
 
-            //Obtaining the ingress port as an immutable byte sequence
+            // Obtaining the ingress port as an immutable byte sequence.
             ImmutableByteSequence portByteSequence = packetMetadata.get().value();
 
-            //Converting immutableByteSequence to short
+            // Converting immutableByteSequence to short.
             short s = portByteSequence.asReadOnlyBuffer().getShort();
 
             ConnectPoint receivedFrom = new ConnectPoint(deviceId, PortNumber.portNumber(s));
diff --git a/apps/pi-demo/ecmp/src/main/resources/ecmp.json b/apps/pi-demo/ecmp/src/main/resources/ecmp_14.json
similarity index 100%
rename from apps/pi-demo/ecmp/src/main/resources/ecmp.json
rename to apps/pi-demo/ecmp/src/main/resources/ecmp_14.json
diff --git a/apps/pi-demo/ecmp/src/main/resources/ecmp.p4info b/apps/pi-demo/ecmp/src/main/resources/ecmp_14.p4info
similarity index 100%
rename from apps/pi-demo/ecmp/src/main/resources/ecmp.p4info
rename to apps/pi-demo/ecmp/src/main/resources/ecmp_14.p4info
diff --git a/apps/pi-demo/ecmp/src/main/resources/ecmp_16.json b/apps/pi-demo/ecmp/src/main/resources/ecmp_16.json
new file mode 120000
index 0000000..5b1b6d9
--- /dev/null
+++ b/apps/pi-demo/ecmp/src/main/resources/ecmp_16.json
@@ -0,0 +1 @@
+../../../../../../tools/test/p4src/p4-14/p4c-out/ecmp.json
\ No newline at end of file
diff --git a/apps/pi-demo/ecmp/src/main/resources/ecmp_16.p4info b/apps/pi-demo/ecmp/src/main/resources/ecmp_16.p4info
new file mode 120000
index 0000000..7fe1b6c
--- /dev/null
+++ b/apps/pi-demo/ecmp/src/main/resources/ecmp_16.p4info
@@ -0,0 +1 @@
+../../../../../../tools/test/p4src/p4-14/p4c-out/ecmp.p4info
\ No newline at end of file
diff --git a/core/net/src/test/java/org/onosproject/net/pi/impl/MockInterpreter.java b/core/net/src/test/java/org/onosproject/net/pi/impl/MockInterpreter.java
index 2dff2f1..794e7f8 100644
--- a/core/net/src/test/java/org/onosproject/net/pi/impl/MockInterpreter.java
+++ b/core/net/src/test/java/org/onosproject/net/pi/impl/MockInterpreter.java
@@ -50,7 +50,7 @@
     static final String TABLE0 = "table0";
     static final String SEND_TO_CPU = "send_to_cpu";
     static final String PORT = "port";
-    static final String DROP = "drop";
+    static final String DROP = "_drop";
     static final String SET_EGRESS_PORT = "set_egress_port";
 
     static final PiHeaderFieldId IN_PORT_ID = PiHeaderFieldId.of("standard_metadata", "ingress_port");