[ONOS-7129] Pipeliner for fabric pipeline

Change-Id: I86b44694e1251611359e8ddc8be2533a741230cc
diff --git a/pipelines/fabric/BUCK b/pipelines/fabric/BUCK
index 114701d..9dfe33e 100644
--- a/pipelines/fabric/BUCK
+++ b/pipelines/fabric/BUCK
@@ -1,8 +1,10 @@
 COMPILE_DEPS = [
     '//lib:CORE_DEPS',
+    '//lib:KRYO',
     '//protocols/p4runtime/model:onos-protocols-p4runtime-model',
     '//protocols/p4runtime/api:onos-protocols-p4runtime-api',
     '//pipelines/basic:onos-pipelines-basic',
+    '//core/store/serializers:onos-core-serializers',
 ]
 
 TEST_DEPS = [
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/PipeconfLoader.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/PipeconfLoader.java
index 2eec587..c7c7421 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/PipeconfLoader.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/PipeconfLoader.java
@@ -22,6 +22,7 @@
 import org.apache.felix.scr.annotations.Deactivate;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onosproject.net.behaviour.Pipeliner;
 import org.onosproject.net.device.PortStatisticsDiscovery;
 import org.onosproject.net.pi.model.DefaultPiPipeconf;
 import org.onosproject.net.pi.model.PiPipeconf;
@@ -31,6 +32,7 @@
 import org.onosproject.net.pi.service.PiPipeconfService;
 import org.onosproject.p4runtime.model.P4InfoParser;
 import org.onosproject.p4runtime.model.P4InfoParserException;
+import org.onosproject.pipelines.fabric.pipeliner.FabricPipeliner;
 
 import java.net.URL;
 import java.util.Collection;
@@ -78,6 +80,7 @@
                 .withId(FABRIC_PIPECONF_ID)
                 .withPipelineModel(model)
                 .addBehaviour(PiPipelineInterpreter.class, FabricInterpreter.class)
+                .addBehaviour(Pipeliner.class, FabricPipeliner.class)
                 .addBehaviour(PortStatisticsDiscovery.class, FabricPortStatisticsDiscovery.class)
                 .addExtension(P4_INFO_TEXT, p4InfoUrl)
                 .addExtension(BMV2_JSON, jsonUrl)
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricFilteringPipeliner.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricFilteringPipeliner.java
new file mode 100644
index 0000000..69ec0ab
--- /dev/null
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricFilteringPipeliner.java
@@ -0,0 +1,244 @@
+/*
+ * 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.pipeliner;
+
+import com.google.common.collect.Sets;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.DeviceId;
+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.flow.criteria.PiCriterion;
+import org.onosproject.net.flow.criteria.PortCriterion;
+import org.onosproject.net.flow.criteria.VlanIdCriterion;
+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.FabricConstants;
+import org.slf4j.Logger;
+
+import java.util.Collection;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Handling filtering objective for fabric pipeliner.
+ */
+public class FabricFilteringPipeliner {
+    private static final Logger log = getLogger(FabricFilteringPipeliner.class);
+    // Forwarding types
+    private static final byte FWD_BRIDGING = 0;
+    private static final byte FWD_MPLS = 1;
+    private static final byte FWD_IPV4_UNICAST = 2;
+    private static final byte FWD_IPV4_MULTICAST = 3;
+    private static final byte FWD_IPV6_UNICAST = 4;
+    private static final byte FWD_IPV6_MULTICAST = 5;
+    private static final PiCriterion VLAN_VALID = PiCriterion.builder()
+            .matchExact(FabricConstants.HF_VLAN_TAG_IS_VALID_ID, new byte[]{1})
+            .build();
+    private static final PiCriterion VLAN_INVALID = PiCriterion.builder()
+            .matchExact(FabricConstants.HF_VLAN_TAG_IS_VALID_ID, new byte[]{0})
+            .build();
+
+    protected DeviceId deviceId;
+
+    public FabricFilteringPipeliner(DeviceId deviceId) {
+        this.deviceId = deviceId;
+    }
+
+    /**
+     * Translates filtering objective to flows and groups.
+     *
+     * @param filterObjective the filtering objective
+     * @return translation result, contains flows, groups or error it generated
+     */
+    public PipelinerTranslationResult filter(FilteringObjective filterObjective) {
+        PipelinerTranslationResult.Builder resultBuilder = PipelinerTranslationResult.builder();
+        // maps selector and treatment from filtering objective to filtering
+        // control block.
+
+        if (filterObjective.type() == FilteringObjective.Type.DENY) {
+            log.warn("Unsupported filtering objective type {}", filterObjective.type());
+            resultBuilder.setError(ObjectiveError.UNSUPPORTED);
+            return resultBuilder.build();
+        }
+
+        if (filterObjective.key() == null ||
+                filterObjective.key().type() != Criterion.Type.IN_PORT) {
+            log.warn("Unsupported filter key {}", filterObjective.key());
+            resultBuilder.setError(ObjectiveError.BADPARAMS);
+            return resultBuilder.build();
+        }
+        PortCriterion inPortCriterion = (PortCriterion) filterObjective.key();
+        VlanIdCriterion vlanCriterion = filterObjective.conditions().stream()
+                .filter(criterion -> criterion.type() == Criterion.Type.VLAN_VID)
+                .map(criterion -> (VlanIdCriterion) criterion)
+                .findFirst()
+                .orElse(null);
+        EthCriterion ethDstCriterion = filterObjective.conditions().stream()
+                .filter(criterion -> criterion.type() == Criterion.Type.ETH_DST)
+                .map(criterion -> (EthCriterion) criterion)
+                .findFirst()
+                .orElse(null);
+
+        FlowRule inPortVlanTableRule = createInPortVlanTable(inPortCriterion, vlanCriterion,
+                                                             filterObjective);
+        Collection<FlowRule> fwdClassifierRules = createFwdClassifierRules(inPortCriterion, ethDstCriterion,
+                                                                           filterObjective);
+
+        resultBuilder.addFlowRule(inPortVlanTableRule);
+        fwdClassifierRules.forEach(resultBuilder::addFlowRule);
+        return resultBuilder.build();
+    }
+
+    private FlowRule createInPortVlanTable(Criterion inPortCriterion,
+                                           VlanIdCriterion vlanCriterion,
+                                           FilteringObjective filterObjective) {
+        Criterion vlanIsVlalidCriterion;
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder()
+                .add(inPortCriterion);
+
+        VlanId vlanId = null;
+        if (vlanCriterion != null) {
+            vlanId = vlanCriterion.vlanId();
+        }
+
+        vlanIsVlalidCriterion = VLAN_VALID;
+        if (vlanId == null || vlanId.equals(VlanId.NONE)) {
+            // untag vlan, match in port only
+            vlanIsVlalidCriterion = VLAN_INVALID;
+        }
+
+        selector.add(vlanIsVlalidCriterion);
+
+        // TODO: check if this treatment is valid or not
+        TrafficTreatment treatment = filterObjective.meta();
+        if (treatment == null) {
+            treatment = DefaultTrafficTreatment.emptyTreatment();
+        }
+
+        return DefaultFlowRule.builder()
+                .fromApp(filterObjective.appId())
+                .withPriority(filterObjective.priority())
+                .withSelector(selector.build())
+                .withTreatment(treatment)
+                .withPriority(filterObjective.priority())
+                .forTable(FabricConstants.TBL_INGRESS_PORT_VLAN_ID)
+                .forDevice(deviceId)
+                .makePermanent()
+                .build();
+    }
+
+    private Collection<FlowRule> createFwdClassifierRules(PortCriterion inPortCriterion,
+                                                          EthCriterion ethDstCriterion,
+                                                          FilteringObjective filterObjective) {
+        Collection<FlowRule> flowRules = Sets.newHashSet();
+        if (ethDstCriterion == null) {
+            // Bridging table, do nothing
+            return flowRules;
+        }
+        PortNumber port = inPortCriterion.port();
+        MacAddress dstMac = ethDstCriterion.mac();
+        if (dstMac.isMulticast()) {
+            flowRules.add(createMulticastFwdClassifierRule(port, dstMac, filterObjective));
+            return flowRules;
+        }
+
+        flowRules.addAll(createIpFwdClassifierRules(port, dstMac, filterObjective));
+        flowRules.add(createMplsFwdClassifierRule(port, dstMac, filterObjective));
+        return flowRules;
+    }
+
+    private FlowRule createMulticastFwdClassifierRule(PortNumber inPort, MacAddress dstMac,
+                                                      FilteringObjective filterObjective) {
+        TrafficTreatment treatment;
+        short ethType;
+        if (dstMac.equals(MacAddress.IPV4_MULTICAST)) {
+            // Ipv4 multicast
+            treatment = createFwdClassifierTreatment(FWD_IPV4_MULTICAST);
+            ethType = Ethernet.TYPE_IPV4;
+        } else {
+            // IPv6 multicast
+            treatment = createFwdClassifierTreatment(FWD_IPV6_MULTICAST);
+            ethType = Ethernet.TYPE_IPV6;
+        }
+        return createFwdClassifierRule(inPort, ethType, dstMac, treatment, filterObjective);
+    }
+
+    private Collection<FlowRule> createIpFwdClassifierRules(PortNumber inPort,
+                                                            MacAddress dstMac,
+                                                            FilteringObjective filterObjective) {
+        Collection<FlowRule> flowRules = Sets.newHashSet();
+        TrafficTreatment treatment;
+        treatment = createFwdClassifierTreatment(FWD_IPV4_UNICAST);
+        flowRules.add(createFwdClassifierRule(inPort, Ethernet.TYPE_IPV4, dstMac, treatment, filterObjective));
+        treatment = createFwdClassifierTreatment(FWD_IPV6_UNICAST);
+        flowRules.add(createFwdClassifierRule(inPort, Ethernet.TYPE_IPV6, dstMac, treatment, filterObjective));
+        return flowRules;
+    }
+
+    private FlowRule createMplsFwdClassifierRule(PortNumber inPort,
+                                                 MacAddress dstMac,
+                                                 FilteringObjective filterObjective) {
+        TrafficTreatment treatment = createFwdClassifierTreatment(FWD_MPLS);
+        return createFwdClassifierRule(inPort, Ethernet.MPLS_UNICAST, dstMac, treatment, filterObjective);
+    }
+
+    private FlowRule createFwdClassifierRule(PortNumber inPort,
+                                             short ethType,
+                                             MacAddress dstMac,
+                                             TrafficTreatment treatment,
+                                             FilteringObjective filterObjective) {
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder()
+                .matchInPort(inPort)
+                .matchEthDst(dstMac)
+                .matchEthType(ethType);
+
+        return DefaultFlowRule.builder()
+                .withSelector(selector.build())
+                .withTreatment(treatment)
+                .fromApp(filterObjective.appId())
+                .withPriority(filterObjective.priority())
+                .forDevice(deviceId)
+                .makePermanent()
+                .forTable(FabricConstants.TBL_FWD_CLASSIFIER_ID)
+                .build();
+    }
+
+    private TrafficTreatment createFwdClassifierTreatment(byte fwdType) {
+        PiActionParam param = new PiActionParam(FabricConstants.ACT_PRM_FWD_TYPE_ID,
+                                                ImmutableByteSequence.copyFrom(fwdType));
+        PiAction action = PiAction.builder()
+                .withId(FabricConstants.ACT_SET_FORWARDING_TYPE_ID)
+                .withParameter(param)
+                .build();
+        return DefaultTrafficTreatment.builder()
+                .piTableAction(action)
+                .build();
+
+    }
+}
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricForwardingPipeliner.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricForwardingPipeliner.java
new file mode 100644
index 0000000..d37402d
--- /dev/null
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricForwardingPipeliner.java
@@ -0,0 +1,211 @@
+/*
+ * 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.pipeliner;
+
+import com.google.common.collect.ImmutableSet;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.DeviceId;
+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.flow.criteria.VlanIdCriterion;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+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.FabricConstants;
+import org.slf4j.Logger;
+
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Handling forwarding objective for fabric pipeliner.
+ */
+public class FabricForwardingPipeliner {
+    private static final Logger log = getLogger(FabricForwardingPipeliner.class);
+
+    protected DeviceId deviceId;
+
+    public FabricForwardingPipeliner(DeviceId deviceId) {
+        this.deviceId = deviceId;
+    }
+
+    public PipelinerTranslationResult forward(ForwardingObjective forwardObjective) {
+        PipelinerTranslationResult.Builder resultBuilder = PipelinerTranslationResult.builder();
+        if (forwardObjective.flag() == ForwardingObjective.Flag.VERSATILE) {
+            processVersatileFwd(forwardObjective, resultBuilder);
+        } else {
+            processSpecificFwd(forwardObjective, resultBuilder);
+        }
+        return resultBuilder.build();
+    }
+
+    private void processVersatileFwd(ForwardingObjective fwd,
+                                     PipelinerTranslationResult.Builder resultBuilder) {
+        // program ACL table only
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .withSelector(fwd.selector())
+                .withTreatment(fwd.treatment())
+                .forTable(FabricConstants.TBL_ACL_ID)
+                .withPriority(fwd.priority())
+                .forDevice(deviceId)
+                .makePermanent()
+                .fromApp(fwd.appId())
+                .build();
+        resultBuilder.addFlowRule(flowRule);
+    }
+
+    private void processSpecificFwd(ForwardingObjective fwd,
+                                    PipelinerTranslationResult.Builder resultBuilder) {
+        TrafficSelector selector = fwd.selector();
+        TrafficSelector meta = fwd.meta();
+
+        ImmutableSet.Builder<Criterion> criterionSetBuilder = ImmutableSet.builder();
+        criterionSetBuilder.addAll(selector.criteria());
+
+        if (meta != null) {
+            criterionSetBuilder.addAll(meta.criteria());
+        }
+
+        Set<Criterion> criteria = criterionSetBuilder.build();
+
+        VlanIdCriterion vlanIdCriterion = null;
+        EthCriterion ethDstCriterion = null;
+
+        for (Criterion criterion : criteria) {
+            switch (criterion.type()) {
+                case ETH_DST:
+                    ethDstCriterion = (EthCriterion) criterion;
+                    break;
+                case VLAN_VID:
+                    vlanIdCriterion = (VlanIdCriterion) criterion;
+                    break;
+                default:
+                    log.warn("Unsupported criterion {}", criterion);
+                    break;
+            }
+        }
+
+        ForwardingFunctionType forwardingFunctionType =
+                ForwardingFunctionType.getForwardingFunctionType(fwd);
+        switch (forwardingFunctionType) {
+            case L2_UNICAST:
+                processL2UnicastRule(vlanIdCriterion, ethDstCriterion, fwd, resultBuilder);
+                break;
+            case L2_BROADCAST:
+                processL2BroadcastRule(vlanIdCriterion, fwd, resultBuilder);
+                break;
+            case IPV4_UNICAST:
+            case IPV4_MULTICAST:
+            case IPV6_UNICAST:
+            case IPV6_MULTICAST:
+            case MPLS:
+            default:
+                log.warn("Unsupported forwarding function type {}", criteria);
+                resultBuilder.setError(ObjectiveError.UNSUPPORTED);
+                break;
+        }
+    }
+
+    // L2 Unicast: learnt mac address + vlan
+    private void processL2UnicastRule(VlanIdCriterion vlanIdCriterion,
+                                      EthCriterion ethDstCriterion,
+                                      ForwardingObjective fwd,
+                                      PipelinerTranslationResult.Builder resultBuilder) {
+        checkNotNull(vlanIdCriterion, "VlanId criterion should not be null");
+        checkNotNull(ethDstCriterion, "EthDst criterion should not be null");
+
+        if (fwd.nextId() == null) {
+            log.warn("Forwarding objective for L2 unicast should contains next id");
+            resultBuilder.setError(ObjectiveError.BADPARAMS);
+            return;
+        }
+
+        VlanId vlanId = vlanIdCriterion.vlanId();
+        MacAddress ethDst = ethDstCriterion.mac();
+
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchVlanId(vlanId)
+                .matchEthDst(ethDst)
+                .build();
+        TrafficTreatment treatment = buildSetNextIdTreatment(fwd.nextId());
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .fromApp(fwd.appId())
+                .withPriority(fwd.priority())
+                .makePermanent()
+                .forDevice(deviceId)
+                .forTable(FabricConstants.TBL_BRIDGING_ID)
+                .build();
+
+        resultBuilder.addFlowRule(flowRule);
+    }
+
+    private void processL2BroadcastRule(VlanIdCriterion vlanIdCriterion,
+                                        ForwardingObjective fwd,
+                                        PipelinerTranslationResult.Builder resultBuilder) {
+        checkNotNull(vlanIdCriterion, "VlanId criterion should not be null");
+        if (fwd.nextId() == null) {
+            log.warn("Forwarding objective for L2 broadcast should contains next id");
+            resultBuilder.setError(ObjectiveError.BADPARAMS);
+            return;
+        }
+
+        VlanId vlanId = vlanIdCriterion.vlanId();
+
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchVlanId(vlanId)
+                .build();
+        TrafficTreatment treatment = buildSetNextIdTreatment(fwd.nextId());
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .fromApp(fwd.appId())
+                .withPriority(fwd.priority())
+                .makePermanent()
+                .forDevice(deviceId)
+                .forTable(FabricConstants.TBL_BRIDGING_ID)
+                .build();
+
+        resultBuilder.addFlowRule(flowRule);
+    }
+
+    private static TrafficTreatment buildSetNextIdTreatment(Integer nextId) {
+        PiActionParam nextIdParam = new PiActionParam(FabricConstants.ACT_PRM_NEXT_ID_ID,
+                                                      ImmutableByteSequence.copyFrom(nextId.byteValue()));
+        PiAction nextIdAction = PiAction.builder()
+                .withId(FabricConstants.ACT_SET_NEXT_ID_ID)
+                .withParameter(nextIdParam)
+                .build();
+
+        return DefaultTrafficTreatment.builder()
+                .piTableAction(nextIdAction)
+                .build();
+    }
+
+}
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipeliner.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipeliner.java
new file mode 100644
index 0000000..7487fbe
--- /dev/null
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipeliner.java
@@ -0,0 +1,159 @@
+/*
+ * 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.pipeliner;
+
+import com.google.common.collect.ImmutableMap;
+import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.DeviceId;
+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.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
+import org.onosproject.net.flowobjective.NextObjective;
+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.FabricConstants;
+import org.slf4j.Logger;
+
+import java.util.Map;
+
+import static org.onosproject.pipelines.fabric.pipeliner.FabricPipeliner.fail;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Handling next objective for fabric pipeliner.
+ */
+public class FabricNextPipeliner {
+    private static final Logger log = getLogger(FabricNextPipeliner.class);
+
+    // Next types
+    private static final byte TBL_SIMPLE = 0;
+    private static final byte TBL_HASHED = 1;
+    private static final byte TBL_BROADCAST = 2;
+    private static final byte PUNT = 3;
+    private static final Map<NextObjective.Type, Byte> NEXT_TYPE_MAP =
+            ImmutableMap.<NextObjective.Type, Byte>builder()
+                    .put(NextObjective.Type.SIMPLE, TBL_SIMPLE)
+                    .put(NextObjective.Type.HASHED, TBL_HASHED)
+                    .put(NextObjective.Type.BROADCAST, TBL_BROADCAST)
+                    .build();
+
+    protected DeviceId deviceId;
+
+    public FabricNextPipeliner(DeviceId deviceId) {
+        this.deviceId = deviceId;
+    }
+
+    public PipelinerTranslationResult next(NextObjective nextObjective) {
+        PipelinerTranslationResult.Builder resultBuilder = PipelinerTranslationResult.builder();
+        FlowRule nextIdMappingRule = processNextIdMapping(nextObjective);
+        FlowRule nextRule = null;
+        switch (nextObjective.type()) {
+            case SIMPLE:
+                nextRule = processSimpleNext(nextObjective);
+                break;
+            default:
+                log.warn("Unsupported next type {}", nextObjective);
+                resultBuilder.setError(ObjectiveError.UNSUPPORTED);
+                break;
+        }
+
+        if (nextIdMappingRule != null && nextRule != null) {
+            resultBuilder.addFlowRule(nextIdMappingRule);
+            resultBuilder.addFlowRule(nextRule);
+        }
+
+        return resultBuilder.build();
+    }
+
+    private FlowRule processNextIdMapping(NextObjective next) {
+        // program the next id mapping table
+        TrafficSelector nextIdSelector = buildNextIdSelector(next.id());
+        TrafficTreatment setNextTypeTreatment = buildSetNextTypeTreatment(next.type());
+
+        return DefaultFlowRule.builder()
+                .withSelector(nextIdSelector)
+                .withTreatment(setNextTypeTreatment)
+                .forDevice(deviceId)
+                .forTable(FabricConstants.TBL_NEXT_ID_MAPPING_ID)
+                .makePermanent()
+                .withPriority(next.priority())
+                .fromApp(next.appId())
+                .build();
+    }
+
+    private FlowRule processSimpleNext(NextObjective next) {
+        if (next.next().size() > 1) {
+            log.warn("Only one treatment in simple next objective");
+            fail(next, ObjectiveError.BADPARAMS);
+            return null;
+        }
+
+        TrafficSelector selector = buildNextIdSelector(next.id());
+        TrafficTreatment treatment = next.next().iterator().next();
+        OutputInstruction outputInst = treatment.allInstructions()
+                .stream()
+                .filter(inst -> inst.type() == Instruction.Type.OUTPUT)
+                .map(inst -> (OutputInstruction) inst)
+                .findFirst()
+                .orElse(null);
+
+        if (outputInst == null) {
+            log.warn("At least one output instruction in simple next objective");
+            fail(next, ObjectiveError.BADPARAMS);
+            return null;
+        }
+        return DefaultFlowRule.builder()
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .forTable(FabricConstants.TBL_SIMPLE_ID)
+                .makePermanent()
+                .withPriority(next.priority())
+                .forDevice(deviceId)
+                .fromApp(next.appId())
+                .build();
+    }
+
+    private TrafficSelector buildNextIdSelector(int nextId) {
+        byte[] nextIdVal = new byte[]{(byte) nextId};
+        PiCriterion nextIdCrriterion = PiCriterion.builder()
+                .matchExact(FabricConstants.HF_FABRIC_METADATA_NEXT_ID_ID, nextIdVal)
+                .build();
+        return DefaultTrafficSelector.builder()
+                .matchPi(nextIdCrriterion)
+                .build();
+    }
+
+    private TrafficTreatment buildSetNextTypeTreatment(NextObjective.Type nextType) {
+        byte nextTypeVal = NEXT_TYPE_MAP.getOrDefault(nextType, PUNT);
+        PiActionParam nextTypeParam = new PiActionParam(FabricConstants.ACT_PRM_NEXT_TYPE_ID,
+                                                        ImmutableByteSequence.copyFrom(nextTypeVal));
+        PiAction nextTypeAction = PiAction.builder()
+                .withId(FabricConstants.ACT_SET_NEXT_TYPE_ID)
+                .withParameter(nextTypeParam)
+                .build();
+        return DefaultTrafficTreatment.builder()
+                .piTableAction(nextTypeAction)
+                .build();
+    }
+}
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricPipeliner.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricPipeliner.java
new file mode 100644
index 0000000..e2d8048
--- /dev/null
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricPipeliner.java
@@ -0,0 +1,303 @@
+/*
+ * 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.pipeliner;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.behaviour.NextGroup;
+import org.onosproject.net.behaviour.Pipeliner;
+import org.onosproject.net.behaviour.PipelinerContext;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleOperations;
+import org.onosproject.net.flow.FlowRuleOperationsContext;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.flowobjective.FilteringObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveStore;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.NextObjective;
+import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.flowobjective.ObjectiveError;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupEvent;
+import org.onosproject.net.group.GroupListener;
+import org.onosproject.net.group.GroupService;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.slf4j.Logger;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Pipeliner for fabric pipeline.
+ */
+public class FabricPipeliner  extends AbstractHandlerBehaviour implements Pipeliner {
+    private static final Logger log = getLogger(FabricPipeliner.class);
+
+    protected static final KryoNamespace KRYO = new KryoNamespace.Builder()
+            .register(KryoNamespaces.API)
+            .register(FabricNextGroup.class)
+            .build("FabricPipeliner");
+
+    // TODO: make this configurable
+    private static final long DEFAULT_INSTALLATION_TIME_OUT = 10;
+
+    protected DeviceId deviceId;
+    protected FlowRuleService flowRuleService;
+    protected GroupService groupService;
+    protected FlowObjectiveStore flowObjectiveStore;
+    protected FabricFilteringPipeliner pipelinerFilter;
+    protected FabricForwardingPipeliner pipelinerForward;
+    protected FabricNextPipeliner pipelinerNext;
+
+
+    @Override
+    public void init(DeviceId deviceId, PipelinerContext context) {
+        this.deviceId = deviceId;
+        this.flowRuleService = context.directory().get(FlowRuleService.class);
+        this.groupService = context.directory().get(GroupService.class);
+        this.flowObjectiveStore = context.directory().get(FlowObjectiveStore.class);
+        this.pipelinerFilter = new FabricFilteringPipeliner(deviceId);
+        this.pipelinerForward = new FabricForwardingPipeliner(deviceId);
+        this.pipelinerNext = new FabricNextPipeliner(deviceId);
+    }
+
+    @Override
+    public void filter(FilteringObjective filterObjective) {
+        PipelinerTranslationResult result = pipelinerFilter.filter(filterObjective);
+        if (result.error().isPresent()) {
+            fail(filterObjective, result.error().get());
+            return;
+        }
+
+        applyTranslationResult(filterObjective, result, success -> {
+            if (success) {
+                success(filterObjective);
+            } else {
+                fail(filterObjective, ObjectiveError.FLOWINSTALLATIONFAILED);
+            }
+        });
+    }
+
+    @Override
+    public void forward(ForwardingObjective forwardObjective) {
+        PipelinerTranslationResult result = pipelinerForward.forward(forwardObjective);
+        if (result.error().isPresent()) {
+            fail(forwardObjective, result.error().get());
+            return;
+        }
+
+        applyTranslationResult(forwardObjective, result, success -> {
+            if (success) {
+                success(forwardObjective);
+            } else {
+                fail(forwardObjective, ObjectiveError.FLOWINSTALLATIONFAILED);
+            }
+        });
+    }
+
+    @Override
+    public void next(NextObjective nextObjective) {
+        PipelinerTranslationResult result = pipelinerNext.next(nextObjective);
+
+        if (result.error().isPresent()) {
+            fail(nextObjective, result.error().get());
+            return;
+        }
+
+        applyTranslationResult(nextObjective, result, success -> {
+            if (!success) {
+                fail(nextObjective, ObjectiveError.GROUPINSTALLATIONFAILED);
+                return;
+            }
+
+            // Success, put next group to objective store
+            List<PortNumber> portNumbers = Lists.newArrayList();
+            nextObjective.next().forEach(treatment -> {
+                Instructions.OutputInstruction outputInst = treatment.allInstructions()
+                        .stream()
+                        .filter(inst -> inst.type() == Instruction.Type.OUTPUT)
+                        .map(inst -> (Instructions.OutputInstruction) inst)
+                        .findFirst()
+                        .orElse(null);
+
+                if (outputInst != null) {
+                    portNumbers.add(outputInst.port());
+                }
+            });
+            FabricNextGroup nextGroup = new FabricNextGroup(nextObjective.type(),
+                                                            portNumbers);
+            flowObjectiveStore.putNextGroup(nextObjective.id(), nextGroup);
+            success(nextObjective);
+        });
+    }
+
+    @Override
+    public List<String> getNextMappings(NextGroup nextGroup) {
+        return null;
+    }
+
+    private void applyTranslationResult(Objective objective,
+                                        PipelinerTranslationResult result,
+                                        Consumer<Boolean> callback) {
+        Collection<GroupDescription> groups = result.groups();
+        Collection<FlowRule> flowRules = result.flowRules();
+        CompletableFuture.supplyAsync(() -> installGroups(objective, groups))
+                .thenApplyAsync(groupSuccess -> groupSuccess && installFlows(objective, flowRules))
+                .thenAcceptAsync(callback)
+                .exceptionally((ex) -> {
+                    log.warn("Got unexpected exception while applying translation result {}",
+                             result);
+                    fail(objective, ObjectiveError.UNKNOWN);
+                    return null;
+                });
+    }
+
+    private boolean installFlows(Objective objective, Collection<FlowRule> flowRules) {
+        if (flowRules.isEmpty()) {
+            return true;
+        }
+        CompletableFuture<Boolean> flowInstallFuture = new CompletableFuture<>();
+        FlowRuleOperationsContext ctx = new FlowRuleOperationsContext() {
+            @Override
+            public void onSuccess(FlowRuleOperations ops) {
+                flowInstallFuture.complete(true);
+            }
+
+            @Override
+            public void onError(FlowRuleOperations ops) {
+                log.warn("Failed to install flow rules: {}", flowRules);
+                flowInstallFuture.complete(false);
+            }
+        };
+
+        FlowRuleOperations ops = buildFlowRuleOps(objective, flowRules, ctx);
+        flowRuleService.apply(ops);
+
+        try {
+            return flowInstallFuture.get(DEFAULT_INSTALLATION_TIME_OUT, TimeUnit.SECONDS);
+        } catch (InterruptedException | ExecutionException | TimeoutException e) {
+            log.warn("Got exception while installing groups: {}", e);
+            return false;
+        }
+    }
+
+    private boolean installGroups(Objective objective, Collection<GroupDescription> groups) {
+        if (groups.isEmpty()) {
+            return true;
+        }
+        int numGroupsToBeInstalled = groups.size();
+        CompletableFuture<Boolean> groupInstallFuture = new CompletableFuture<>();
+        AtomicInteger numGroupsInstalled = new AtomicInteger(0);
+        GroupListener listener = new GroupListener() {
+            @Override
+            public void event(GroupEvent event) {
+                int currentNumGroupInstalled = numGroupsInstalled.incrementAndGet();
+                if (currentNumGroupInstalled == numGroupsToBeInstalled) {
+                    // install completed
+                    groupService.removeListener(this);
+                    groupInstallFuture.complete(true);
+                }
+            }
+            @Override
+            public boolean isRelevant(GroupEvent event) {
+                return groups.contains(event.subject());
+            }
+        };
+        groupService.addListener(listener);
+
+        switch (objective.op()) {
+            case ADD:
+                groups.forEach(groupService::addGroup);
+                break;
+            case REMOVE:
+                groups.forEach(group -> groupService.removeGroup(deviceId, group.appCookie(), objective.appId()));
+                break;
+            default:
+                log.warn("Unsupported objective operation {}", objective.op());
+                groupService.removeListener(listener);
+        }
+        try {
+            return groupInstallFuture.get(DEFAULT_INSTALLATION_TIME_OUT, TimeUnit.SECONDS);
+        } catch (InterruptedException | ExecutionException | TimeoutException e) {
+            log.warn("Got exception while installing groups: {}", e);
+            return false;
+        }
+    }
+
+    static void fail(Objective objective, ObjectiveError error) {
+        objective.context().ifPresent(ctx -> ctx.onError(objective, error));
+    }
+
+    static void success(Objective objective) {
+        objective.context().ifPresent(ctx -> ctx.onSuccess(objective));
+    }
+
+    static FlowRuleOperations buildFlowRuleOps(Objective objective, Collection<FlowRule> flowRules,
+                                               FlowRuleOperationsContext ctx) {
+        FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
+        switch (objective.op()) {
+            case ADD:
+                flowRules.forEach(ops::add);
+                break;
+            case REMOVE:
+                flowRules.forEach(ops::remove);
+                break;
+            default:
+                log.warn("Unsupported op {} for {}", objective);
+                fail(objective, ObjectiveError.BADPARAMS);
+                return null;
+        }
+        return ops.build(ctx);
+    }
+
+    class FabricNextGroup implements NextGroup {
+        private NextObjective.Type type;
+        private Collection<PortNumber> outputPorts;
+
+        public FabricNextGroup(NextObjective.Type type, Collection<PortNumber> outputPorts) {
+            this.type = type;
+            this.outputPorts = ImmutableList.copyOf(outputPorts);
+        }
+
+        public NextObjective.Type type() {
+            return type;
+        }
+
+        public Collection<PortNumber> outputPorts() {
+            return outputPorts;
+        }
+
+        @Override
+        public byte[] data() {
+            return KRYO.serialize(this);
+        }
+    }
+}
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/ForwardingFunctionType.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/ForwardingFunctionType.java
new file mode 100644
index 0000000..8d8de54
--- /dev/null
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/ForwardingFunctionType.java
@@ -0,0 +1,122 @@
+/*
+ * 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.pipeliner;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+
+import java.util.Map;
+import java.util.Set;
+
+import static org.onosproject.net.flow.criteria.Criterion.Type.ETH_DST;
+import static org.onosproject.net.flow.criteria.Criterion.Type.ETH_TYPE;
+import static org.onosproject.net.flow.criteria.Criterion.Type.IPV4_DST;
+import static org.onosproject.net.flow.criteria.Criterion.Type.IPV6_DST;
+import static org.onosproject.net.flow.criteria.Criterion.Type.MPLS_BOS;
+import static org.onosproject.net.flow.criteria.Criterion.Type.MPLS_LABEL;
+import static org.onosproject.net.flow.criteria.Criterion.Type.VLAN_VID;
+
+public enum ForwardingFunctionType {
+    /**
+     * L2 unicast, with vlan id + mac address criterion.
+     */
+    L2_UNICAST,
+
+    /**
+     * L2 broadcast, with vlan id criterion only.
+     */
+    L2_BROADCAST,
+
+    /**
+     * IPv4 unicast, with EtherType and IPv4 unicast destination address.
+     */
+    IPV4_UNICAST,
+
+    /**
+     * IPv4 multicast, with EtherType and IPv4 multicast destination address.
+     */
+    IPV4_MULTICAST,
+
+    /**
+     * IPv6 unicast, with EtherType and IPv6 unicast destination address.
+     */
+    IPV6_UNICAST,
+
+    /**
+     * IPv6 multicast, with EtherType and IPv6 multicast destination address.
+     */
+    IPV6_MULTICAST,
+
+    /**
+     * MPLS, with EtherType, MPLS label and MPLS BOS criterion.
+     */
+    MPLS,
+
+    /**
+     * Unsupported type.
+     */
+    UNSUPPORTED;
+
+    // Different criteria combinations for different FFT
+    private static final Set<Criterion.Type> L2_UNI_CRITERIA_TYPE =
+            ImmutableSet.of(VLAN_VID, ETH_DST);
+    private static final Set<Criterion.Type> L2_BRC_CRITERIA_TYPE =
+            ImmutableSet.of(VLAN_VID);
+    private static final Set<Criterion.Type> IPV4_UNI_CRITERIA_TYPE =
+            ImmutableSet.of(ETH_TYPE, IPV4_DST);
+    private static final Set<Criterion.Type> IPV4_MCAST_CRITERIA_TYPE =
+            ImmutableSet.of(ETH_TYPE, VLAN_VID, IPV4_DST);
+    private static final Set<Criterion.Type> IPV6_UNI_CRITERIA_TYPE =
+            ImmutableSet.of(ETH_TYPE, IPV6_DST);
+    private static final Set<Criterion.Type> IPV6_MCAST_CRITERIA_TYPE =
+            ImmutableSet.of(ETH_TYPE, VLAN_VID, IPV6_DST);
+    private static final Set<Criterion.Type> MPLS_UNI_CRITERIA_TYPE =
+            ImmutableSet.of(ETH_TYPE, MPLS_LABEL, MPLS_BOS);
+
+    private static final Map<Set<Criterion.Type>, ForwardingFunctionType> FFT_MAP =
+            ImmutableMap.<Set<Criterion.Type>, ForwardingFunctionType>builder()
+                    .put(L2_UNI_CRITERIA_TYPE, L2_UNICAST)
+                    .put(L2_BRC_CRITERIA_TYPE, L2_BROADCAST)
+                    .put(IPV4_UNI_CRITERIA_TYPE, IPV4_UNICAST)
+                    .put(IPV4_MCAST_CRITERIA_TYPE, IPV4_MULTICAST)
+                    .put(IPV6_UNI_CRITERIA_TYPE, IPV6_UNICAST)
+                    .put(IPV6_MCAST_CRITERIA_TYPE, IPV6_MULTICAST)
+                    .put(MPLS_UNI_CRITERIA_TYPE, MPLS)
+                    .build();
+
+    /**
+     * Gets forwarding function type of the forwarding objective.
+     *
+     * @param fwd the forwarding objective
+     * @return forwarding function type of the forwarding objective
+     */
+    public static ForwardingFunctionType getForwardingFunctionType(ForwardingObjective fwd) {
+        Set<Criterion.Type> criteriaType = Sets.newHashSet();
+        fwd.selector().criteria().stream().map(Criterion::type)
+                .forEach(criteriaType::add);
+
+        if (fwd.meta() != null) {
+            fwd.meta().criteria().stream().map(Criterion::type)
+                    .forEach(criteriaType::add);
+        }
+
+        return FFT_MAP.getOrDefault(criteriaType, UNSUPPORTED);
+    }
+}
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/PipelinerTranslationResult.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/PipelinerTranslationResult.java
new file mode 100644
index 0000000..575d590
--- /dev/null
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/PipelinerTranslationResult.java
@@ -0,0 +1,154 @@
+/*
+ * 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.pipeliner;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flowobjective.ObjectiveError;
+import org.onosproject.net.group.GroupDescription;
+
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * Translation results from fabric pipeliner.
+ */
+public final class PipelinerTranslationResult {
+    private Collection<FlowRule> flowRules;
+    private Collection<GroupDescription> groups;
+    private ObjectiveError error;
+
+    private PipelinerTranslationResult(Collection<FlowRule> flowRules,
+                                      Collection<GroupDescription> groups,
+                                      ObjectiveError error) {
+        this.flowRules = flowRules;
+        this.groups = groups;
+        this.error = error;
+    }
+
+    /**
+     * Gets flow rules from result.
+     *
+     * @return flow rules
+     */
+    public Collection<FlowRule> flowRules() {
+        return flowRules;
+    }
+
+    /**
+     * Gets groups from result.
+     *
+     * @return groups
+     */
+    public Collection<GroupDescription> groups() {
+        return groups;
+    }
+
+    /**
+     * Gets error from result.
+     *
+     * @return error of the result; empty if there is no error
+     */
+    public Optional<ObjectiveError> error() {
+        return Optional.ofNullable(error);
+    }
+
+    /**
+     * Creates a new builder.
+     *
+     * @return the builder
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("flowRules", flowRules)
+                .add("groups", groups)
+                .add("error", error)
+                .toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(flowRules, groups, error);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        final PipelinerTranslationResult other = (PipelinerTranslationResult) obj;
+        return Objects.equals(this.flowRules, other.flowRules)
+                && Objects.equals(this.groups, other.groups)
+                && Objects.equals(this.error, other.error);
+    }
+
+    /**
+     * Builder for PipelinerTranslationResult.
+     */
+    public static final class Builder {
+        private ImmutableList.Builder<FlowRule> flowRules = ImmutableList.builder();
+        private ImmutableList.Builder<GroupDescription> groups = ImmutableList.builder();
+        private ObjectiveError error = null;
+
+        // Hide default constructor
+        private Builder() {
+        }
+
+        /**
+         * Adds flow rule to the result.
+         *
+         * @param flowRule the flow rule
+         */
+        public void addFlowRule(FlowRule flowRule) {
+            flowRules.add(flowRule);
+        }
+
+        /**
+         * Adds group to the result.
+         *
+         * @param group the group
+         */
+        public void addGroup(GroupDescription group) {
+            groups.add(group);
+        }
+
+        /**
+         * Sets objective error to the result.
+         *
+         * @param error the error
+         */
+        public void setError(ObjectiveError error) {
+            this.error = error;
+        }
+
+        public PipelinerTranslationResult build() {
+            return new PipelinerTranslationResult(flowRules.build(),
+                                                  groups.build(),
+                                                  error);
+        }
+    }
+}
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/package-info.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/package-info.java
new file mode 100644
index 0000000..75bf40e
--- /dev/null
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Pipeliner for fabric.p4.
+ */
+package org.onosproject.pipelines.fabric.pipeliner;
\ No newline at end of file
diff --git a/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricFilteringPipelinerTest.java b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricFilteringPipelinerTest.java
new file mode 100644
index 0000000..f3de7b7
--- /dev/null
+++ b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricFilteringPipelinerTest.java
@@ -0,0 +1,355 @@
+/*
+ * 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.pipeliner;
+
+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.flowobjective.DefaultFilteringObjective;
+import org.onosproject.net.flowobjective.FilteringObjective;
+import org.onosproject.net.flowobjective.ObjectiveError;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.pipelines.fabric.FabricConstants;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test cases for fabric.p4 pipeline filtering control block.
+ */
+public class FabricFilteringPipelinerTest extends FabricPipelinerTest {
+
+    /**
+     * 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() {
+        FilteringObjective filteringObjective = buildFilteringObjective(ROUTER_MAC);
+        PipelinerTranslationResult result = pipeliner.pipelinerFilter.filter(filteringObjective);
+
+        List<FlowRule> flowRulesInstalled = (List<FlowRule>) result.flowRules();
+        List<GroupDescription> groupsInstalled = (List<GroupDescription>) result.groups();
+
+        assertTrue(groupsInstalled.isEmpty());
+
+        // in port vlan flow rule
+        FlowRule actualFlowRule = flowRulesInstalled.get(0);
+        FlowRule flowRuleExpected = buildExpectedVlanInPortRule(PORT_1,
+                                                                VlanId.NONE,
+                                                                VLAN_100,
+                                                                FabricConstants.TBL_INGRESS_PORT_VLAN_ID);
+        assertTrue(flowRuleExpected.exactMatch(actualFlowRule));
+
+        // forwarding classifier ipv4
+        actualFlowRule = flowRulesInstalled.get(1);
+        flowRuleExpected = buildExpectedFwdClassifierRule(PORT_1,
+                                                          ROUTER_MAC,
+                                                          Ethernet.TYPE_IPV4,
+                                                          FWD_IPV4_UNICAST);
+        assertTrue(flowRuleExpected.exactMatch(actualFlowRule));
+
+        // forwarding classifier ipv6
+        actualFlowRule = flowRulesInstalled.get(2);
+        flowRuleExpected = buildExpectedFwdClassifierRule(PORT_1,
+                                                          ROUTER_MAC,
+                                                          Ethernet.TYPE_IPV6,
+                                                          FWD_IPV6_UNICAST);
+        assertTrue(flowRuleExpected.exactMatch(actualFlowRule));
+
+        // forwarding classifier mpls
+        actualFlowRule = flowRulesInstalled.get(3);
+        flowRuleExpected = buildExpectedFwdClassifierRule(PORT_1,
+                                                          ROUTER_MAC,
+                                                          Ethernet.MPLS_UNICAST,
+                                                          FWD_MPLS);
+        assertTrue(flowRuleExpected.exactMatch(actualFlowRule));
+    }
+
+    /**
+     * 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() {
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .pushVlan()
+                .setVlanId(VLAN_100)
+                .build();
+        FilteringObjective filteringObjective = DefaultFilteringObjective.builder()
+                .permit()
+                .withPriority(PRIORITY)
+                .withKey(Criteria.matchInPort(PORT_1))
+                .addCondition(Criteria.matchEthDst(MacAddress.IPV4_MULTICAST))
+                .addCondition(Criteria.matchVlanId(VlanId.NONE))
+                .withMeta(treatment)
+                .fromApp(APP_ID)
+                .makePermanent()
+                .add();
+        PipelinerTranslationResult result = pipeliner.pipelinerFilter.filter(filteringObjective);
+        List<FlowRule> flowRulesInstalled = (List<FlowRule>) result.flowRules();
+        List<GroupDescription> groupsInstalled = (List<GroupDescription>) result.groups();
+
+        assertTrue(groupsInstalled.isEmpty());
+
+        // in port vlan flow rule
+        FlowRule actualFlowRule = flowRulesInstalled.get(0);
+        FlowRule flowRuleExpected = buildExpectedVlanInPortRule(PORT_1,
+                                                                VlanId.NONE,
+                                                                VLAN_100,
+                                                                FabricConstants.TBL_INGRESS_PORT_VLAN_ID);
+        assertTrue(flowRuleExpected.exactMatch(actualFlowRule));
+
+        // forwarding classifier
+        actualFlowRule = flowRulesInstalled.get(1);
+        flowRuleExpected = buildExpectedFwdClassifierRule(PORT_1,
+                                                          MacAddress.IPV4_MULTICAST,
+                                                          Ethernet.TYPE_IPV4,
+                                                          FWD_IPV4_MULTICAST);
+        assertTrue(flowRuleExpected.exactMatch(actualFlowRule));
+    }
+
+    /**
+     * 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() {
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .pushVlan()
+                .setVlanId(VLAN_100)
+                .build();
+        FilteringObjective filteringObjective = DefaultFilteringObjective.builder()
+                .permit()
+                .withPriority(PRIORITY)
+                .withKey(Criteria.matchInPort(PORT_1))
+                .addCondition(Criteria.matchEthDst(MacAddress.IPV6_MULTICAST))
+                .addCondition(Criteria.matchVlanId(VlanId.NONE))
+                .withMeta(treatment)
+                .fromApp(APP_ID)
+                .makePermanent()
+                .add();
+        PipelinerTranslationResult result = pipeliner.pipelinerFilter.filter(filteringObjective);
+        List<FlowRule> flowRulesInstalled = (List<FlowRule>) result.flowRules();
+        List<GroupDescription> groupsInstalled = (List<GroupDescription>) result.groups();
+
+        assertTrue(groupsInstalled.isEmpty());
+
+        // in port vlan flow rule
+        FlowRule actualFlowRule = flowRulesInstalled.get(0);
+        FlowRule flowRuleExpected = buildExpectedVlanInPortRule(PORT_1,
+                                                                VlanId.NONE,
+                                                                VLAN_100,
+                                                                FabricConstants.TBL_INGRESS_PORT_VLAN_ID);
+        assertTrue(flowRuleExpected.exactMatch(actualFlowRule));
+
+        // forwarding classifier
+        actualFlowRule = flowRulesInstalled.get(1);
+        flowRuleExpected = buildExpectedFwdClassifierRule(PORT_1,
+                                                          MacAddress.IPV6_MULTICAST,
+                                                          Ethernet.TYPE_IPV6,
+                                                          FWD_IPV6_MULTICAST);
+        assertTrue(flowRuleExpected.exactMatch(actualFlowRule));
+    }
+
+    /**
+     * 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() {
+        FilteringObjective filteringObjective = buildFilteringObjective(null);
+        PipelinerTranslationResult result = pipeliner.pipelinerFilter.filter(filteringObjective);
+        List<FlowRule> flowRulesInstalled = (List<FlowRule>) result.flowRules();
+        List<GroupDescription> groupsInstalled = (List<GroupDescription>) result.groups();
+
+        assertTrue(groupsInstalled.isEmpty());
+
+        // in port vlan flow rule
+        FlowRule actualFlowRule = flowRulesInstalled.get(0);
+        FlowRule flowRuleExpected = buildExpectedVlanInPortRule(PORT_1,
+                                                                VlanId.NONE,
+                                                                VLAN_100,
+                                                                FabricConstants.TBL_INGRESS_PORT_VLAN_ID);
+        assertTrue(flowRuleExpected.exactMatch(actualFlowRule));
+
+        // No rules in forwarding classifier, will do default action: set fwd type to bridging
+    }
+
+    /**
+     * We supports only PERMIT type of filtering objective.
+     */
+    @Test
+    public void testUnsupportedObjective() {
+        FilteringObjective filteringObjective = DefaultFilteringObjective.builder()
+                .deny()
+                .withKey(Criteria.matchInPort(PORT_1))
+                .addCondition(Criteria.matchVlanId(VLAN_100))
+                .fromApp(APP_ID)
+                .makePermanent()
+                .add();
+
+        PipelinerTranslationResult result = pipeliner.pipelinerFilter.filter(filteringObjective);
+        pipeliner.pipelinerFilter.filter(filteringObjective);
+
+        List<FlowRule> flowRulesInstalled = (List<FlowRule>) result.flowRules();
+        List<GroupDescription> groupsInstalled = (List<GroupDescription>) result.groups();
+
+        assertTrue(flowRulesInstalled.isEmpty());
+        assertTrue(groupsInstalled.isEmpty());
+
+        assertTrue(result.error().isPresent());
+        ObjectiveError error = result.error().get();
+        assertEquals(ObjectiveError.UNSUPPORTED, error);
+    }
+
+    /**
+     * 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();
+
+        PipelinerTranslationResult result = pipeliner.pipelinerFilter.filter(filteringObjective);
+        pipeliner.pipelinerFilter.filter(filteringObjective);
+
+        assertTrue(result.error().isPresent());
+        ObjectiveError error = result.error().get();
+        assertEquals(ObjectiveError.BADPARAMS, error);
+
+        // 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();
+
+        result = pipeliner.pipelinerFilter.filter(filteringObjective);
+        pipeliner.pipelinerFilter.filter(filteringObjective);
+
+        assertTrue(result.error().isPresent());
+        error = result.error().get();
+        assertEquals(ObjectiveError.BADPARAMS, error);
+    }
+
+    /* Utilities */
+
+    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 internalVlan,
+                                                 TableId tableId) {
+
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder()
+                .matchInPort(inPort);
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+        if (vlanId == null || vlanId.equals(VlanId.NONE)) {
+            selector.matchPi(VLAN_INVALID);
+            treatment.pushVlan();
+            treatment.setVlanId(internalVlan);
+        } else {
+            selector.matchPi(VLAN_VALID);
+            selector.matchVlanId(vlanId);
+        }
+
+        return DefaultFlowRule.builder()
+                .withPriority(PRIORITY)
+                .withSelector(selector.build())
+                .withTreatment(treatment.build())
+                .fromApp(APP_ID)
+                .forDevice(DEVICE_ID)
+                .makePermanent()
+                .forTable(tableId)
+                .build();
+    }
+
+    private FlowRule buildExpectedFwdClassifierRule(PortNumber inPort,
+                                                    MacAddress dstMac,
+                                                    short ethType,
+                                                    byte fwdClass) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthDst(dstMac)
+                .matchInPort(inPort)
+                .matchEthType(ethType)
+                .build();
+        PiActionParam classParam = new PiActionParam(FabricConstants.ACT_PRM_FWD_TYPE_ID,
+                                                     ImmutableByteSequence.copyFrom(fwdClass));
+        PiAction fwdClassifierAction = PiAction.builder()
+                .withId(FabricConstants.ACT_SET_FORWARDING_TYPE_ID)
+                .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.TBL_FWD_CLASSIFIER_ID)
+                .build();
+    }
+}
diff --git a/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricForwardingPipelineTest.java b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricForwardingPipelineTest.java
new file mode 100644
index 0000000..d580827
--- /dev/null
+++ b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricForwardingPipelineTest.java
@@ -0,0 +1,260 @@
+/*
+ * 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.pipeliner;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.UDP;
+import org.onlab.util.ImmutableByteSequence;
+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.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.group.GroupDescription;
+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.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 {
+
+    /**
+     * Test versatile flag of forwarding objective with ARP match.
+     */
+    @Test
+    public void testAclArp() {
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .wipeDeferred()
+                .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();
+
+        PipelinerTranslationResult result = pipeliner.pipelinerForward.forward(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);
+        FlowRule expectedFlowRule = DefaultFlowRule.builder()
+                .forDevice(DEVICE_ID)
+                .forTable(FabricConstants.TBL_ACL_ID)
+                .withPriority(PRIORITY)
+                .makePermanent()
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .fromApp(APP_ID)
+                .build();
+
+        assertTrue(expectedFlowRule.exactMatch(actualFlowRule));
+    }
+
+    /**
+     * 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();
+
+        PipelinerTranslationResult result = pipeliner.pipelinerForward.forward(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);
+        FlowRule expectedFlowRule = DefaultFlowRule.builder()
+                .forDevice(DEVICE_ID)
+                .forTable(FabricConstants.TBL_ACL_ID)
+                .withPriority(PRIORITY)
+                .makePermanent()
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .fromApp(APP_ID)
+                .build();
+
+        assertTrue(expectedFlowRule.exactMatch(actualFlowRule));
+    }
+
+    /**
+     * Test programming L2 unicast rule to bridging table.
+     */
+    @Test
+    public void testL2Unicast() {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchVlanId(VLAN_100)
+                .matchEthDst(HOST_MAC)
+                .build();
+        testSpecificForward(FabricConstants.TBL_BRIDGING_ID, selector, selector, NEXT_ID_1);
+    }
+
+    @Test
+    public void testL2Broadcast() {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchVlanId(VLAN_100)
+                .build();
+        testSpecificForward(FabricConstants.TBL_BRIDGING_ID, selector, selector, NEXT_ID_1);
+    }
+
+    @Test
+    @Ignore
+    public void testIPv4Unicast() {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(IPV4_UNICAST_ADDR)
+                .build();
+        TrafficSelector expectedSelector = DefaultTrafficSelector.builder()
+                .matchIPDst(IPV4_UNICAST_ADDR)
+                .build();
+        testSpecificForward(FabricConstants.TBL_UNICAST_V4_ID, expectedSelector, selector, NEXT_ID_1);
+    }
+
+    @Test
+    @Ignore
+    public void testIPv4Multicast() {
+        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.TBL_MULTICAST_V4_ID, expectedSelector, selector, NEXT_ID_1);
+    }
+
+    @Test
+    @Ignore
+    public void testIPv6Unicast() {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV6)
+                .matchIPDst(IPV6_UNICAST_ADDR)
+                .build();
+        TrafficSelector expectedSelector = DefaultTrafficSelector.builder()
+                .matchIPDst(IPV6_UNICAST_ADDR)
+                .build();
+        testSpecificForward(FabricConstants.TBL_UNICAST_V6_ID, expectedSelector, selector, NEXT_ID_1);
+    }
+
+    @Test
+    @Ignore
+    public void testIPv6Multicast() {
+        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.TBL_MULTICAST_V6_ID, expectedSelector, selector, NEXT_ID_1);
+    }
+
+    @Test
+    @Ignore
+    public void testMpls() {
+
+    }
+
+    private void testSpecificForward(PiTableId expectedTableId, TrafficSelector expectedSelector,
+                                     TrafficSelector selector, Integer nextId) {
+        ForwardingObjective fwd = DefaultForwardingObjective.builder()
+                .withSelector(selector)
+                .withPriority(PRIORITY)
+                .fromApp(APP_ID)
+                .makePermanent()
+                .withFlag(ForwardingObjective.Flag.SPECIFIC)
+                .nextStep(nextId)
+                .add();
+
+        PipelinerTranslationResult result = pipeliner.pipelinerForward.forward(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);
+        PiActionParam nextIdParam = new PiActionParam(FabricConstants.ACT_PRM_NEXT_ID_ID,
+                                                      ImmutableByteSequence.copyFrom(nextId.byteValue()));
+        PiAction setNextIdAction = PiAction.builder()
+                .withId(FabricConstants.ACT_SET_NEXT_ID_ID)
+                .withParameter(nextIdParam)
+                .build();
+        TrafficTreatment setNextIdTreatment = DefaultTrafficTreatment.builder()
+                .piTableAction(setNextIdAction)
+                .build();
+
+        FlowRule expectedFlowRule = DefaultFlowRule.builder()
+                .forDevice(DEVICE_ID)
+                .forTable(expectedTableId)
+                .withPriority(PRIORITY)
+                .makePermanent()
+                .withSelector(expectedSelector)
+                .withTreatment(setNextIdTreatment)
+                .fromApp(APP_ID)
+                .build();
+
+        assertTrue(expectedFlowRule.exactMatch(actualFlowRule));
+    }
+}
diff --git a/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipelinerTest.java b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipelinerTest.java
new file mode 100644
index 0000000..107752c
--- /dev/null
+++ b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipelinerTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.pipeliner;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.onlab.util.ImmutableByteSequence;
+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.GroupDescription;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.pipelines.fabric.FabricConstants;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test cases for fabric.p4 pipeline next control block.
+ */
+public class FabricNextPipelinerTest extends FabricPipelinerTest {
+
+    /**
+     * Test program output rule for Simple table.
+     */
+    @Test
+    public void testSimpleOutput() {
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(PORT_1)
+                .build();
+        testSimple(treatment);
+    }
+
+    /**
+     * Test program set vlan and output rule for Simple table.
+     */
+    @Test
+    public void testSimpleOutputWithVlanTranslation() {
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setVlanId(VLAN_100)
+                .setOutput(PORT_1)
+                .build();
+        testSimple(treatment);
+    }
+
+    private void testSimple(TrafficTreatment treatment) {
+        NextObjective nextObjective = DefaultNextObjective.builder()
+                .withId(NEXT_ID_1)
+                .withPriority(PRIORITY)
+                .addTreatment(treatment)
+                .withType(NextObjective.Type.SIMPLE)
+                .makePermanent()
+                .fromApp(APP_ID)
+                .add();
+
+        PipelinerTranslationResult result = pipeliner.pipelinerNext.next(nextObjective);
+
+        List<FlowRule> flowRulesInstalled = (List<FlowRule>) result.flowRules();
+        List<GroupDescription> groupsInstalled = (List<GroupDescription>) result.groups();
+        assertEquals(2, flowRulesInstalled.size());
+        assertTrue(groupsInstalled.isEmpty());
+
+        FlowRule actualFlowRule;
+        FlowRule expectedFlowRule;
+
+        // Next id mapping table
+        actualFlowRule = flowRulesInstalled.get(0);
+        byte[] nextIdVal = new byte[]{NEXT_ID_1.byteValue()};
+        PiCriterion nextIdCriterion = PiCriterion.builder()
+                .matchExact(FabricConstants.HF_FABRIC_METADATA_NEXT_ID_ID, nextIdVal)
+                .build();
+        TrafficSelector nextIdSelector = DefaultTrafficSelector.builder()
+                .matchPi(nextIdCriterion)
+                .build();
+        PiActionParam setNextToSimpleParam = new PiActionParam(FabricConstants.ACT_PRM_NEXT_TYPE_ID,
+                                                               ImmutableByteSequence.copyFrom(NEXT_TYPE_SIMPLE));
+        PiAction setNextToSimpleAction = PiAction.builder()
+                .withId(FabricConstants.ACT_SET_NEXT_TYPE_ID)
+                .withParameter(setNextToSimpleParam)
+                .build();
+        TrafficTreatment setNextTypeTreatment = DefaultTrafficTreatment.builder()
+                .piTableAction(setNextToSimpleAction)
+                .build();
+        expectedFlowRule = DefaultFlowRule.builder()
+                .forDevice(DEVICE_ID)
+                .fromApp(APP_ID)
+                .makePermanent()
+                // FIXME: currently next objective doesn't support priority, set priority to zero
+                .withPriority(0)
+                .forTable(FabricConstants.TBL_NEXT_ID_MAPPING_ID)
+                .withSelector(nextIdSelector)
+                .withTreatment(setNextTypeTreatment)
+                .build();
+        assertTrue(expectedFlowRule.exactMatch(actualFlowRule));
+
+        // Simple table
+        actualFlowRule = flowRulesInstalled.get(1);
+        expectedFlowRule = DefaultFlowRule.builder()
+                .forDevice(DEVICE_ID)
+                .fromApp(APP_ID)
+                .makePermanent()
+                // FIXME: currently next objective doesn't support priority, ignore this
+                .withPriority(0)
+                .forTable(FabricConstants.TBL_SIMPLE_ID)
+                .withSelector(nextIdSelector)
+                .withTreatment(treatment)
+                .build();
+        assertTrue(expectedFlowRule.exactMatch(actualFlowRule));
+    }
+
+    /**
+     * Test program ecmp output group for Hashed table.
+     */
+    @Test
+    @Ignore
+    public void testHashedOutput() {
+
+    }
+
+    /**
+     * Test program output group for Broadcast table.
+     */
+    @Test
+    @Ignore
+    public void testBroadcastOutput() {
+
+    }
+}
diff --git a/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricPipelinerTest.java b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricPipelinerTest.java
new file mode 100644
index 0000000..30610ed
--- /dev/null
+++ b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricPipelinerTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.pipeliner;
+
+import org.junit.Before;
+import org.onlab.osgi.ServiceDirectory;
+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.behaviour.PipelinerContext;
+import org.onosproject.net.flow.criteria.PiCriterion;
+import org.onosproject.pipelines.fabric.FabricConstants;
+
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+
+public abstract class FabricPipelinerTest {
+    static final ApplicationId APP_ID = TestApplicationId.create("FabricPipelinerTest");
+    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 VlanId VLAN_100 = VlanId.vlanId("100");
+    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;
+
+    // Forwarding types
+    static final byte FWD_BRIDGING = 0;
+    static final byte FWD_MPLS = 1;
+    static final byte FWD_IPV4_UNICAST = 2;
+    static final byte FWD_IPV4_MULTICAST = 3;
+    static final byte FWD_IPV6_UNICAST = 4;
+    static final byte FWD_IPV6_MULTICAST = 5;
+
+    // Next types
+    static final byte NEXT_TYPE_SIMPLE = 0;
+    static final byte NEXT_TYPE_HASHED = 1;
+    static final byte NEXT_TYPE_BROADCAST = 2;
+    static final byte NEXT_TYPE_PUNT = 3;
+
+    static final PiCriterion VLAN_VALID = PiCriterion.builder()
+            .matchExact(FabricConstants.HF_VLAN_TAG_IS_VALID_ID, new byte[]{1})
+            .build();
+    static final PiCriterion VLAN_INVALID = PiCriterion.builder()
+            .matchExact(FabricConstants.HF_VLAN_TAG_IS_VALID_ID, new byte[]{0})
+            .build();
+
+    FabricPipeliner pipeliner;
+
+    @Before
+    public void setup() {
+        pipeliner = new FabricPipeliner();
+
+        ServiceDirectory serviceDirectory = createNiceMock(ServiceDirectory.class);
+        PipelinerContext pipelinerContext = createNiceMock(PipelinerContext.class);
+        expect(pipelinerContext.directory()).andReturn(serviceDirectory).anyTimes();
+        replay(serviceDirectory, pipelinerContext);
+
+        pipeliner.init(DEVICE_ID, pipelinerContext);
+    }
+}
diff --git a/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/ForwardingFunctionTypeTest.java b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/ForwardingFunctionTypeTest.java
new file mode 100644
index 0000000..9c68cd6
--- /dev/null
+++ b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/ForwardingFunctionTypeTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.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 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
+    @Ignore
+    public void testIpv4Unicast() {
+        selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(IPV4_UNICAST_ADDR)
+                .build();
+        testFft(selector, ForwardingFunctionType.IPV4_UNICAST);
+    }
+
+    @Test
+    @Ignore
+    public void testIpv4Multicast() {
+        selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPDst(IPV4_MCAST_ADDR)
+                .build();
+        testFft(selector, ForwardingFunctionType.IPV4_MULTICAST);
+    }
+
+    @Test
+    @Ignore
+    public void testIpv6Unicast() {
+        selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV6)
+                .matchIPDst(IPV6_UNICAST_ADDR)
+                .build();
+        testFft(selector, ForwardingFunctionType.IPV6_UNICAST);
+    }
+
+    @Test
+    @Ignore
+    public void testIpv6Multicast() {
+        selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV6)
+                .matchIPDst(IPV6_MCAST_ADDR)
+                .build();
+        testFft(selector, ForwardingFunctionType.IPV4_MULTICAST);
+    }
+
+    @Test
+    @Ignore
+    public void testMplsUnicast() {
+        selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.MPLS_UNICAST)
+                .matchMplsLabel(MPLS_10)
+                .matchMplsBos(true)
+                .build();
+        testFft(selector, ForwardingFunctionType.MPLS);
+    }
+
+    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));
+    }
+}