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
(cherry picked from commit db347377bec8bf6f71fb9828f4dc552731e562f7)
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();
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricTreatmentInterpreter.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricTreatmentInterpreter.java
index de3ecbf..9b33d2d 100644
--- a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricTreatmentInterpreter.java
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricTreatmentInterpreter.java
@@ -38,7 +38,13 @@
 
 import static java.lang.String.format;
 import static org.onosproject.net.flow.instructions.Instruction.Type.OUTPUT;
-import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.*;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.ETH_DST;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.ETH_SRC;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.MPLS_LABEL;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.MPLS_PUSH;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.VLAN_ID;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.VLAN_POP;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.VLAN_PUSH;
 import static org.onosproject.pipelines.fabric.impl.behaviour.FabricUtils.instruction;
 import static org.onosproject.pipelines.fabric.impl.behaviour.FabricUtils.l2Instruction;
 import static org.onosproject.pipelines.fabric.impl.behaviour.FabricUtils.l2Instructions;
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/FabricPipeliner.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/FabricPipeliner.java
index 79045f1..eda5786 100644
--- a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/FabricPipeliner.java
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/FabricPipeliner.java
@@ -17,16 +17,26 @@
 package org.onosproject.pipelines.fabric.impl.behaviour.pipeliner;
 
 import com.google.common.collect.ImmutableList;
+import org.onlab.packet.Ethernet;
 import org.onlab.util.KryoNamespace;
 import org.onlab.util.SharedExecutors;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.behaviour.NextGroup;
 import org.onosproject.net.behaviour.Pipeliner;
 import org.onosproject.net.behaviour.PipelinerContext;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.FlowRule;
 import org.onosproject.net.flow.FlowRuleOperations;
 import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criteria;
+import org.onosproject.net.flow.criteria.PiCriterion;
 import org.onosproject.net.flowobjective.FilteringObjective;
 import org.onosproject.net.flowobjective.FlowObjectiveStore;
 import org.onosproject.net.flowobjective.ForwardingObjective;
@@ -37,6 +47,10 @@
 import org.onosproject.net.flowobjective.ObjectiveError;
 import org.onosproject.net.group.GroupDescription;
 import org.onosproject.net.group.GroupService;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.pipelines.fabric.FabricConstants;
+import org.onosproject.pipelines.fabric.impl.FabricPipeconfLoader;
 import org.onosproject.pipelines.fabric.impl.behaviour.AbstractFabricHandlerBehavior;
 import org.onosproject.pipelines.fabric.impl.behaviour.FabricCapabilities;
 import org.onosproject.store.serializers.KryoNamespaces;
@@ -51,7 +65,10 @@
 
 import static java.lang.String.format;
 import static org.onosproject.net.flowobjective.NextObjective.Type.SIMPLE;
+import static org.onosproject.pipelines.fabric.impl.behaviour.FabricInterpreter.ONE;
+import static org.onosproject.pipelines.fabric.impl.behaviour.FabricInterpreter.ZERO;
 import static org.onosproject.pipelines.fabric.impl.behaviour.FabricUtils.outputPort;
