Xconnect support for fabric.p4 pipeliner

Change-Id: I3bd802ccbc34561b71862a160bab67adeccc2891
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 a90353e..580d6f7 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
@@ -76,7 +76,8 @@
     private static final Set<PiTableId> NEXT_CTRL_TBLS = ImmutableSet.of(
             FabricConstants.FABRIC_INGRESS_NEXT_NEXT_VLAN,
             FabricConstants.FABRIC_INGRESS_NEXT_SIMPLE,
-            FabricConstants.FABRIC_INGRESS_NEXT_HASHED);
+            FabricConstants.FABRIC_INGRESS_NEXT_HASHED,
+            FabricConstants.FABRIC_INGRESS_NEXT_XCONNECT);
     private static final Set<PiTableId> E_NEXT_CTRL_TBLS = ImmutableSet.of(
             FabricConstants.FABRIC_EGRESS_EGRESS_NEXT_EGRESS_VLAN);
 
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricTreatmentInterpreter.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricTreatmentInterpreter.java
index 4248111..bca8424 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricTreatmentInterpreter.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricTreatmentInterpreter.java
@@ -108,6 +108,8 @@
             return mapNextHashedOrSimpleTreatment(treatment, tableId, false);
         } else if (tableId == FabricConstants.FABRIC_INGRESS_NEXT_SIMPLE) {
             return mapNextHashedOrSimpleTreatment(treatment, tableId, true);
+        } else if (tableId == FabricConstants.FABRIC_INGRESS_NEXT_XCONNECT) {
+            return mapNextXconnect(treatment, tableId);
         }
         throw new PiInterpreterException(format(
                 "Treatment mapping not supported for table '%s'", tableId));
@@ -174,6 +176,18 @@
         }
     }
 
+    private static PiAction mapNextXconnect(
+            TrafficTreatment treatment, PiTableId tableId)
+            throws PiInterpreterException {
+        final PortNumber outPort = ((OutputInstruction) instructionOrFail(
+                treatment, OUTPUT, tableId)).port();
+        return PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_NEXT_OUTPUT_XCONNECT)
+                .withParameter(new PiActionParam(
+                        FabricConstants.PORT_NUM, outPort.toLong()))
+                .build();
+    }
+
     static PiAction mapAclTreatment(TrafficTreatment treatment, PiTableId tableId)
             throws PiInterpreterException {
         if (isNoAction(treatment)) {
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/NextObjectiveTranslator.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/NextObjectiveTranslator.java
index e45768a..b2c5406 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/NextObjectiveTranslator.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/NextObjectiveTranslator.java
@@ -66,6 +66,8 @@
 class NextObjectiveTranslator
         extends AbstractObjectiveTranslator<NextObjective> {
 
+    private static final String XCONNECT = "xconnect";
+
     NextObjectiveTranslator(DeviceId deviceId, FabricCapabilities capabilities) {
         super(deviceId, capabilities);
     }
@@ -85,7 +87,11 @@
                 hashedNext(obj, resultBuilder);
                 break;
             case BROADCAST:
-                multicastNext(obj, resultBuilder);
+                if (isXconnect(obj)) {
+                    xconnectNext(obj, resultBuilder);
+                } else {
+                    multicastNext(obj, resultBuilder);
+                }
                 break;
             default:
                 log.warn("Unsupported NextObjective type '{}'", obj);
@@ -248,12 +254,57 @@
     }
 
     private TrafficSelector nextIdSelector(int nextId) {
+        return nextIdSelectorBuilder(nextId).build();
+    }
+
+    private TrafficSelector.Builder nextIdSelectorBuilder(int nextId) {
         final PiCriterion nextIdCriterion = PiCriterion.builder()
                 .matchExact(FabricConstants.HDR_NEXT_ID, nextId)
                 .build();
         return DefaultTrafficSelector.builder()
-                .matchPi(nextIdCriterion)
+                .matchPi(nextIdCriterion);
+    }
+
+    private void xconnectNext(NextObjective obj, ObjectiveTranslation.Builder resultBuilder)
+            throws FabricPipelinerException {
+
+        final Collection<DefaultNextTreatment> defaultNextTreatments =
+                defaultNextTreatmentsOrFail(obj.nextTreatments());
+
+        final List<PortNumber> outPorts = defaultNextTreatments.stream()
+                .map(DefaultNextTreatment::treatment)
+                .map(FabricUtils::outputPort)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+
+        if (outPorts.size() != 2) {
+            throw new FabricPipelinerException(format(
+                    "Handling XCONNECT with %d treatments (ports), but expected is 2",
+                    defaultNextTreatments.size()), ObjectiveError.UNSUPPORTED);
+        }
+
+        final PortNumber port1 = outPorts.get(0);
+        final PortNumber port2 = outPorts.get(1);
+        final TrafficSelector selector1 = nextIdSelectorBuilder(obj.id())
+                .matchInPort(port1)
                 .build();
+        final TrafficTreatment treatment1 = DefaultTrafficTreatment.builder()
+                .setOutput(port2)
+                .build();
+        final TrafficSelector selector2 = nextIdSelectorBuilder(obj.id())
+                .matchInPort(port2)
+                .build();
+        final TrafficTreatment treatment2 = DefaultTrafficTreatment.builder()
+                .setOutput(port1)
+                .build();
+
+        resultBuilder.addFlowRule(flowRule(
+                obj, FabricConstants.FABRIC_INGRESS_NEXT_XCONNECT,
+                selector1, treatment1));
+        resultBuilder.addFlowRule(flowRule(
+                obj, FabricConstants.FABRIC_INGRESS_NEXT_XCONNECT,
+                selector2, treatment2));
+
     }
 
     private void multicastNext(NextObjective obj,
@@ -410,4 +461,8 @@
         return obj.op() == Objective.Operation.ADD_TO_EXISTING ||
                 obj.op() == Objective.Operation.REMOVE_FROM_EXISTING;
     }
+
+    private boolean isXconnect(NextObjective obj) {
+        return obj.appId().name().contains(XCONNECT);
+    }
 }
diff --git a/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipelinerTest.java b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipelinerTest.java
index 081f68f..a56d26b 100644
--- a/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipelinerTest.java
+++ b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipelinerTest.java
@@ -415,4 +415,83 @@
 
         assertEquals(expectedTranslation, actualTranslation);
     }
