SDFAB-193 Add packet-out routing feature to fabric-v1model

This is a port from fabric-tna:
https://github.com/stratum/fabric-tna/pull/262

By default, all packet-outs are sent straight to the egress port passed
as a controller packet-out metadata, bypassing the forwarding tables in
the ingress pipe. With this change, the control plane can set a new
packet-out metadata named `do_forwarding` to instruct the parser to
forward packet-outs as regular packets.

When handling `OutboundPacket` in ONOS, the pipeconf (interpreter) uses
the `OUTPUT` instruction with logical port `TABLE` to enable forwarding.
This is consistent with the OpenFlow behavior, from the spec:

    Required: TABLE: Represents the start of the OpenFlow pipeline (see
    5.1). This port is only valid in an output action in the action list
    of a packet-out message (see 7.3.7), and submits the packet to the
    first flow table so that the packet can be processed through the
    regular OpenFlow pipeline.

We also rename some test classes for consistency with main classes.
Before we had a FabricPipelinerTest class that was used for a different
purpose than testing FabricPipeliner.

Change-Id: I1b47c4b4f233df5b67d1a6dc743dea27c54772b2
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricInterpreter.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricInterpreter.java
index 9d19e00..358f86d 100644
--- a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricInterpreter.java
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricInterpreter.java
@@ -53,6 +53,7 @@
 import static org.onlab.util.ImmutableByteSequence.copyFrom;
 import static org.onosproject.net.PortNumber.CONTROLLER;
 import static org.onosproject.net.PortNumber.FLOOD;
+import static org.onosproject.net.PortNumber.TABLE;
 import static org.onosproject.net.flow.instructions.Instruction.Type.OUTPUT;
 import static org.onosproject.net.pi.model.PiPacketOperationType.PACKET_OUT;
 
@@ -63,6 +64,8 @@
         implements PiPipelineInterpreter {
 
     private static final int PORT_BITWIDTH = 9;
+    public static final byte[] ONE = new byte[]{1};
+    public static final byte[] ZERO = new byte[]{0};
 
     // Group tables by control block.
     private static final Set<PiTableId> FILTERING_CTRL_TBLS = ImmutableSet.of(
@@ -180,23 +183,30 @@
     }
 
     private PiPacketOperation createPiPacketOperation(
-            DeviceId deviceId, ByteBuffer data, long portNumber)
+            ByteBuffer data, long portNumber, boolean doForwarding)
             throws PiInterpreterException {
-        PiPacketMetadata metadata = createPacketMetadata(portNumber);
         return PiPacketOperation.builder()
                 .withType(PACKET_OUT)
                 .withData(copyFrom(data))
-                .withMetadatas(ImmutableList.of(metadata))
+                .withMetadatas(createPacketMetadata(portNumber, doForwarding))
                 .build();
     }
 
-    private PiPacketMetadata createPacketMetadata(long portNumber)
+    private Collection<PiPacketMetadata> createPacketMetadata(
+            long portNumber, boolean doForwarding)
             throws PiInterpreterException {
         try {
-            return PiPacketMetadata.builder()
+            ImmutableList.Builder<PiPacketMetadata> builder = ImmutableList.builder();
+            builder.add(PiPacketMetadata.builder()
                     .withId(FabricConstants.EGRESS_PORT)
-                    .withValue(copyFrom(portNumber).fit(PORT_BITWIDTH))
-                    .build();
+                    .withValue(copyFrom(portNumber)
+                            .fit(PORT_BITWIDTH))
+                    .build());
+            builder.add(PiPacketMetadata.builder()
+                    .withId(FabricConstants.DO_FORWARDING)
+                    .withValue(copyFrom(doForwarding ? ONE : ZERO))
+                    .build());
+            return builder.build();
         } catch (ImmutableByteSequence.ByteSequenceTrimException e) {
             throw new PiInterpreterException(format(
                     "Port number '%d' too big, %s", portNumber, e.getMessage()));
@@ -224,18 +234,21 @@
 
         ImmutableList.Builder<PiPacketOperation> builder = ImmutableList.builder();
         for (Instructions.OutputInstruction outInst : outInstructions) {
-            if (outInst.port().isLogical() && !outInst.port().equals(FLOOD)) {
-                throw new PiInterpreterException(format(
-                        "Output on logical port '%s' not supported", outInst.port()));
+            if (outInst.port().equals(TABLE)) {
+                // Logical port. Forward using the switch tables like a regular packet.
+                builder.add(createPiPacketOperation(packet.data(), 0, true));
             } else if (outInst.port().equals(FLOOD)) {
-                // Since fabric.p4 does not support flooding, we create a packet
-                // operation for each switch port.
+                // Logical port. Create a packet operation for each switch port.
                 final DeviceService deviceService = handler().get(DeviceService.class);
                 for (Port port : deviceService.getPorts(packet.sendThrough())) {
-                    builder.add(createPiPacketOperation(deviceId, packet.data(), port.number().toLong()));
+                    builder.add(createPiPacketOperation(packet.data(), port.number().toLong(), false));
                 }
+            } else if (outInst.port().isLogical()) {
+                throw new PiInterpreterException(format(
+                        "Output on logical port '%s' not supported", outInst.port()));
             } else {
-                builder.add(createPiPacketOperation(deviceId, packet.data(), outInst.port().toLong()));
+                // Send as-is to given port bypassing all switch tables.
+                builder.add(createPiPacketOperation(packet.data(), outInst.port().toLong(), false));
             }
         }
         return builder.build();