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/FabricInterpreterTest.java b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricInterpreterTest.java
new file mode 100644
index 0000000..a0e6194
--- /dev/null
+++ b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricInterpreterTest.java
@@ -0,0 +1,213 @@
+/*
+ * 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;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.MplsLabel;
+import org.onlab.packet.VlanId;
+import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionParam;
+
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test for fabric interpreter.
+ */
+public class FabricInterpreterTest {
+    private static final VlanId VLAN_100 = VlanId.vlanId("100");
+    private static final PortNumber PORT_1 = PortNumber.portNumber(1);
+    private static final MacAddress SRC_MAC = MacAddress.valueOf("00:00:00:00:00:01");
+    private static final MacAddress DST_MAC = MacAddress.valueOf("00:00:00:00:00:02");
+    private static final MplsLabel MPLS_10 = MplsLabel.mplsLabel(10);
+
+    private FabricInterpreter interpreter;
+
+    FabricCapabilities allCapabilities;
+
+    @Before
+    public void setup() {
+        allCapabilities = createNiceMock(FabricCapabilities.class);
+        expect(allCapabilities.hasHashedTable()).andReturn(true).anyTimes();
+        expect(allCapabilities.supportDoubleVlanTerm()).andReturn(true).anyTimes();
+        replay(allCapabilities);
+        interpreter = new FabricInterpreter(allCapabilities);
+    }
+
+    /* Filtering control block */
+
+    /**
+     * Map treatment to push_internal_vlan action.
+     */
+    @Test
+    public void testFilteringTreatmentPermitWithInternalVlan() throws Exception {
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .pushVlan()
+                .setVlanId(VLAN_100)
+                .build();
+        PiAction mappedAction = interpreter.mapTreatment(treatment,
+                                                         FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN);
+        PiActionParam param = new PiActionParam(FabricConstants.VLAN_ID,
+                                                ImmutableByteSequence.copyFrom(VLAN_100.toShort()));
+        PiAction expectedAction = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_FILTERING_PERMIT_WITH_INTERNAL_VLAN)
+                .withParameter(param)
+                .build();
+
+        assertEquals(expectedAction, mappedAction);
+    }
+
+    /**
+     * Map treatment to permit action.
+     */
+    @Test
+    public void testFilteringTreatmentPermit() throws Exception {
+        TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment();
+        PiAction mappedAction = interpreter.mapTreatment(treatment,
+                                                         FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN);
+        PiAction expectedAction = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_FILTERING_PERMIT)
+                .build();
+
+        assertEquals(expectedAction, mappedAction);
+    }
+
+    /* Forwarding control block */
+
+    /**
+     * Map empty treatment for routing v4 table.
+     */
+    @Test
+    public void testRoutingV4TreatmentEmpty() throws Exception {
+        TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment();
+        PiAction mappedAction = interpreter.mapTreatment(
+                treatment, FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4);
+        PiAction expectedAction = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_FORWARDING_NOP_ROUTING_V4)
+                .build();
+        assertEquals(expectedAction, mappedAction);
+    }
+
+    /**
+     * Map empty treatment for ACL table.
+     */
+    @Test
+    public void testAclTreatmentEmpty() throws Exception {
+        TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment();
+        PiAction mappedAction = interpreter.mapTreatment(
+                treatment, FabricConstants.FABRIC_INGRESS_ACL_ACL);
+        PiAction expectedAction = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_ACL_NOP_ACL)
+                .build();
+        assertEquals(expectedAction, mappedAction);
+    }
+
+    /* Next control block */
+
+    /**
+     * Map treatment to output action.
+     */
+    @Test
+    public void testNextTreatmentSimpleOutput() throws Exception {
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(PORT_1)
+                .build();
+        PiAction mappedAction = interpreter.mapTreatment(
+                treatment, FabricConstants.FABRIC_INGRESS_NEXT_SIMPLE);
+        PiActionParam param = new PiActionParam(FabricConstants.PORT_NUM, PORT_1.toLong());
+        PiAction expectedAction = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_NEXT_OUTPUT_SIMPLE)
+                .withParameter(param)
+                .build();
+        assertEquals(expectedAction, mappedAction);
+    }
+
+    /**
+     * Map treatment for hashed table to routing v4 action.
+     */
+    @Test
+    public void testNextTreatmentHashedRoutingV4() throws Exception {
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setEthSrc(SRC_MAC)
+                .setEthDst(DST_MAC)
+                .setOutput(PORT_1)
+                .build();
+        PiAction mappedAction = interpreter.mapTreatment(
+                treatment, FabricConstants.FABRIC_INGRESS_NEXT_HASHED);
+        PiActionParam ethSrcParam = new PiActionParam(FabricConstants.SMAC, SRC_MAC.toBytes());
+        PiActionParam ethDstParam = new PiActionParam(FabricConstants.DMAC, DST_MAC.toBytes());
+        PiActionParam portParam = new PiActionParam(FabricConstants.PORT_NUM, PORT_1.toLong());
+        PiAction expectedAction = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_NEXT_ROUTING_HASHED)
+                .withParameters(ImmutableList.of(ethSrcParam, ethDstParam, portParam))
+                .build();
+        assertEquals(expectedAction, mappedAction);
+    }
+
+    /**
+     * Map treatment for hashed table to routing v4 action.
+     */
+    @Test
+    public void testNextTreatmentHashedRoutingMpls() throws Exception {
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setEthSrc(SRC_MAC)
+                .setEthDst(DST_MAC)
+                .setOutput(PORT_1)
+                .pushMpls()
+                .setMpls(MPLS_10)
+                .build();
+        PiAction mappedAction = interpreter.mapTreatment(
+                treatment, FabricConstants.FABRIC_INGRESS_NEXT_HASHED);
+        PiActionParam ethSrcParam = new PiActionParam(FabricConstants.SMAC, SRC_MAC.toBytes());
+        PiActionParam ethDstParam = new PiActionParam(FabricConstants.DMAC, DST_MAC.toBytes());
+        PiActionParam portParam = new PiActionParam(FabricConstants.PORT_NUM, PORT_1.toLong());
+        PiActionParam mplsParam = new PiActionParam(FabricConstants.LABEL, MPLS_10.toInt());
+        PiAction expectedAction = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_NEXT_MPLS_ROUTING_HASHED)
+                .withParameters(ImmutableList.of(ethSrcParam, ethDstParam, portParam, mplsParam))
+                .build();
+        assertEquals(expectedAction, mappedAction);
+    }
+
+    /**
+     * Map treatment to set_vlan_output action.
+     */
+    @Test
+    public void testNextTreatment3() throws Exception {
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setVlanId(VLAN_100)
+                .build();
+        PiAction mappedAction = interpreter.mapTreatment(
+                treatment, FabricConstants.FABRIC_INGRESS_NEXT_NEXT_VLAN);
+        PiActionParam vlanParam = new PiActionParam(
+                FabricConstants.VLAN_ID, VLAN_100.toShort());
+        PiAction expectedAction = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_NEXT_SET_VLAN)
+                .withParameter(vlanParam)
+                .build();
+        assertEquals(expectedAction, mappedAction);
+    }
+}
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();
+    }
+}
diff --git a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/FabricForwardingPipelineTest.java b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/FabricForwardingPipelineTest.java
new file mode 100644
index 0000000..3d2942f
--- /dev/null
+++ b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/FabricForwardingPipelineTest.java
@@ -0,0 +1,383 @@
+/*
+ * 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 com.google.common.collect.ImmutableList;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.UDP;
+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.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.EthCriterion;
+import org.onosproject.net.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.group.DefaultGroupBucket;
+import org.onosproject.net.group.DefaultGroupDescription;
+import org.onosproject.net.group.DefaultGroupKey;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.pi.model.PiTableId;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.pipelines.fabric.impl.behaviour.FabricConstants;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test cases for fabric.p4 pipeline forwarding control block.
+ */
+public class FabricForwardingPipelineTest extends FabricPipelinerTest {
+
+    private ForwardingObjectiveTranslator translator;
+
+    @Before
+    public void setup() {
+        super.doSetup();
+        translator = new ForwardingObjectiveTranslator(DEVICE_ID, capabilitiesHashed);
+    }
+
+    /**
+     * Test versatile flag of forwarding objective with ARP match.
+     */
+    @Test
+    public void testAclArp() {
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .punt()
+                .build();
+        // ARP
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_ARP)
+                .build();
+        ForwardingObjective fwd = DefaultForwardingObjective.builder()
+                .withSelector(selector)
+                .withPriority(PRIORITY)
+                .fromApp(APP_ID)
+                .makePermanent()
+                .withFlag(ForwardingObjective.Flag.VERSATILE)
+                .withTreatment(treatment)
+                .add();
+
+        ObjectiveTranslation result = translator.translate(fwd);
+
+        List<FlowRule> flowRulesInstalled = (List<FlowRule>) result.flowRules();
+        List<GroupDescription> groupsInstalled = (List<GroupDescription>) result.groups();
+        assertEquals(1, flowRulesInstalled.size());
+        assertEquals(1, groupsInstalled.size());
+
+        FlowRule actualFlowRule = flowRulesInstalled.get(0);
+        PiAction piAction = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_ACL_SET_CLONE_SESSION_ID)
+                .withParameter(new PiActionParam(
+                        FabricConstants.CLONE_ID,
+                        ForwardingObjectiveTranslator.CLONE_TO_CPU_ID))
+                .build();
+        FlowRule expectedFlowRule = DefaultFlowRule.builder()
+                .forDevice(DEVICE_ID)
+                .forTable(FabricConstants.FABRIC_INGRESS_ACL_ACL)
+                .withPriority(PRIORITY)
+                .makePermanent()
+                .withSelector(selector)
+                .withTreatment(DefaultTrafficTreatment.builder()
+                                       .piTableAction(piAction).build())
+                .fromApp(APP_ID)
+                .build();
+
+        GroupDescription actualCloneGroup = groupsInstalled.get(0);
+        TrafficTreatment cloneGroupTreatment = DefaultTrafficTreatment.builder()
+                .setOutput(PortNumber.CONTROLLER)
+                .build();
+
+        List<GroupBucket> cloneBuckets = ImmutableList.of(
+                DefaultGroupBucket.createCloneGroupBucket(cloneGroupTreatment));
+
+        GroupBuckets cloneGroupBuckets = new GroupBuckets(cloneBuckets);
+        GroupKey cloneGroupKey = new DefaultGroupKey(
+                FabricPipeliner.KRYO.serialize(ForwardingObjectiveTranslator.CLONE_TO_CPU_ID));
+        GroupDescription expectedCloneGroup = new DefaultGroupDescription(
+                DEVICE_ID,
+                GroupDescription.Type.CLONE,
+                cloneGroupBuckets,
+                cloneGroupKey,
+                ForwardingObjectiveTranslator.CLONE_TO_CPU_ID,
+                APP_ID
+        );
+
+        assertTrue(expectedFlowRule.exactMatch(actualFlowRule));
+        assertTrue(expectedCloneGroup.equals(actualCloneGroup));
+    }
+
+    /**
+     * Test versatile flag of forwarding objective with DHCP match.
+     */
+    @Test
+    public void testAclDhcp() {
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .wipeDeferred()
+                .punt()
+                .build();
+        // DHCP
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(IPv4.PROTOCOL_UDP)
+                .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
+                .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
+                .build();
+        ForwardingObjective fwd = DefaultForwardingObjective.builder()
+                .withSelector(selector)
+                .withPriority(PRIORITY)
+                .fromApp(APP_ID)
+                .makePermanent()
+                .withFlag(ForwardingObjective.Flag.VERSATILE)
+                .withTreatment(treatment)
+                .add();
+
+        ObjectiveTranslation result = translator.translate(fwd);
+
+        List<FlowRule> flowRulesInstalled = (List<FlowRule>) result.flowRules();
+        List<GroupDescription> groupsInstalled = (List<GroupDescription>) result.groups();
+        assertEquals(1, flowRulesInstalled.size());
+        assertTrue(groupsInstalled.isEmpty());
+
+        FlowRule actualFlowRule = flowRulesInstalled.get(0);
+        PiAction piAction = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_ACL_PUNT_TO_CPU)
+                .build();
+        FlowRule expectedFlowRule = DefaultFlowRule.builder()
+                .forDevice(DEVICE_ID)
+                .forTable(FabricConstants.FABRIC_INGRESS_ACL_ACL)
+                .withPriority(PRIORITY)
+                .makePermanent()
+                .withSelector(selector)
+                .withTreatment(DefaultTrafficTreatment.builder()
+                                       .piTableAction(piAction).build())
+                .fromApp(APP_ID)
+                .build();
+
+        assertTrue(expectedFlowRule.exactMatch(actualFlowRule));
+    }
+
+    /**
+     * Test programming L2 unicast rule to bridging table.
+     */
+    @Test
+    public void testL2Unicast() throws FabricPipelinerException {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchVlanId(VLAN_100)
+                .matchEthDst(HOST_MAC)
+                .build();
+        testSpecificForward(FabricConstants.FABRIC_INGRESS_FORWARDING_BRIDGING,
+                            buildExpectedSelector(selector), selector, NEXT_ID_1);
+    }
+
+    @Test
+    public void testL2Broadcast() throws FabricPipelinerException {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchVlanId(VLAN_100)
+                .build();
+        testSpecificForward(FabricConstants.FABRIC_INGRESS_FORWARDING_BRIDGING,
+                            selector, selector, NEXT_ID_1);
+    }
+
+    @Test
+    public void testIPv4Unicast() throws FabricPipelinerException {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(IPV4_UNICAST_ADDR)
+                .build();
+        TrafficSelector expectedSelector = DefaultTrafficSelector.builder()
+                .matchIPDst(IPV4_UNICAST_ADDR)
+                .build();
+        testSpecificForward(FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4,
+                            expectedSelector, selector, NEXT_ID_1);
+    }
+
+    @Test
+    public void testIPv4UnicastWithNoNextId() throws FabricPipelinerException {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(IPV4_UNICAST_ADDR)
+                .build();
+        TrafficSelector expectedSelector = DefaultTrafficSelector.builder()
+                .matchIPDst(IPV4_UNICAST_ADDR)
+                .build();
+        testSpecificForward(FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4,
+                            expectedSelector, selector, null);
+    }
+
+    @Test
+    @Ignore
+    public void testIPv4Multicast() throws FabricPipelinerException {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchVlanId(VLAN_100)
+                .matchIPDst(IPV4_MCAST_ADDR)
+                .build();
+        TrafficSelector expectedSelector = DefaultTrafficSelector.builder()
+                .matchIPDst(IPV4_MCAST_ADDR)
+                .build();
+        testSpecificForward(FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4,
+                            expectedSelector, selector, NEXT_ID_1);
+    }
+
+    @Test
+    @Ignore
+    public void testIPv6Unicast() throws FabricPipelinerException {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV6)
+                .matchIPDst(IPV6_UNICAST_ADDR)
+                .build();
+        TrafficSelector expectedSelector = DefaultTrafficSelector.builder()
+                .matchIPDst(IPV6_UNICAST_ADDR)
+                .build();
+        testSpecificForward(FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V6,
+                            expectedSelector, selector, NEXT_ID_1);
+
+    }
+
+    @Test
+    @Ignore
+    public void testIPv6Multicast() throws FabricPipelinerException {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV6)
+                .matchVlanId(VLAN_100)
+                .matchIPDst(IPV6_MCAST_ADDR)
+                .build();
+        TrafficSelector expectedSelector = DefaultTrafficSelector.builder()
+                .matchIPDst(IPV6_MCAST_ADDR)
+                .build();
+        testSpecificForward(FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V6,
+                            expectedSelector, selector, NEXT_ID_1);
+    }
+
+    @Test
+    public void testMpls() throws FabricPipelinerException {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.MPLS_UNICAST)
+                .matchMplsLabel(MPLS_10)
+                .matchMplsBos(true)
+                .build();
+        TrafficSelector expectedSelector = DefaultTrafficSelector.builder()
+                .matchMplsLabel(MPLS_10)
+                .build();
+
+        PiActionParam nextIdParam = new PiActionParam(FabricConstants.NEXT_ID, NEXT_ID_1);
+        PiAction setNextIdAction = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_FORWARDING_POP_MPLS_AND_NEXT)
+                .withParameter(nextIdParam)
+                .build();
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .piTableAction(setNextIdAction)
+                .build();
+        testSpecificForward(FabricConstants.FABRIC_INGRESS_FORWARDING_MPLS,
+                            expectedSelector, selector, NEXT_ID_1, treatment);
+    }
+
+    private void testSpecificForward(PiTableId expectedTableId, TrafficSelector expectedSelector,
+                                     TrafficSelector selector, Integer nextId) throws FabricPipelinerException {
+        TrafficTreatment setNextIdTreatment;
+        if (nextId == null) {
+            // Ref: RoutingRulePopulator.java->revokeIpRuleForRouter
+
+            setNextIdTreatment = DefaultTrafficTreatment.builder().
+                    piTableAction(PiAction.builder()
+                                          .withId(FabricConstants.FABRIC_INGRESS_FORWARDING_NOP_ROUTING_V4)
+                                          .build())
+                    .build();
+        } else {
+            PiActionParam nextIdParam = new PiActionParam(FabricConstants.NEXT_ID, nextId);
+            PiAction.Builder setNextIdAction = PiAction.builder()
+                    .withParameter(nextIdParam);
+
+            if (expectedTableId.equals(FabricConstants.FABRIC_INGRESS_FORWARDING_BRIDGING)) {
+                setNextIdAction.withId(FabricConstants.FABRIC_INGRESS_FORWARDING_SET_NEXT_ID_BRIDGING);
+            } else if (expectedTableId.equals(FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4)) {
+                setNextIdAction.withId(FabricConstants.FABRIC_INGRESS_FORWARDING_SET_NEXT_ID_ROUTING_V4);
+            } else if (expectedTableId.equals(FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V6)) {
+                setNextIdAction.withId(FabricConstants.FABRIC_INGRESS_FORWARDING_SET_NEXT_ID_ROUTING_V6);
+            }
+
+            setNextIdTreatment = DefaultTrafficTreatment.builder()
+                    .piTableAction(setNextIdAction.build())
+                    .build();
+        }
+
+        testSpecificForward(expectedTableId, expectedSelector, selector, nextId, setNextIdTreatment);
+
+    }
+
+    private void testSpecificForward(PiTableId expectedTableId, TrafficSelector expectedSelector,
+                                     TrafficSelector selector, Integer nextId, TrafficTreatment treatment)
+            throws FabricPipelinerException {
+        ForwardingObjective.Builder fwd = DefaultForwardingObjective.builder()
+                .withSelector(selector)
+                .withPriority(PRIORITY)
+                .fromApp(APP_ID)
+                .makePermanent()
+                .withTreatment(treatment)
+                .withFlag(ForwardingObjective.Flag.SPECIFIC);
+
+        if (nextId != null) {
+            fwd.nextStep(nextId);
+        }
+
+        ObjectiveTranslation actualTranslation = translator.translate(fwd.add());
+
+        FlowRule expectedFlowRule = DefaultFlowRule.builder()
+                .forDevice(DEVICE_ID)
+                .forTable(expectedTableId)
+                .withPriority(PRIORITY)
+                .makePermanent()
+                .withSelector(expectedSelector)
+                .withTreatment(treatment)
+                .fromApp(APP_ID)
+                .build();
+
+        ObjectiveTranslation expectedTranslation = ObjectiveTranslation.builder()
+                .addFlowRule(expectedFlowRule)
+                .build();
+
+        assertEquals(expectedTranslation, actualTranslation);
+    }
+
+    private TrafficSelector buildExpectedSelector(TrafficSelector selector) {
+        TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
+        selector.criteria().forEach(c -> {
+            if (c.type() == Criterion.Type.ETH_DST) {
+                sbuilder.matchEthDstMasked(((EthCriterion) c).mac(), MacAddress.EXACT_MASK);
+            } else {
+                sbuilder.add(c);
+            }
+        });
+        return sbuilder.build();
+    }
+}
diff --git a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/FabricNextPipelinerTest.java b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/FabricNextPipelinerTest.java
new file mode 100644
index 0000000..a7d03c2
--- /dev/null
+++ b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/FabricNextPipelinerTest.java
@@ -0,0 +1,576 @@
+/*
+ * 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 com.google.common.collect.ImmutableList;
+import org.junit.Before;
+import org.junit.Test;
+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.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.PiCriterion;
+import org.onosproject.net.flowobjective.DefaultNextObjective;
+import org.onosproject.net.flowobjective.NextObjective;
+import org.onosproject.net.group.DefaultGroupBucket;
+import org.onosproject.net.group.DefaultGroupDescription;
+import org.onosproject.net.group.DefaultGroupKey;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.net.pi.runtime.PiActionProfileGroupId;
+import org.onosproject.net.pi.runtime.PiGroupKey;
+import org.onosproject.pipelines.fabric.impl.behaviour.FabricConstants;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test cases for fabric.p4 pipeline next control block.
+ */
+public class FabricNextPipelinerTest extends FabricPipelinerTest {
+
+    private NextObjectiveTranslator translatorHashed;
+    private NextObjectiveTranslator translatorSimple;
+
+    private FlowRule vlanMetaFlowRule;
+
+    @Before
+    public void setup() {
+        super.doSetup();
+
+        translatorHashed = new NextObjectiveTranslator(DEVICE_ID, capabilitiesHashed);
+        translatorSimple = new NextObjectiveTranslator(DEVICE_ID, capabilitiesSimple);
+
+        PiCriterion nextIdCriterion = PiCriterion.builder()
+                .matchExact(FabricConstants.HDR_NEXT_ID, NEXT_ID_1)
+                .build();
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchPi(nextIdCriterion)
+                .build();
+        PiAction piAction = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_NEXT_SET_VLAN)
+                .withParameter(new PiActionParam(FabricConstants.VLAN_ID, VLAN_100.toShort()))
+                .build();
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .piTableAction(piAction)
+                .build();
+        vlanMetaFlowRule = DefaultFlowRule.builder()
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .forTable(FabricConstants.FABRIC_INGRESS_NEXT_NEXT_VLAN)
+                .makePermanent()
+                // FIXME: currently next objective doesn't support priority, ignore this
+                .withPriority(0)
+                .forDevice(DEVICE_ID)
+                .fromApp(APP_ID)
+                .build();
+    }
+
+    /**
+     * Test program output rule for Simple table.
+     */
+    @Test
+    public void testSimpleOutput() throws FabricPipelinerException {
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(PORT_1)
+                .build();
+        PiAction piAction = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_NEXT_OUTPUT_SIMPLE)
+                .withParameter(new PiActionParam(
+                        FabricConstants.PORT_NUM, PORT_1.toLong()))
+                .build();
+        testSimple(treatment, piAction);
+    }
+
+    /**
+     * Test program set vlan and output rule for Simple table.
+     */
+    @Test
+    public void testSimpleOutputWithVlanTranslation() throws FabricPipelinerException {
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setVlanId(VLAN_100)
+                .setOutput(PORT_1)
+                .build();
+        PiAction piAction = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_NEXT_OUTPUT_SIMPLE)
+                .withParameter(new PiActionParam(
+                        FabricConstants.PORT_NUM, PORT_1.toLong()))
+                .build();
+        testSimple(treatment, piAction);
+    }
+
+    /**
+     * Test program set mac and output rule for Simple table.
+     */
+    @Test
+    public void testSimpleOutputWithMacTranslation() throws FabricPipelinerException {
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setEthSrc(ROUTER_MAC)
+                .setEthDst(HOST_MAC)
+                .setOutput(PORT_1)
+                .build();
+        PiAction piAction = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_NEXT_ROUTING_SIMPLE)
+                .withParameter(new PiActionParam(
+                        FabricConstants.SMAC, ROUTER_MAC.toBytes()))
+                .withParameter(new PiActionParam(
+                        FabricConstants.DMAC, HOST_MAC.toBytes()))
+                .withParameter(new PiActionParam(
+                        FabricConstants.PORT_NUM, PORT_1.toLong()))
+                .build();
+        testSimple(treatment, piAction);
+    }
+
+    /**
+     * Test program set mac, set vlan, and output rule for Simple table.
+     */
+    @Test
+    public void testSimpleOutputWithVlanAndMacTranslation() throws FabricPipelinerException {
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setEthSrc(ROUTER_MAC)
+                .setEthDst(HOST_MAC)
+                .setVlanId(VLAN_100)
+                .setOutput(PORT_1)
+                .build();
+        PiAction piAction = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_NEXT_ROUTING_SIMPLE)
+                .withParameter(new PiActionParam(
+                        FabricConstants.SMAC, ROUTER_MAC.toBytes()))
+                .withParameter(new PiActionParam(
+                        FabricConstants.DMAC, HOST_MAC.toBytes()))
+                .withParameter(new PiActionParam(
+                        FabricConstants.PORT_NUM, PORT_1.toLong()))
+                .build();
+        testSimple(treatment, piAction);
+    }
+
+    private void testSimple(TrafficTreatment treatment, PiAction piAction) throws FabricPipelinerException {
+        NextObjective nextObjective = DefaultNextObjective.builder()
+                .withId(NEXT_ID_1)
+                .withPriority(PRIORITY)
+                .withMeta(VLAN_META)
+                .addTreatment(treatment)
+                .withType(NextObjective.Type.SIMPLE)
+                .makePermanent()
+                .fromApp(APP_ID)
+                .add();
+
+        ObjectiveTranslation actualTranslation = translatorSimple.translate(nextObjective);
+
+        // Simple table
+        PiCriterion nextIdCriterion = PiCriterion.builder()
+                .matchExact(FabricConstants.HDR_NEXT_ID, NEXT_ID_1)
+                .build();
+        TrafficSelector nextIdSelector = DefaultTrafficSelector.builder()
+                .matchPi(nextIdCriterion)
+                .build();
+        FlowRule expectedFlowRule = DefaultFlowRule.builder()
+                .forDevice(DEVICE_ID)
+                .fromApp(APP_ID)
+                .makePermanent()
+                // FIXME: currently next objective doesn't support priority, ignore this
+                .withPriority(0)
+                .forTable(FabricConstants.FABRIC_INGRESS_NEXT_SIMPLE)
+                .withSelector(nextIdSelector)
+                .withTreatment(DefaultTrafficTreatment.builder()
+                                       .piTableAction(piAction).build())
+                .build();
+
+        ObjectiveTranslation expectedTranslation = ObjectiveTranslation.builder()
+                .addFlowRule(vlanMetaFlowRule)
+                .addFlowRule(expectedFlowRule)
+                .build();
+
+        assertEquals(expectedTranslation, actualTranslation);
+    }
+
+    /**
+     * Test Route and Push Next Objective (set mac, set double vlan and output port).
+     */
+    @Test
+    public void testRouteAndPushNextObjective() throws FabricPipelinerException {
+        TrafficTreatment routeAndPushTreatment = DefaultTrafficTreatment.builder()
+                .setEthSrc(ROUTER_MAC)
+                .setEthDst(HOST_MAC)
+                .setOutput(PORT_1)
+                .setVlanId(VLAN_100)
+                .pushVlan()
+                .setVlanId(VLAN_200)
+                .build();
+
+        NextObjective nextObjective = DefaultNextObjective.builder()
+                .withId(NEXT_ID_1)
+                .withPriority(PRIORITY)
+                .addTreatment(routeAndPushTreatment)
+                .withType(NextObjective.Type.SIMPLE)
+                .makePermanent()
+                .fromApp(APP_ID)
+                .add();
+
+        ObjectiveTranslation actualTranslation = translatorSimple.translate(nextObjective);
+
+        PiAction piActionRouting = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_NEXT_ROUTING_SIMPLE)
+                .withParameter(new PiActionParam(
+                        FabricConstants.SMAC, ROUTER_MAC.toBytes()))
+                .withParameter(new PiActionParam(
+                        FabricConstants.DMAC, HOST_MAC.toBytes()))
+                .withParameter(new PiActionParam(
+                        FabricConstants.PORT_NUM, PORT_1.toLong()))
+                .build();
+
+        PiAction piActionPush = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_NEXT_SET_DOUBLE_VLAN)
+                .withParameter(new PiActionParam(
+                        FabricConstants.INNER_VLAN_ID, VLAN_100.toShort()))
+                .withParameter(new PiActionParam(
+                        FabricConstants.OUTER_VLAN_ID, VLAN_200.toShort()))
+                .build();
+
+
+        TrafficSelector nextIdSelector = DefaultTrafficSelector.builder()
+                .matchPi(PiCriterion.builder()
+                                 .matchExact(FabricConstants.HDR_NEXT_ID, NEXT_ID_1)
+                                 .build())
+                .build();
+        FlowRule expectedFlowRuleRouting = DefaultFlowRule.builder()
+                .forDevice(DEVICE_ID)
+                .fromApp(APP_ID)
+                .makePermanent()
+                // FIXME: currently next objective doesn't support priority, ignore this
+                .withPriority(0)
+                .forTable(FabricConstants.FABRIC_INGRESS_NEXT_SIMPLE)
+                .withSelector(nextIdSelector)
+                .withTreatment(DefaultTrafficTreatment.builder()
+                                       .piTableAction(piActionRouting).build())
+                .build();
+        FlowRule expectedFlowRuleDoublePush = DefaultFlowRule.builder()
+                .withSelector(nextIdSelector)
+                .withTreatment(DefaultTrafficTreatment.builder()
+                                       .piTableAction(piActionPush)
+                                       .build())
+                .forTable(FabricConstants.FABRIC_INGRESS_NEXT_NEXT_VLAN)
+                .makePermanent()
+                // FIXME: currently next objective doesn't support priority, ignore this
+                .withPriority(0)
+                .forDevice(DEVICE_ID)
+                .fromApp(APP_ID)
+                .build();
+
+        ObjectiveTranslation expectedTranslation = ObjectiveTranslation.builder()
+                .addFlowRule(expectedFlowRuleDoublePush)
+                .addFlowRule(expectedFlowRuleRouting)
+                .build();
+
+
+        assertEquals(expectedTranslation, actualTranslation);
+    }
+
+    /**
+     * Test program ecmp output group for Hashed table.
+     */
+    @Test
+    public void testHashedOutput() throws Exception {
+        PiAction piAction1 = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_NEXT_ROUTING_HASHED)
+                .withParameter(new PiActionParam(
+                        FabricConstants.SMAC, ROUTER_MAC.toBytes()))
+                .withParameter(new PiActionParam(
+                        FabricConstants.DMAC, HOST_MAC.toBytes()))
+                .withParameter(new PiActionParam(
+                        FabricConstants.PORT_NUM, PORT_1.toLong()))
+                .build();
+        PiAction piAction2 = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_NEXT_ROUTING_HASHED)
+                .withParameter(new PiActionParam(
+                        FabricConstants.SMAC, ROUTER_MAC.toBytes()))
+                .withParameter(new PiActionParam(
+                        FabricConstants.DMAC, HOST_MAC.toBytes()))
+                .withParameter(new PiActionParam(
+                        FabricConstants.PORT_NUM, PORT_1.toLong()))
+                .build();
+        TrafficTreatment treatment1 = DefaultTrafficTreatment.builder()
+                .piTableAction(piAction1)
+                .build();
+        TrafficTreatment treatment2 = DefaultTrafficTreatment.builder()
+                .piTableAction(piAction2)
+                .build();
+
+        NextObjective nextObjective = DefaultNextObjective.builder()
+                .withId(NEXT_ID_1)
+                .withPriority(PRIORITY)
+                .withMeta(VLAN_META)
+                .addTreatment(treatment1)
+                .addTreatment(treatment2)
+                .withType(NextObjective.Type.HASHED)
+                .makePermanent()
+                .fromApp(APP_ID)
+                .add();
+
+        ObjectiveTranslation actualTranslation = translatorHashed.doTranslate(nextObjective);
+
+        // Expected hashed table flow rule.
+        PiCriterion nextIdCriterion = PiCriterion.builder()
+                .matchExact(FabricConstants.HDR_NEXT_ID, NEXT_ID_1)
+                .build();
+        TrafficSelector nextIdSelector = DefaultTrafficSelector.builder()
+                .matchPi(nextIdCriterion)
+                .build();
+        PiActionProfileGroupId actionGroupId = PiActionProfileGroupId.of(NEXT_ID_1);
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .piTableAction(actionGroupId)
+                .build();
+        FlowRule expectedFlowRule = DefaultFlowRule.builder()
+                .forDevice(DEVICE_ID)
+                .fromApp(APP_ID)
+                .makePermanent()
+                // FIXME: currently next objective doesn't support priority, ignore this
+                .withPriority(0)
+                .forTable(FabricConstants.FABRIC_INGRESS_NEXT_HASHED)
+                .withSelector(nextIdSelector)
+                .withTreatment(treatment)
+                .build();
+
+        // Expected group
+        List<TrafficTreatment> treatments = ImmutableList.of(treatment1, treatment2);
+        List<GroupBucket> buckets = treatments.stream()
+                .map(DefaultGroupBucket::createSelectGroupBucket)
+                .collect(Collectors.toList());
+        GroupBuckets groupBuckets = new GroupBuckets(buckets);
+        PiGroupKey groupKey = new PiGroupKey(FabricConstants.FABRIC_INGRESS_NEXT_HASHED,
+                                             FabricConstants.FABRIC_INGRESS_NEXT_HASHED_SELECTOR,
+                                             NEXT_ID_1);
+        GroupDescription expectedGroup = new DefaultGroupDescription(
+                DEVICE_ID,
+                GroupDescription.Type.SELECT,
+                groupBuckets,
+                groupKey,
+                NEXT_ID_1,
+                APP_ID
+        );
+
+        ObjectiveTranslation expectedTranslation = ObjectiveTranslation.builder()
+                .addFlowRule(expectedFlowRule)
+                .addFlowRule(vlanMetaFlowRule)
+                .addGroup(expectedGroup)
+                .build();
+
+        assertEquals(expectedTranslation, actualTranslation);
+
+    }
+
+    /**
+     * Test program output group for Broadcast table.
+     */
+    @Test
+    public void testBroadcastOutput() throws FabricPipelinerException {
+        TrafficTreatment treatment1 = DefaultTrafficTreatment.builder()
+                .setOutput(PORT_1)
+                .build();
+        TrafficTreatment treatment2 = DefaultTrafficTreatment.builder()
+                .popVlan()
+                .setOutput(PORT_2)
+                .build();
+        NextObjective nextObjective = DefaultNextObjective.builder()
+                .withId(NEXT_ID_1)
+                .withPriority(PRIORITY)
+                .addTreatment(treatment1)
+                .addTreatment(treatment2)
+                .withMeta(VLAN_META)
+                .withType(NextObjective.Type.BROADCAST)
+                .makePermanent()
+                .fromApp(APP_ID)
+                .add();
+
+        ObjectiveTranslation actualTranslation = translatorHashed.doTranslate(nextObjective);
+
+        // Should generate 3 flows:
+        // - Multicast table flow that matches on next-id and set multicast group (1)
+        // - Egress VLAN pop handling for treatment2 (0)
+        // - Next VLAN flow (2)
+        // And 2 groups:
+        // - Multicast group
+
+        // Expected multicast table flow rule.
+        PiCriterion nextIdCriterion = PiCriterion.builder()
+                .matchExact(FabricConstants.HDR_NEXT_ID, NEXT_ID_1)
+                .build();
+        TrafficSelector nextIdSelector = DefaultTrafficSelector.builder()
+                .matchPi(nextIdCriterion)
+                .build();
+        PiAction setMcGroupAction = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_NEXT_SET_MCAST_GROUP_ID)
+                .withParameter(new PiActionParam(
+                        FabricConstants.GROUP_ID, NEXT_ID_1))
+                .build();
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .piTableAction(setMcGroupAction)
+                .build();
+        FlowRule expectedHashedFlowRule = DefaultFlowRule.builder()
+                .forDevice(DEVICE_ID)
+                .fromApp(APP_ID)
+                .makePermanent()
+                .withPriority(nextObjective.priority())
+                .forTable(FabricConstants.FABRIC_INGRESS_NEXT_MULTICAST)
+                .withSelector(nextIdSelector)
+                .withTreatment(treatment)
+                .build();
+
+        // Expected egress VLAN POP flow rule.
+        PiCriterion egressVlanTableMatch = PiCriterion.builder()
+                .matchExact(FabricConstants.HDR_EG_PORT, PORT_2.toLong())
+                .build();
+        TrafficSelector selectorForEgressVlan = DefaultTrafficSelector.builder()
+                .matchPi(egressVlanTableMatch)
+                .matchVlanId(VLAN_100)
+                .build();
+        PiAction piActionForEgressVlan = PiAction.builder()
+                .withId(FabricConstants.FABRIC_EGRESS_EGRESS_NEXT_POP_VLAN)
+                .build();
+        TrafficTreatment treatmentForEgressVlan = DefaultTrafficTreatment.builder()
+                .piTableAction(piActionForEgressVlan)
+                .build();
+        FlowRule expectedEgressVlanRule = DefaultFlowRule.builder()
+                .withSelector(selectorForEgressVlan)
+                .withTreatment(treatmentForEgressVlan)
+                .forTable(FabricConstants.FABRIC_EGRESS_EGRESS_NEXT_EGRESS_VLAN)
+                .makePermanent()
+                .withPriority(nextObjective.priority())
+                .forDevice(DEVICE_ID)
+                .fromApp(APP_ID)
+                .build();
+
+        // Expected ALL group.
+        TrafficTreatment allGroupTreatment1 = DefaultTrafficTreatment.builder()
+                .setOutput(PORT_1)
+                .build();
+        TrafficTreatment allGroupTreatment2 = DefaultTrafficTreatment.builder()
+                .setOutput(PORT_2)
+                .build();
+        List<TrafficTreatment> allTreatments = ImmutableList.of(
+                allGroupTreatment1, allGroupTreatment2);
+        List<GroupBucket> allBuckets = allTreatments.stream()
+                .map(DefaultGroupBucket::createAllGroupBucket)
+                .collect(Collectors.toList());
+        GroupBuckets allGroupBuckets = new GroupBuckets(allBuckets);
+        GroupKey allGroupKey = new DefaultGroupKey(FabricPipeliner.KRYO.serialize(NEXT_ID_1));
+        GroupDescription expectedAllGroup = new DefaultGroupDescription(
+                DEVICE_ID,
+                GroupDescription.Type.ALL,
+                allGroupBuckets,
+                allGroupKey,
+                NEXT_ID_1,
+                APP_ID
+        );
+
+        ObjectiveTranslation expectedTranslation = ObjectiveTranslation.builder()
+                .addFlowRule(expectedHashedFlowRule)
+                .addFlowRule(vlanMetaFlowRule)
+                .addFlowRule(expectedEgressVlanRule)
+                .addGroup(expectedAllGroup)
+                .build();
+
+        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/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/FabricPipelinerTest.java b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/FabricPipelinerTest.java
new file mode 100644
index 0000000..850e21f
--- /dev/null
+++ b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/FabricPipelinerTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.Test;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.MplsLabel;
+import org.onlab.packet.VlanId;
+import org.onosproject.TestApplicationId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.pipelines.fabric.impl.behaviour.FabricCapabilities;
+
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+
+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);
+    static final PortNumber PORT_2 = PortNumber.portNumber(2);
+    static final VlanId VLAN_100 = VlanId.vlanId("100");
+    static final VlanId VLAN_200 = VlanId.vlanId("200");
+    static final MacAddress HOST_MAC = MacAddress.valueOf("00:00:00:00:00:01");
+    static final MacAddress ROUTER_MAC = MacAddress.valueOf("00:00:00:00:02:01");
+    static final IpPrefix IPV4_UNICAST_ADDR = IpPrefix.valueOf("10.0.0.1/32");
+    static final IpPrefix IPV4_MCAST_ADDR = IpPrefix.valueOf("224.0.0.1/32");
+    static final IpPrefix IPV6_UNICAST_ADDR = IpPrefix.valueOf("2000::1/32");
+    static final IpPrefix IPV6_MCAST_ADDR = IpPrefix.valueOf("ff00::1/32");
+    static final MplsLabel MPLS_10 = MplsLabel.mplsLabel(10);
+    static final Integer NEXT_ID_1 = 1;
+    static final TrafficSelector VLAN_META = DefaultTrafficSelector.builder()
+            .matchVlanId(VLAN_100)
+            .build();
+
+    FabricCapabilities capabilitiesHashed;
+    FabricCapabilities capabilitiesSimple;
+
+    void doSetup() {
+        this.capabilitiesHashed = createNiceMock(FabricCapabilities.class);
+        this.capabilitiesSimple = createNiceMock(FabricCapabilities.class);
+        expect(capabilitiesHashed.hasHashedTable()).andReturn(true).anyTimes();
+        expect(capabilitiesSimple.hasHashedTable()).andReturn(false).anyTimes();
+        expect(capabilitiesSimple.supportDoubleVlanTerm()).andReturn(true).anyTimes();
+        replay(capabilitiesHashed);
+        replay(capabilitiesSimple);
+    }
+
+    @Test
+    public void fakeTest() {
+        // Needed otherwise Bazel complains about a test class without test cases.
+        assert true;
+    }
+}
diff --git a/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/ForwardingFunctionTypeTest.java b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/ForwardingFunctionTypeTest.java
new file mode 100644
index 0000000..8d98d862
--- /dev/null
+++ b/pipelines/fabric/impl/src/test/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/ForwardingFunctionTypeTest.java
@@ -0,0 +1,138 @@
+/*
+ * 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.Ignore;
+import org.junit.Test;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.MplsLabel;
+import org.onlab.packet.VlanId;
+import org.onosproject.TestApplicationId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit tests for Forwarding class.
+ */
+public class ForwardingFunctionTypeTest {
+    private static final ApplicationId APP_ID = TestApplicationId.create("ForwardingFunctionTypeTest");
+    private static final VlanId VLAN_100 = VlanId.vlanId((short) 100);
+    private static final MacAddress MAC_ADDR = MacAddress.valueOf("00:00:00:00:00:01");
+    private static final MacAddress MAC_NONE = MacAddress.NONE;
+    private static final IpPrefix IPV4_UNICAST_ADDR = IpPrefix.valueOf("10.0.0.1/32");
+    private static final IpPrefix IPV4_MCAST_ADDR = IpPrefix.valueOf("224.0.0.1/32");
+    private static final IpPrefix IPV6_UNICAST_ADDR = IpPrefix.valueOf("2000::1/32");
+    private static final IpPrefix IPV6_MCAST_ADDR = IpPrefix.valueOf("ff00::1/32");
+    private static final MplsLabel MPLS_10 = MplsLabel.mplsLabel(10);
+    private TrafficSelector selector;
+
+    /**
+     * Match Vlan + EthDst.
+     */
+    @Test
+    public void testL2Unicast() {
+        selector = DefaultTrafficSelector.builder()
+                .matchVlanId(VLAN_100)
+                .matchEthDst(MAC_ADDR)
+                .build();
+        testFft(selector, ForwardingFunctionType.L2_UNICAST);
+    }
+
+    @Test
+    public void testL2Broadcast() {
+        selector = DefaultTrafficSelector.builder()
+                .matchVlanId(VLAN_100)
+                .build();
+        testFft(selector, ForwardingFunctionType.L2_BROADCAST);
+    }
+
+    @Test
+    public void testL2BroadcastWithMacNone() {
+        selector = DefaultTrafficSelector.builder()
+                .matchVlanId(VLAN_100)
+                .matchEthDst(MAC_NONE)
+                .build();
+        testFft(selector, ForwardingFunctionType.L2_BROADCAST);
+    }
+
+    @Test
+    public void testIpv4Unicast() {
+        selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(IPV4_UNICAST_ADDR)
+                .build();
+        testFft(selector, ForwardingFunctionType.IPV4_ROUTING);
+    }
+
+    @Test
+    @Ignore
+    public void testIpv4Multicast() {
+        selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(IPV4_MCAST_ADDR)
+                .build();
+        testFft(selector, ForwardingFunctionType.IPV4_ROUTING);
+    }
+
+    @Test
+    @Ignore
+    public void testIpv6Unicast() {
+        selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV6)
+                .matchIPDst(IPV6_UNICAST_ADDR)
+                .build();
+        testFft(selector, ForwardingFunctionType.IPV6_ROUTING);
+    }
+
+    @Test
+    @Ignore
+    public void testIpv6Multicast() {
+        selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV6)
+                .matchIPDst(IPV6_MCAST_ADDR)
+                .build();
+        testFft(selector, ForwardingFunctionType.IPV4_ROUTING);
+    }
+
+    @Test
+    public void testMplsUnicast() {
+        selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.MPLS_UNICAST)
+                .matchMplsLabel(MPLS_10)
+                .matchMplsBos(true)
+                .build();
+        testFft(selector, ForwardingFunctionType.MPLS_SEGMENT_ROUTING);
+    }
+
+    private void testFft(TrafficSelector selector, ForwardingFunctionType expectedFft) {
+        ForwardingObjective fwd = DefaultForwardingObjective.builder()
+                .withSelector(selector)
+                .withFlag(ForwardingObjective.Flag.SPECIFIC)
+                .nextStep(0)
+                .fromApp(APP_ID)
+                .add();
+        assertEquals(expectedFft,
+                     ForwardingFunctionType.getForwardingFunctionType(fwd));
+    }
+}