+
+    /**
+     * Test XConnect NextObjective.
+     *
+     * @throws FabricPipelinerException
+     */
+    @Test
+    public void testXconnectOutput() throws FabricPipelinerException {
+        TrafficTreatment treatment1 = DefaultTrafficTreatment.builder()
+                .setOutput(PORT_1)
+                .build();
+        TrafficTreatment treatment2 = DefaultTrafficTreatment.builder()
+                .setOutput(PORT_2)
+                .build();
+        NextObjective nextObjective = DefaultNextObjective.builder()
+                .withId(NEXT_ID_1)
+                .withPriority(PRIORITY)
+                .addTreatment(treatment1)
+                .addTreatment(treatment2)
+                .withType(NextObjective.Type.BROADCAST)
+                .makePermanent()
+                .fromApp(XCONNECT_APP_ID)
+                .add();
+
+        ObjectiveTranslation actualTranslation = translatorHashed.doTranslate(nextObjective);
+
+        // Should generate 2 flows for the xconnect table.
+
+        // Expected multicast table flow rule.
+        PiCriterion nextIdCriterion = PiCriterion.builder()
+                .matchExact(FabricConstants.HDR_NEXT_ID, NEXT_ID_1)
+                .build();
+        TrafficSelector xcSelector1 = DefaultTrafficSelector.builder()
+                .matchPi(nextIdCriterion)
+                .matchInPort(PORT_1)
+                .build();
+        TrafficTreatment xcTreatment1 = DefaultTrafficTreatment.builder()
+                .piTableAction(PiAction.builder()
+                                       .withId(FabricConstants.FABRIC_INGRESS_NEXT_OUTPUT_XCONNECT)
+                                       .withParameter(new PiActionParam(FabricConstants.PORT_NUM, PORT_2.toLong()))
+                                       .build())
+                .build();
+        TrafficSelector xcSelector2 = DefaultTrafficSelector.builder()
+                .matchPi(nextIdCriterion)
+                .matchInPort(PORT_2)
+                .build();
+        TrafficTreatment xcTreatment2 = DefaultTrafficTreatment.builder()
+                .piTableAction(PiAction.builder()
+                                       .withId(FabricConstants.FABRIC_INGRESS_NEXT_OUTPUT_XCONNECT)
+                                       .withParameter(new PiActionParam(FabricConstants.PORT_NUM, PORT_1.toLong()))
+                                       .build())
+                .build();
+
+        FlowRule expectedXcFlowRule1 = DefaultFlowRule.builder()
+                .forDevice(DEVICE_ID)
+                .fromApp(XCONNECT_APP_ID)
+                .makePermanent()
+                .withPriority(nextObjective.priority())
+                .forTable(FabricConstants.FABRIC_INGRESS_NEXT_XCONNECT)
+                .withSelector(xcSelector1)
+                .withTreatment(xcTreatment1)
+                .build();
+        FlowRule expectedXcFlowRule2 = DefaultFlowRule.builder()
+                .forDevice(DEVICE_ID)
+                .fromApp(XCONNECT_APP_ID)
+                .makePermanent()
+                .withPriority(nextObjective.priority())
+                .forTable(FabricConstants.FABRIC_INGRESS_NEXT_XCONNECT)
+                .withSelector(xcSelector2)
+                .withTreatment(xcTreatment2)
+                .build();
+
+        ObjectiveTranslation expectedTranslation = ObjectiveTranslation.builder()
+                .addFlowRule(expectedXcFlowRule1)
+                .addFlowRule(expectedXcFlowRule2)
+                .build();
+
+        assertEquals(expectedTranslation, actualTranslation);
+    }
 }
diff --git a/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricPipelinerTest.java b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricPipelinerTest.java
index a5704da..0b8a71c 100644
--- a/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricPipelinerTest.java
+++ b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricPipelinerTest.java
@@ -37,6 +37,7 @@
 
 public class FabricPipelinerTest {
     static final ApplicationId APP_ID = TestApplicationId.create("FabricPipelinerTest");
+    static final ApplicationId XCONNECT_APP_ID = TestApplicationId.create("FabricPipelinerTest.xconnect");
     static final DeviceId DEVICE_ID = DeviceId.deviceId("device:bmv2:11");
     static final int PRIORITY = 100;
     static final PortNumber PORT_1 = PortNumber.portNumber(1);