Support creation of vendor-specific versions of the fabric pipeconf

We provide a new service to facilitate registration of vendor-specific
versions of the Fabric pipeconf (e.g., for Tofino) from third-party
apps. This service is designed such that third-party apps do not need to
depend on internal classes at compile time, such as the behaviour
implementations.

To make this possible, the package structure has been refactored to
separate APIs from implementation.

Change-Id: I487cb806541eb9e6877ebf398a94f057613df8cc
(cherry picked from commit 36d5e7a2337c242e45ee57beacd82bba07a0851d)
diff --git a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/FabricFilteringPipelinerTest.java b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/FabricFilteringPipelinerTest.java
new file mode 100644
index 0000000..f40741f
--- /dev/null
+++ b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/FabricFilteringPipelinerTest.java
@@ -0,0 +1,501 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.pipelines.fabric.impl.behaviour.pipeliner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.PortNumber;
+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.TableId;
+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.DefaultFilteringObjective;
+import org.onosproject.net.flowobjective.FilteringObjective;
+import org.onosproject.net.flowobjective.ObjectiveError;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.pipelines.fabric.impl.behaviour.FabricConstants;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test cases for fabric.p4 pipeline filtering control block.
+ */
+public class FabricFilteringPipelinerTest extends FabricPipelinerTest {
+
+    public static final byte[] ONE = {1};
+    public static final byte[] ZERO = {0};
+    private FilteringObjectiveTranslator translator;
+
+    @Before
+    public void setup() {
+        super.doSetup();
+        translator = new FilteringObjectiveTranslator(DEVICE_ID, capabilitiesHashed);
+    }
+
+    /**
+     * Creates one rule for ingress_port_vlan table and 3 rules for
+     * fwd_classifier table (IPv4, IPv6 and MPLS unicast) when the condition is
+     * VLAN + MAC.
+     */
+    @Test
+    public void testRouterMacAndVlanFilter() throws FabricPipelinerException {
+        FilteringObjective filteringObjective = buildFilteringObjective(ROUTER_MAC);
+        ObjectiveTranslation actualTranslation = translator.translate(filteringObjective);
+
+        // in port vlan flow rule
+        FlowRule inportFlowRuleExpected = buildExpectedVlanInPortRule(
+                PORT_1,
+                VlanId.NONE,
+                VlanId.NONE,
+                VLAN_100,
+                FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN);
+
+        // forwarding classifier ipv4
+        FlowRule classifierV4FlowRuleExpected = buildExpectedFwdClassifierRule(
+                PORT_1,
+                ROUTER_MAC,
+                null,
+                Ethernet.TYPE_IPV4,
+                FilteringObjectiveTranslator.FWD_IPV4_ROUTING);
+
+        // forwarding classifier ipv6
+        FlowRule classifierV6FlowRuleExpected = buildExpectedFwdClassifierRule(
+                PORT_1,
+                ROUTER_MAC,
+                null,
+                Ethernet.TYPE_IPV6,
+                FilteringObjectiveTranslator.FWD_IPV6_ROUTING);
+
+        // forwarding classifier mpls
+        FlowRule classifierMplsFlowRuleExpected = buildExpectedFwdClassifierRule(
+                PORT_1,
+                ROUTER_MAC,
+                null,
+                Ethernet.MPLS_UNICAST,
+                FilteringObjectiveTranslator.FWD_MPLS);
+
+        ObjectiveTranslation expectedTranslation = ObjectiveTranslation.builder()
+                .addFlowRule(inportFlowRuleExpected)
+                .addFlowRule(classifierV4FlowRuleExpected)
+                .addFlowRule(classifierV6FlowRuleExpected)
+                .addFlowRule(classifierMplsFlowRuleExpected)
+                .build();
+
+        assertEquals(expectedTranslation, actualTranslation);
+    }
+
+    /**
+     * Creates one rule for ingress_port_vlan table and one rule for
+     * fwd_classifier table (IPv4 multicast) when the condition is ipv4
+     * multicast mac address.
+     */
+    @Test
+    public void testIpv4MulticastFwdClass() throws FabricPipelinerException {
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .pushVlan()
+                .setVlanId(VLAN_100)
+                .build();
+        FilteringObjective filteringObjective = DefaultFilteringObjective.builder()
+                .permit()
+                .withPriority(PRIORITY)
+                .withKey(Criteria.matchInPort(PORT_1))
+                .addCondition(Criteria.matchEthDstMasked(MacAddress.IPV4_MULTICAST, MacAddress.IPV4_MULTICAST_MASK))
+                .addCondition(Criteria.matchVlanId(VlanId.NONE))
+                .withMeta(treatment)
+                .fromApp(APP_ID)
+                .makePermanent()
+                .add();
+        ObjectiveTranslation actualTranslation = translator.translate(filteringObjective);
+
+        // in port vlan flow rule
+        FlowRule inportFlowRuleExpected = buildExpectedVlanInPortRule(
+                PORT_1,
+                VlanId.NONE,
+                VlanId.NONE,
+                VLAN_100,
+                FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN);
+
+        // forwarding classifier
+        FlowRule classifierFlowRuleExpected = buildExpectedFwdClassifierRule(
+                PORT_1,
+                MacAddress.IPV4_MULTICAST,
+                MacAddress.IPV4_MULTICAST_MASK,
+                Ethernet.TYPE_IPV4,
+                FilteringObjectiveTranslator.FWD_IPV4_ROUTING);
+
+        ObjectiveTranslation expectedTranslation = ObjectiveTranslation.builder()
+                .addFlowRule(inportFlowRuleExpected)
+                .addFlowRule(classifierFlowRuleExpected)
+                .build();
+
+        assertEquals(expectedTranslation, actualTranslation);
+    }
+
+    /**
+     * Creates one rule for ingress_port_vlan table and one rule for
+     * fwd_classifier table (IPv6 multicast) when the condition is ipv6
+     * multicast mac address.
+     */
+    @Test
+    public void testIpv6MulticastFwdClass() throws FabricPipelinerException {
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .pushVlan()
+                .setVlanId(VLAN_100)
+                .build();
+        FilteringObjective filteringObjective = DefaultFilteringObjective.builder()
+                .permit()
+                .withPriority(PRIORITY)
+                .withKey(Criteria.matchInPort(PORT_1))
+                .addCondition(Criteria.matchEthDstMasked(MacAddress.IPV6_MULTICAST, MacAddress.IPV6_MULTICAST_MASK))
+                .addCondition(Criteria.matchVlanId(VlanId.NONE))
+                .withMeta(treatment)
+                .fromApp(APP_ID)
+                .makePermanent()
+                .add();
+        ObjectiveTranslation actualTranslation = translator.translate(filteringObjective);
+
+        // in port vlan flow rule
+        FlowRule inportFlowRuleExpected = buildExpectedVlanInPortRule(
+                PORT_1,
+                VlanId.NONE,
+                VlanId.NONE,
+                VLAN_100,
+                FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN);
+
+        FlowRule classifierFlowRuleExpected = buildExpectedFwdClassifierRule(
+                PORT_1,
+                MacAddress.IPV6_MULTICAST,
+                MacAddress.IPV6_MULTICAST_MASK,
+                Ethernet.TYPE_IPV6,
+                FilteringObjectiveTranslator.FWD_IPV6_ROUTING);
+
+        ObjectiveTranslation expectedTranslation = ObjectiveTranslation.builder()
+                .addFlowRule(inportFlowRuleExpected)
+                .addFlowRule(classifierFlowRuleExpected)
+                .build();
+
+        assertEquals(expectedTranslation, actualTranslation);
+    }
+
+    /**
+     * Creates only one rule for ingress_port_vlan table if there is no
+     * condition of destination mac address. The packet will be handled by
+     * bridging table by default.
+     */
+    @Test
+    public void testFwdBridging() throws Exception {
+        FilteringObjective filteringObjective = buildFilteringObjective(null);
+        ObjectiveTranslation actualTranslation = translator.translate(filteringObjective);
+
+        // in port vlan flow rule
+        FlowRule flowRuleExpected = buildExpectedVlanInPortRule(
+                PORT_1,
+                VlanId.NONE,
+                VlanId.NONE,
+                VLAN_100,
+                FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN);
+
+        // No rules in forwarding classifier, will do default action: set fwd type to bridging
+
+        ObjectiveTranslation expectedTranslation = ObjectiveTranslation.builder()
+                .addFlowRule(flowRuleExpected)
+                .build();
+
+        assertEquals(expectedTranslation, actualTranslation);
+    }
+
+    /**
+     * Test DENY objective.
+     */
+    @Test
+    public void testDenyObjective() throws FabricPipelinerException {
+        FilteringObjective filteringObjective = DefaultFilteringObjective.builder()
+                .deny()
+                .withKey(Criteria.matchInPort(PORT_1))
+                .addCondition(Criteria.matchVlanId(VlanId.NONE))
+                .fromApp(APP_ID)
+                .makePermanent()
+                .withPriority(PRIORITY)
+                .add();
+
+        ObjectiveTranslation actualTranslation = translator.translate(filteringObjective);
+
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder()
+                .matchInPort(PORT_1)
+                .matchPi(buildPiCriterionVlan(null, null));
+        PiAction piAction = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_FILTERING_DENY)
+                .build();
+        FlowRule expectedFlowRule = DefaultFlowRule.builder()
+                .withPriority(PRIORITY)
+                .withSelector(selector.build())
+                .withTreatment(DefaultTrafficTreatment.builder()
+                                       .piTableAction(piAction).build())
+                .fromApp(APP_ID)
+                .forDevice(DEVICE_ID)
+                .makePermanent()
+                .forTable(FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN)
+                .build();
+
+        ObjectiveTranslation expectedTranslation = ObjectiveTranslation.builder()
+                .addFlowRule(expectedFlowRule)
+                .build();
+
+        assertEquals(expectedTranslation, actualTranslation);
+
+    }
+
+    /**
+     * Test double VLAN pop filtering objective Creates one rule for
+     * ingress_port_vlan table and 3 rules for fwd_classifier table (IPv4, IPv6
+     * and MPLS unicast) when the condition is MAC + VLAN + INNER_VLAN.
+     */
+    @Test
+    public void testPopVlan() throws FabricPipelinerException {
+        FilteringObjective filteringObjective = DefaultFilteringObjective.builder()
+                .withKey(Criteria.matchInPort(PORT_1))
+                .addCondition(Criteria.matchEthDst(ROUTER_MAC))
+                .addCondition(Criteria.matchVlanId(VLAN_100))
+                .addCondition(Criteria.matchInnerVlanId(VLAN_200))
+                .withPriority(PRIORITY)
+                .fromApp(APP_ID)
+                .withMeta(DefaultTrafficTreatment.builder()
+                                  .popVlan()
+                                  .build())
+                .permit()
+                .add();
+        ObjectiveTranslation actualTranslation = translator.translate(filteringObjective);
+
+        // Ingress port vlan rule
+        FlowRule inportFlowRuleExpected = buildExpectedVlanInPortRule(
+                PORT_1, VLAN_100, VLAN_200, VlanId.NONE,
+                FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN);
+        // Forwarding classifier rules (ipv6, ipv4, mpls)
+        FlowRule classifierV4FlowRuleExpected = buildExpectedFwdClassifierRule(
+                PORT_1, ROUTER_MAC, null, Ethernet.TYPE_IPV4,
+                FilteringObjectiveTranslator.FWD_IPV4_ROUTING);
+        FlowRule classifierV6FlowRuleExpected = buildExpectedFwdClassifierRule(
+                PORT_1, ROUTER_MAC, null, Ethernet.TYPE_IPV6,
+                FilteringObjectiveTranslator.FWD_IPV6_ROUTING);
+        FlowRule classifierMplsFlowRuleExpected = buildExpectedFwdClassifierRule(
+                PORT_1, ROUTER_MAC, null, Ethernet.MPLS_UNICAST,
+                FilteringObjectiveTranslator.FWD_MPLS);
+        ObjectiveTranslation expectedTranslation = ObjectiveTranslation.builder()
+                .addFlowRule(inportFlowRuleExpected)
+                .addFlowRule(classifierV4FlowRuleExpected)
+                .addFlowRule(classifierV6FlowRuleExpected)
+                .addFlowRule(classifierMplsFlowRuleExpected)
+                .build();
+
+        assertEquals(expectedTranslation, actualTranslation);
+    }
+
+    /**
+     * Incorrect filtering key or filtering conditions test.
+     */
+    @Test
+    public void badParamTest() {
+        // Filtering objective should contains filtering key
+        FilteringObjective filteringObjective = DefaultFilteringObjective.builder()
+                .permit()
+                .addCondition(Criteria.matchVlanId(VLAN_100))
+                .fromApp(APP_ID)
+                .makePermanent()
+                .add();
+
+        ObjectiveTranslation result1 = translator.translate(filteringObjective);
+        assertError(ObjectiveError.BADPARAMS, result1);
+
+        // Filtering objective should use in_port as key
+        filteringObjective = DefaultFilteringObjective.builder()
+                .permit()
+                .withKey(Criteria.matchEthDst(ROUTER_MAC))
+                .addCondition(Criteria.matchVlanId(VLAN_100))
+                .withMeta(DefaultTrafficTreatment.emptyTreatment())
+                .fromApp(APP_ID)
+                .makePermanent()
+                .add();
+
+        ObjectiveTranslation result2 = translator.translate(filteringObjective);
+        assertError(ObjectiveError.BADPARAMS, result2);
+    }
+
+    /**
+     * Test the mapping between EtherType and conditions.
+     */
+    @Test
+    public void testMappingEthType() {
+        PiCriterion expectedMappingDefault = PiCriterion.builder()
+                .matchExact(FabricConstants.HDR_IS_MPLS, ZERO)
+                .matchExact(FabricConstants.HDR_IS_IPV4, ZERO)
+                .matchExact(FabricConstants.HDR_IS_IPV6, ZERO)
+                .build();
+        PiCriterion expectedMappingIpv6 = PiCriterion.builder()
+                .matchExact(FabricConstants.HDR_IS_MPLS, ZERO)
+                .matchExact(FabricConstants.HDR_IS_IPV4, ZERO)
+                .matchExact(FabricConstants.HDR_IS_IPV6, ONE)
+                .build();
+        PiCriterion expectedMappingIpv4 = PiCriterion.builder()
+                .matchExact(FabricConstants.HDR_IS_MPLS, ZERO)
+                .matchExact(FabricConstants.HDR_IS_IPV4, ONE)
+                .matchExact(FabricConstants.HDR_IS_IPV6, ZERO)
+                .build();
+        PiCriterion expectedMappingMpls = PiCriterion.builder()
+                .matchExact(FabricConstants.HDR_IS_MPLS, ONE)
+                .matchExact(FabricConstants.HDR_IS_IPV4, ZERO)
+                .matchExact(FabricConstants.HDR_IS_IPV6, ZERO)
+                .build();
+
+
+        PiCriterion actualMappingIpv6 = FilteringObjectiveTranslator.mapEthTypeFwdClassifier(Ethernet.TYPE_IPV6);
+        assertEquals(expectedMappingIpv6, actualMappingIpv6);
+
+        PiCriterion actualMappingIpv4 = FilteringObjectiveTranslator.mapEthTypeFwdClassifier(Ethernet.TYPE_IPV4);
+        assertEquals(expectedMappingIpv4, actualMappingIpv4);
+
+        PiCriterion actualMappingMpls = FilteringObjectiveTranslator.mapEthTypeFwdClassifier(Ethernet.MPLS_UNICAST);
+        assertEquals(expectedMappingMpls, actualMappingMpls);
+
+        PiCriterion actualMapping = FilteringObjectiveTranslator.mapEthTypeFwdClassifier(Ethernet.TYPE_ARP);
+        assertEquals(expectedMappingDefault, actualMapping);
+
+
+    }
+
+    /* Utilities */
+
+    private void assertError(ObjectiveError error, ObjectiveTranslation actualTranslation) {
+        ObjectiveTranslation expectedTranslation = ObjectiveTranslation.ofError(error);
+        assertEquals(expectedTranslation, actualTranslation);
+    }
+
+    private FilteringObjective buildFilteringObjective(MacAddress dstMac) {
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .pushVlan()
+                .setVlanId(VLAN_100)
+                .build();
+        DefaultFilteringObjective.Builder builder = DefaultFilteringObjective.builder()
+                .permit()
+                .withPriority(PRIORITY)
+                .withKey(Criteria.matchInPort(PORT_1));
+        if (dstMac != null) {
+            builder.addCondition(Criteria.matchEthDst(dstMac));
+        }
+
+        builder.addCondition(Criteria.matchVlanId(VlanId.NONE))
+                .withMeta(treatment)
+                .fromApp(APP_ID)
+                .makePermanent();
+        return builder.add();
+    }
+
+    private FlowRule buildExpectedVlanInPortRule(PortNumber inPort,
+                                                 VlanId vlanId,
+                                                 VlanId innerVlanId,
+                                                 VlanId internalVlan,
+                                                 TableId tableId) {
+
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder()
+                .matchInPort(inPort);
+        PiAction piAction;
+        selector.matchPi(buildPiCriterionVlan(vlanId, innerVlanId));
+        if (!vlanValid(vlanId)) {
+            piAction = PiAction.builder()
+                    .withId(FabricConstants.FABRIC_INGRESS_FILTERING_PERMIT_WITH_INTERNAL_VLAN)
+                    .withParameter(new PiActionParam(
+                            FabricConstants.VLAN_ID, internalVlan.toShort()))
+                    .build();
+        } else {
+            selector.matchVlanId(vlanId);
+            if (vlanValid(innerVlanId)) {
+                selector.matchInnerVlanId(innerVlanId);
+            }
+            piAction = PiAction.builder()
+                    .withId(FabricConstants.FABRIC_INGRESS_FILTERING_PERMIT)
+                    .build();
+        }
+
+        return DefaultFlowRule.builder()
+                .withPriority(PRIORITY)
+                .withSelector(selector.build())
+                .withTreatment(DefaultTrafficTreatment.builder()
+                                       .piTableAction(piAction).build())
+                .fromApp(APP_ID)
+                .forDevice(DEVICE_ID)
+                .makePermanent()
+                .forTable(tableId)
+                .build();
+    }
+
+    private boolean vlanValid(VlanId vlanId) {
+        return (vlanId != null && !vlanId.equals(VlanId.NONE));
+    }
+
+    private PiCriterion buildPiCriterionVlan(VlanId vlanId,
+                                             VlanId innerVlanId) {
+        PiCriterion.Builder piCriterionBuilder = PiCriterion.builder()
+                .matchExact(FabricConstants.HDR_VLAN_IS_VALID,
+                            vlanValid(vlanId) ? ONE : ZERO);
+        return piCriterionBuilder.build();
+    }
+
+    private FlowRule buildExpectedFwdClassifierRule(PortNumber inPort,
+                                                    MacAddress dstMac,
+                                                    MacAddress dstMacMask,
+                                                    short ethType,
+                                                    byte fwdClass) {
+        TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder()
+                .matchInPort(inPort)
+                .matchPi(FilteringObjectiveTranslator.mapEthTypeFwdClassifier(ethType));
+        if (dstMacMask != null) {
+            sbuilder.matchEthDstMasked(dstMac, dstMacMask);
+        } else {
+            sbuilder.matchEthDstMasked(dstMac, MacAddress.EXACT_MASK);
+        }
+        TrafficSelector selector = sbuilder.build();
+
+        PiActionParam classParam = new PiActionParam(FabricConstants.FWD_TYPE,
+                                                     ImmutableByteSequence.copyFrom(fwdClass));
+        PiAction fwdClassifierAction = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_FILTERING_SET_FORWARDING_TYPE)
+                .withParameter(classParam)
+                .build();
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .piTableAction(fwdClassifierAction)
+                .build();
+
+        return DefaultFlowRule.builder()
+                .withPriority(PRIORITY)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .fromApp(APP_ID)
+                .forDevice(DEVICE_ID)
+                .makePermanent()
+                .forTable(FabricConstants.FABRIC_INGRESS_FILTERING_FWD_CLASSIFIER)
+                .build();
+    }
+}