+import static org.onosproject.pipelines.fabric.impl.behaviour.pipeliner.FilteringObjectiveTranslator.FWD_IPV4_ROUTING;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -63,6 +80,8 @@
         implements Pipeliner {
 
     private static final Logger log = getLogger(FabricPipeliner.class);
+    private static final int DEFAULT_FLOW_PRIORITY = 100;
+    public static final int DEFAULT_VLAN = 4094;
 
     protected static final KryoNamespace KRYO = new KryoNamespace.Builder()
             .register(KryoNamespaces.API)
@@ -70,9 +89,11 @@
             .build("FabricPipeliner");
 
     protected DeviceId deviceId;
+    protected ApplicationId appId;
     protected FlowRuleService flowRuleService;
     protected GroupService groupService;
     protected FlowObjectiveStore flowObjectiveStore;
+    protected CoreService coreService;
 
     private FilteringObjectiveTranslator filteringTranslator;
     private ForwardingObjectiveTranslator forwardingTranslator;
@@ -106,6 +127,17 @@
         this.filteringTranslator = new FilteringObjectiveTranslator(deviceId, capabilities);
         this.forwardingTranslator = new ForwardingObjectiveTranslator(deviceId, capabilities);
         this.nextTranslator = new NextObjectiveTranslator(deviceId, capabilities);
+        this.coreService = context.directory().get(CoreService.class);
+        this.appId = coreService.getAppId(FabricPipeconfLoader.PIPELINE_APP_NAME);
+    }
+
+    protected void initializePipeline() {
+        // Set up rules for packet-out forwarding. We support only IPv4 routing.
+        final int cpuPort = capabilities.cpuPort().get();
+        flowRuleService.applyFlowRules(
+                ingressVlanRule(cpuPort, false, DEFAULT_VLAN),
+                fwdClassifierRule(cpuPort, null, Ethernet.TYPE_IPV4, FWD_IPV4_ROUTING,
+                        DEFAULT_FLOW_PRIORITY));
     }
 
     @Override
@@ -268,7 +300,7 @@
     private void removeNextGroup(NextObjective obj) {
         final NextGroup removed = flowObjectiveStore.removeNextGroup(obj.id());
         if (removed == null) {
-            log.debug("NextGroup {} was not found in FlowObjectiveStore");
+            log.debug("NextGroup {} was not found in FlowObjectiveStore", obj);
         }
     }
 
@@ -296,6 +328,57 @@
         }
     }
 
+    public FlowRule ingressVlanRule(long port, boolean vlanValid, int vlanId) {
+        final TrafficSelector selector = DefaultTrafficSelector.builder()
+                .add(Criteria.matchInPort(PortNumber.portNumber(port)))
+                .add(PiCriterion.builder()
+                        .matchExact(FabricConstants.HDR_VLAN_IS_VALID, vlanValid ? ONE : ZERO)
+                        .build())
+                .build();
+        final TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .piTableAction(PiAction.builder()
+                        .withId(vlanValid ? FabricConstants.FABRIC_INGRESS_FILTERING_PERMIT
+                                : FabricConstants.FABRIC_INGRESS_FILTERING_PERMIT_WITH_INTERNAL_VLAN)
+                        .withParameter(new PiActionParam(FabricConstants.VLAN_ID, vlanId))
+                        .build())
+                .build();
+        return DefaultFlowRule.builder()
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .forTable(FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN)
+                .makePermanent()
+                .withPriority(DEFAULT_FLOW_PRIORITY)
+                .forDevice(deviceId)
+                .fromApp(appId)
+                .build();
+    }
+
+    public FlowRule fwdClassifierRule(int port, Short ethType, short ipEthType, byte fwdType, int priority) {
+        final TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder()
+                .matchInPort(PortNumber.portNumber(port))
+                .matchPi(PiCriterion.builder()
+                        .matchExact(FabricConstants.HDR_IP_ETH_TYPE, ipEthType)
+                        .build());
+        if (ethType != null) {
+            selectorBuilder.matchEthType(ethType);
+        }
+        final TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .piTableAction(PiAction.builder()
+                        .withId(FabricConstants.FABRIC_INGRESS_FILTERING_SET_FORWARDING_TYPE)
+                        .withParameter(new PiActionParam(FabricConstants.FWD_TYPE, fwdType))
+                        .build())
+                .build();
+        return DefaultFlowRule.builder()
+                .withSelector(selectorBuilder.build())
+                .withTreatment(treatment)
+                .forTable(FabricConstants.FABRIC_INGRESS_FILTERING_FWD_CLASSIFIER)
+                .makePermanent()
+                .withPriority(priority)
+                .forDevice(deviceId)
+                .fromApp(appId)
+                .build();
+    }
+
     /**
      * NextGroup implementation.
      */