Improve fabric.p4 to reduce pipeline resources and refactor pipeconf impl

This patch affects both the P4 pipeline implementation and the
Java pipeconf.

P4 PIPELINE
- Less tables and smarter use of metadata to reduce inter-tables
dependencies and favor parallel execution of tables.
- Removed unused actions / renamed existing ones to make forwarding
behavior clearer (e.g. ingress_port_vlan table)
- Remove co-existence of simple and hansed table. Hashed should be the
default one, but implementations that do not support action profiles
might compile fabric.p4 to use the simple one.
- Use @name annotations for match fields to make control plane
independent of table implementation.
- Use @hidden to avoid showing actions and table on the p4info that
cannot be controlled at runtime.
- First attempt to support double VLAN cross-connect (xconnect table).
- New design has been tested with "fabric-refactoring" branch of
fabric-p4test:
github.com/opennetworkinglab/fabric-p4test/tree/fabric-refactoring

JAVA PIPECONF
This patch brings a major refactoring that reflects the experience
gathered in the past months of working on fabric.p4 and reasoning on its
pipeconf implementation. Indeed, the FlowObjective API is
under-specified and sometimes ambiguous which makes the process of
creating and maintaining a pipeliner implementation tedious. This
refactoring brings a simplified implementation by removing unused/
unnecessary functionalities and by recognizing commonality when possible
(e.g. by means of abstract and utility classes). It also makes design
patterns more explicit and consistent. Overall, the goal is to reduce
technical debt and to make it easier to support new features as we
evolve fabric.p4

Changes include:
- Changes in pipeliner/interpreter to reflect new pipeline design.
- By default translate objective treatment to PiAction. This favors
debuggability of flow rules in ONOS.
- Support new NextObjective’s NextTreatment class.
- Remove lots of unused/unnecessary code (e.g. async callback handling
for pending objective install status in pipeliner as current
implementation was always returning success)
- Gather commonality in abstract classes and simplify implementation
for objective translator (filtering, forwarding, next)
- New implementation of ForwardingFunctionTypes (FFT) that looks at
criterion instance values along with their types (to avoid relying on
case-specific if-else conditions to recognize variants of an FFT)
- Adaptive translation of NextObjective based on presence of simple or
hashed table.
- Support DENY FilteringObjective

Also:
- Fix onos-p4-gen-constants to avoid generating conflicting
PiMatchFieldId variable names.
- Install Graphviz tools in p4vm to generate p4c graphs
- Generate p4c graphs by default when compiling fabric.p4
- Use more compact Hex string when printing PI values

Change-Id: Ife79e44054dc5bc48833f95d0551a7370150eac5
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/AbstractFabricHandlerBehavior.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/AbstractFabricHandlerBehavior.java
new file mode 100644
index 0000000..2753844
--- /dev/null
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/AbstractFabricHandlerBehavior.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2018-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;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.net.driver.DriverHandler;
+import org.onosproject.net.pi.model.PiPipeconfId;
+import org.onosproject.net.pi.service.PiPipeconfService;
+import org.slf4j.Logger;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.lang.String.format;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Abstract implementation of HandlerBehaviour for the fabric pipeconf
+ * behaviors.
+ */
+public class AbstractFabricHandlerBehavior extends AbstractHandlerBehaviour {
+
+    protected final Logger log = getLogger(getClass());
+
+    protected FabricCapabilities capabilities;
+
+    @Override
+    public void setHandler(DriverHandler handler) {
+        super.setHandler(handler);
+        final PiPipeconfService pipeconfService = handler().get(PiPipeconfService.class);
+        setCapabilities(handler().data().deviceId(), pipeconfService);
+    }
+
+    private void setCapabilities(DeviceId deviceId, PiPipeconfService pipeconfService) {
+        checkNotNull(deviceId);
+        checkNotNull(pipeconfService);
+        // Get pipeconf capabilities.
+        final PiPipeconfId pipeconfId = pipeconfService.ofDevice(deviceId)
+                .orElse(null);
+        if (pipeconfId == null) {
+            throw new IllegalStateException(format(
+                    "Unable to get pipeconf ID of device %s", deviceId.toString()));
+        }
+        if (!pipeconfService.getPipeconf(pipeconfId).isPresent()) {
+            throw new IllegalStateException(format(
+                    "Pipeconf '%s' is not registered ", pipeconfId));
+        }
+        this.capabilities = new FabricCapabilities(
+                pipeconfService.getPipeconf(pipeconfId).get());
+    }
+}
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricCapabilities.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricCapabilities.java
new file mode 100644
index 0000000..07986f9
--- /dev/null
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricCapabilities.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2018-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;
+
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.slf4j.Logger;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.CPU_PORT_TXT;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Representation of the capabilities of a given fabric pipeconf.
+ */
+public class FabricCapabilities {
+
+    private final Logger log = getLogger(getClass());
+
+    private final PiPipeconf pipeconf;
+
+    FabricCapabilities(PiPipeconf pipeconf) {
+        this.pipeconf = checkNotNull(pipeconf);
+    }
+
+    public boolean hasHashedTable() {
+        return pipeconf.pipelineModel()
+                .table(FabricConstants.FABRIC_INGRESS_NEXT_HASHED).isPresent();
+    }
+
+    public Optional<Integer> cpuPort() {
+        // This is probably brittle, but needed to dynamically get the CPU port
+        // for different platforms.
+        if (!pipeconf.extension(CPU_PORT_TXT).isPresent()) {
+            log.warn("Missing {} extension in pipeconf {}", CPU_PORT_TXT, pipeconf.id());
+            return Optional.empty();
+        }
+        try {
+            final InputStream stream = pipeconf.extension(CPU_PORT_TXT).get();
+            final BufferedReader buff = new BufferedReader(
+                    new InputStreamReader(stream));
+            final String str = buff.readLine();
+            buff.close();
+            if (str == null) {
+                log.error("Empty CPU port file for {}", pipeconf.id());
+                return Optional.empty();
+            }
+            try {
+                return Optional.of(Integer.parseInt(str));
+            } catch (NumberFormatException e) {
+                log.error("Invalid CPU port for {}: {}", pipeconf.id(), str);
+                return Optional.empty();
+            }
+        } catch (IOException e) {
+            log.error("Unable to read CPU port file of {}: {}",
+                      pipeconf.id(), e.getMessage());
+            return Optional.empty();
+        }
+    }
+}
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricConstants.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricConstants.java
index 07bb862..9ecc65f 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricConstants.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricConstants.java
@@ -33,55 +33,49 @@
     }
 
     // Header field IDs
-    public static final PiMatchFieldId HDR_VLAN_TAG_VLAN_ID =
-            PiMatchFieldId.of("hdr.vlan_tag.vlan_id");
+    public static final PiMatchFieldId HDR_ICMP_CODE =
+            PiMatchFieldId.of("icmp_code");
+    public static final PiMatchFieldId HDR_IG_PORT =
+            PiMatchFieldId.of("ig_port");
+    public static final PiMatchFieldId HDR_ETH_DST =
+            PiMatchFieldId.of("eth_dst");
+    public static final PiMatchFieldId HDR_GTP_IPV4_DST =
+            PiMatchFieldId.of("gtp_ipv4_dst");
+    public static final PiMatchFieldId HDR_VLAN_IS_VALID =
+            PiMatchFieldId.of("vlan_is_valid");
+    public static final PiMatchFieldId HDR_IPV4_DST =
+            PiMatchFieldId.of("ipv4_dst");
+    public static final PiMatchFieldId HDR_INT_IS_VALID =
+            PiMatchFieldId.of("int_is_valid");
+    public static final PiMatchFieldId HDR_EG_PORT =
+            PiMatchFieldId.of("eg_port");
+    public static final PiMatchFieldId HDR_EG_SPEC =
+            PiMatchFieldId.of("eg_spec");
+    public static final PiMatchFieldId HDR_IPV4_SRC =
+            PiMatchFieldId.of("ipv4_src");
+    public static final PiMatchFieldId HDR_IPV6_DST =
+            PiMatchFieldId.of("ipv6_dst");
+    public static final PiMatchFieldId HDR_ETH_TYPE =
+            PiMatchFieldId.of("eth_type");
     public static final PiMatchFieldId HDR_MPLS_LABEL =
-            PiMatchFieldId.of("hdr.mpls.label");
-    public static final PiMatchFieldId STANDARD_METADATA_EGRESS_PORT =
-            PiMatchFieldId.of("standard_metadata.egress_port");
-    public static final PiMatchFieldId STANDARD_METADATA_INGRESS_PORT =
-            PiMatchFieldId.of("standard_metadata.ingress_port");
-    public static final PiMatchFieldId HDR_VLAN_TAG_IS_VALID =
-            PiMatchFieldId.of("hdr.vlan_tag.is_valid");
-    public static final PiMatchFieldId HDR_ICMP_ICMP_CODE =
-            PiMatchFieldId.of("hdr.icmp.icmp_code");
-    public static final PiMatchFieldId HDR_INT_HEADER_IS_VALID =
-            PiMatchFieldId.of("hdr.int_header.is_valid");
-    public static final PiMatchFieldId HDR_ETHERNET_SRC_ADDR =
-            PiMatchFieldId.of("hdr.ethernet.src_addr");
-    public static final PiMatchFieldId HDR_ICMP_ICMP_TYPE =
-            PiMatchFieldId.of("hdr.icmp.icmp_type");
-    public static final PiMatchFieldId HDR_VLAN_TAG_ETHER_TYPE =
-            PiMatchFieldId.of("hdr.vlan_tag.ether_type");
-    public static final PiMatchFieldId HDR_IPV4_DST_ADDR =
-            PiMatchFieldId.of("hdr.ipv4.dst_addr");
-    public static final PiMatchFieldId HDR_INT_HEADER_INSTRUCTION_MASK_0003 =
-            PiMatchFieldId.of("hdr.int_header.instruction_mask_0003");
-    public static final PiMatchFieldId FABRIC_METADATA_L4_SRC_PORT =
-            PiMatchFieldId.of("fabric_metadata.l4_src_port");
-    public static final PiMatchFieldId FABRIC_METADATA_L4_DST_PORT =
-            PiMatchFieldId.of("fabric_metadata.l4_dst_port");
-    public static final PiMatchFieldId STANDARD_METADATA_EGRESS_SPEC =
-            PiMatchFieldId.of("standard_metadata.egress_spec");
-    public static final PiMatchFieldId GTPU_IPV4_DST_ADDR =
-            PiMatchFieldId.of("gtpu_ipv4.dst_addr");
-    public static final PiMatchFieldId FABRIC_METADATA_IP_PROTO =
-            PiMatchFieldId.of("fabric_metadata.ip_proto");
-    public static final PiMatchFieldId FABRIC_METADATA_NEXT_ID =
-            PiMatchFieldId.of("fabric_metadata.next_id");
-    public static final PiMatchFieldId HDR_IPV4_SRC_ADDR =
-            PiMatchFieldId.of("hdr.ipv4.src_addr");
-    public static final PiMatchFieldId HDR_INT_HEADER_INSTRUCTION_MASK_0407 =
-            PiMatchFieldId.of("hdr.int_header.instruction_mask_0407");
-    public static final PiMatchFieldId HDR_IPV6_DST_ADDR =
-            PiMatchFieldId.of("hdr.ipv6.dst_addr");
-    public static final PiMatchFieldId IPV4_DST_ADDR =
-            PiMatchFieldId.of("ipv4.dst_addr");
-    public static final PiMatchFieldId HDR_ETHERNET_DST_ADDR =
-            PiMatchFieldId.of("hdr.ethernet.dst_addr");
+            PiMatchFieldId.of("mpls_label");
+    public static final PiMatchFieldId HDR_ETH_SRC =
+            PiMatchFieldId.of("eth_src");
+    public static final PiMatchFieldId HDR_IP_PROTO =
+            PiMatchFieldId.of("ip_proto");
+    public static final PiMatchFieldId HDR_L4_DPORT =
+            PiMatchFieldId.of("l4_dport");
+    public static final PiMatchFieldId HDR_NEXT_ID =
+            PiMatchFieldId.of("next_id");
+    public static final PiMatchFieldId HDR_ICMP_TYPE =
+            PiMatchFieldId.of("icmp_type");
+    public static final PiMatchFieldId HDR_VLAN_ID =
+            PiMatchFieldId.of("vlan_id");
+    public static final PiMatchFieldId HDR_L4_SPORT =
+            PiMatchFieldId.of("l4_sport");
     // Table IDs
-    public static final PiTableId FABRIC_INGRESS_FORWARDING_ACL =
-            PiTableId.of("FabricIngress.forwarding.acl");
+    public static final PiTableId FABRIC_INGRESS_NEXT_MULTICAST =
+            PiTableId.of("FabricIngress.next.multicast");
     public static final PiTableId FABRIC_INGRESS_NEXT_HASHED =
             PiTableId.of("FabricIngress.next.hashed");
     public static final PiTableId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_SOURCE_TB_INT_SOURCE =
@@ -92,12 +86,12 @@
             PiTableId.of("FabricIngress.process_set_source_sink.tb_set_sink");
     public static final PiTableId FABRIC_INGRESS_FORWARDING_ROUTING_V4 =
             PiTableId.of("FabricIngress.forwarding.routing_v4");
-    public static final PiTableId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_TB_INT_INST_0407 =
-            PiTableId.of("FabricEgress.process_int_main.process_int_transit.tb_int_inst_0407");
     public static final PiTableId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_TB_INT_INSERT =
             PiTableId.of("FabricEgress.process_int_main.process_int_transit.tb_int_insert");
-    public static final PiTableId FABRIC_INGRESS_NEXT_SIMPLE =
-            PiTableId.of("FabricIngress.next.simple");
+    public static final PiTableId FABRIC_INGRESS_ACL_ACL =
+            PiTableId.of("FabricIngress.acl.acl");
+    public static final PiTableId FABRIC_INGRESS_NEXT_XCONNECT =
+            PiTableId.of("FabricIngress.next.xconnect");
     public static final PiTableId FABRIC_INGRESS_FILTERING_FWD_CLASSIFIER =
             PiTableId.of("FabricIngress.filtering.fwd_classifier");
     public static final PiTableId FABRIC_INGRESS_PROCESS_SET_SOURCE_SINK_TB_SET_SOURCE =
@@ -106,20 +100,18 @@
             PiTableId.of("FabricIngress.forwarding.bridging");
     public static final PiTableId FABRIC_INGRESS_SPGW_INGRESS_S1U_FILTER_TABLE =
             PiTableId.of("FabricIngress.spgw_ingress.s1u_filter_table");
-    public static final PiTableId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_TB_INT_INST_0003 =
-            PiTableId.of("FabricEgress.process_int_main.process_int_transit.tb_int_inst_0003");
-    public static final PiTableId FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN =
-            PiTableId.of("FabricIngress.filtering.ingress_port_vlan");
     public static final PiTableId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_REPORT_TB_GENERATE_REPORT =
             PiTableId.of("FabricEgress.process_int_main.process_int_report.tb_generate_report");
+    public static final PiTableId FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN =
+            PiTableId.of("FabricIngress.filtering.ingress_port_vlan");
     public static final PiTableId FABRIC_INGRESS_SPGW_INGRESS_DL_SESS_LOOKUP =
             PiTableId.of("FabricIngress.spgw_ingress.dl_sess_lookup");
     public static final PiTableId FABRIC_EGRESS_EGRESS_NEXT_EGRESS_VLAN =
             PiTableId.of("FabricEgress.egress_next.egress_vlan");
-    public static final PiTableId FABRIC_INGRESS_NEXT_MULTICAST =
-            PiTableId.of("FabricIngress.next.multicast");
-    public static final PiTableId FABRIC_INGRESS_NEXT_VLAN_META =
-            PiTableId.of("FabricIngress.next.vlan_meta");
+    public static final PiTableId FABRIC_INGRESS_NEXT_NEXT_VLAN =
+            PiTableId.of("FabricIngress.next.next_vlan");
+    public static final PiTableId FABRIC_INGRESS_NEXT_SIMPLE =
+            PiTableId.of("FabricIngress.next.simple");
     public static final PiTableId FABRIC_INGRESS_FORWARDING_ROUTING_V6 =
             PiTableId.of("FabricIngress.forwarding.routing_v6");
     // Indirect Counter IDs
@@ -128,12 +120,10 @@
     public static final PiCounterId FABRIC_INGRESS_PORT_COUNTERS_CONTROL_INGRESS_PORT_COUNTER =
             PiCounterId.of("FabricIngress.port_counters_control.ingress_port_counter");
     // Direct Counter IDs
-    public static final PiCounterId FABRIC_INGRESS_FORWARDING_ACL_COUNTER =
-            PiCounterId.of("FabricIngress.forwarding.acl_counter");
     public static final PiCounterId FABRIC_INGRESS_NEXT_MULTICAST_COUNTER =
             PiCounterId.of("FabricIngress.next.multicast_counter");
-    public static final PiCounterId FABRIC_INGRESS_NEXT_VLAN_META_COUNTER =
-            PiCounterId.of("FabricIngress.next.vlan_meta_counter");
+    public static final PiCounterId FABRIC_INGRESS_NEXT_SIMPLE_COUNTER =
+            PiCounterId.of("FabricIngress.next.simple_counter");
     public static final PiCounterId FABRIC_INGRESS_FILTERING_FWD_CLASSIFIER_COUNTER =
             PiCounterId.of("FabricIngress.filtering.fwd_classifier_counter");
     public static final PiCounterId FABRIC_INGRESS_FORWARDING_BRIDGING_COUNTER =
@@ -146,12 +136,16 @@
             PiCounterId.of("FabricEgress.process_int_main.process_int_source.counter_int_source");
     public static final PiCounterId FABRIC_INGRESS_SPGW_INGRESS_UE_COUNTER =
             PiCounterId.of("FabricIngress.spgw_ingress.ue_counter");
-    public static final PiCounterId FABRIC_INGRESS_NEXT_SIMPLE_COUNTER =
-            PiCounterId.of("FabricIngress.next.simple_counter");
     public static final PiCounterId FABRIC_INGRESS_PROCESS_SET_SOURCE_SINK_COUNTER_SET_SINK =
             PiCounterId.of("FabricIngress.process_set_source_sink.counter_set_sink");
     public static final PiCounterId FABRIC_EGRESS_EGRESS_NEXT_EGRESS_VLAN_COUNTER =
             PiCounterId.of("FabricEgress.egress_next.egress_vlan_counter");
+    public static final PiCounterId FABRIC_INGRESS_ACL_ACL_COUNTER =
+            PiCounterId.of("FabricIngress.acl.acl_counter");
+    public static final PiCounterId FABRIC_INGRESS_NEXT_XCONNECT_COUNTER =
+            PiCounterId.of("FabricIngress.next.xconnect_counter");
+    public static final PiCounterId FABRIC_INGRESS_NEXT_NEXT_VLAN_COUNTER =
+            PiCounterId.of("FabricIngress.next.next_vlan_counter");
     public static final PiCounterId FABRIC_INGRESS_FORWARDING_ROUTING_V6_COUNTER =
             PiCounterId.of("FabricIngress.forwarding.routing_v6_counter");
     public static final PiCounterId FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN_COUNTER =
@@ -161,151 +155,73 @@
     public static final PiCounterId FABRIC_INGRESS_NEXT_HASHED_COUNTER =
             PiCounterId.of("FabricIngress.next.hashed_counter");
     // Action IDs
-    public static final PiActionId FABRIC_INGRESS_FORWARDING_PUNT_TO_CPU =
-            PiActionId.of("FabricIngress.forwarding.punt_to_cpu");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0407_I5 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0407_i5");
-    public static final PiActionId FABRIC_INGRESS_NEXT_MPLS_ROUTING_V6_SIMPLE =
-            PiActionId.of("FabricIngress.next.mpls_routing_v6_simple");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0407_I12 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0407_i12");
+    public static final PiActionId FABRIC_INGRESS_NEXT_SET_NEXT_ID_XCONNECT =
+            PiActionId.of("FabricIngress.next.set_next_id_xconnect");
     public static final PiActionId FABRIC_INGRESS_FORWARDING_NOP_ROUTING_V4 =
             PiActionId.of("FabricIngress.forwarding.nop_routing_v4");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0407_I10 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0407_i10");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0407_I11 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0407_i11");
-    public static final PiActionId FABRIC_INGRESS_FILTERING_NOP_INGRESS_PORT_VLAN =
-            PiActionId.of("FabricIngress.filtering.nop_ingress_port_vlan");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0407_I14 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0407_i14");
+    public static final PiActionId FABRIC_INGRESS_FILTERING_PERMIT_WITH_INTERNAL_VLAN =
+            PiActionId.of("FabricIngress.filtering.permit_with_internal_vlan");
+    public static final PiActionId FABRIC_INGRESS_NEXT_ROUTING_HASHED =
+            PiActionId.of("FabricIngress.next.routing_hashed");
     public static final PiActionId FABRIC_INGRESS_FORWARDING_SET_NEXT_ID_BRIDGING =
             PiActionId.of("FabricIngress.forwarding.set_next_id_bridging");
     public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_SOURCE_INT_SOURCE_DSCP =
             PiActionId.of("FabricEgress.process_int_main.process_int_source.int_source_dscp");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0407_I0 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0407_i0");
     public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INIT_METADATA =
             PiActionId.of("FabricEgress.process_int_main.process_int_transit.init_metadata");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0003_I14 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0003_i14");
-    public static final PiActionId FABRIC_EGRESS_EGRESS_NEXT_POP_VLAN =
-            PiActionId.of("FabricEgress.egress_next.pop_vlan");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0003_I4 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0003_i4");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0407_I2 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0407_i2");
+    public static final PiActionId FABRIC_INGRESS_ACL_DROP =
+            PiActionId.of("FabricIngress.acl.drop");
     public static final PiActionId FABRIC_INGRESS_NEXT_SET_VLAN =
             PiActionId.of("FabricIngress.next.set_vlan");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0407_I4 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0407_i4");
-    public static final PiActionId FABRIC_EGRESS_SPGW_EGRESS_GTPU_ENCAP =
-            PiActionId.of("FabricEgress.spgw_egress.gtpu_encap");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0003_I12 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0003_i12");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0003_I13 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0003_i13");
-    public static final PiActionId FABRIC_INGRESS_FILTERING_SET_VLAN =
-            PiActionId.of("FabricIngress.filtering.set_vlan");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0003_I11 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0003_i11");
-    public static final PiActionId FABRIC_EGRESS_PKT_IO_EGRESS_POP_VLAN =
-            PiActionId.of("FabricEgress.pkt_io_egress.pop_vlan");
-    public static final PiActionId FABRIC_INGRESS_NEXT_L3_ROUTING_SIMPLE =
-            PiActionId.of("FabricIngress.next.l3_routing_simple");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0003_I15 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0003_i15");
-    public static final PiActionId FABRIC_INGRESS_NEXT_SET_MCAST_GROUP =
-            PiActionId.of("FabricIngress.next.set_mcast_group");
+    public static final PiActionId FABRIC_INGRESS_ACL_NOP_ACL =
+            PiActionId.of("FabricIngress.acl.nop_acl");
+    public static final PiActionId FABRIC_INGRESS_NEXT_OUTPUT_XCONNECT =
+            PiActionId.of("FabricIngress.next.output_xconnect");
+    public static final PiActionId FABRIC_INGRESS_ACL_SET_NEXT_ID_ACL =
+            PiActionId.of("FabricIngress.acl.set_next_id_acl");
+    public static final PiActionId FABRIC_INGRESS_FILTERING_PERMIT =
+            PiActionId.of("FabricIngress.filtering.permit");
     public static final PiActionId FABRIC_INGRESS_FORWARDING_SET_NEXT_ID_ROUTING_V4 =
             PiActionId.of("FabricIngress.forwarding.set_next_id_routing_v4");
     public static final PiActionId FABRIC_INGRESS_FORWARDING_SET_NEXT_ID_ROUTING_V6 =
             PiActionId.of("FabricIngress.forwarding.set_next_id_routing_v6");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0407_I13 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0407_i13");
+    public static final PiActionId FABRIC_INGRESS_NEXT_ROUTING_SIMPLE =
+            PiActionId.of("FabricIngress.next.routing_simple");
     public static final PiActionId FABRIC_INGRESS_SPGW_INGRESS_SET_DL_SESS_INFO =
             PiActionId.of("FabricIngress.spgw_ingress.set_dl_sess_info");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0407_I7 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0407_i7");
-    public static final PiActionId FABRIC_INGRESS_FILTERING_PUSH_INTERNAL_VLAN =
-            PiActionId.of("FabricIngress.filtering.push_internal_vlan");
-    public static final PiActionId FABRIC_INGRESS_FORWARDING_CLONE_TO_CPU =
-            PiActionId.of("FabricIngress.forwarding.clone_to_cpu");
-    public static final PiActionId FABRIC_INGRESS_SPGW_INGRESS_GTPU_DECAP =
-            PiActionId.of("FabricIngress.spgw_ingress.gtpu_decap");
+    public static final PiActionId FABRIC_INGRESS_NEXT_OUTPUT_HASHED =
+            PiActionId.of("FabricIngress.next.output_hashed");
     public static final PiActionId FABRIC_INGRESS_FORWARDING_POP_MPLS_AND_NEXT =
             PiActionId.of("FabricIngress.forwarding.pop_mpls_and_next");
-    public static final PiActionId DROP_NOW = PiActionId.of("drop_now");
-    public static final PiActionId FABRIC_INGRESS_NEXT_L3_ROUTING_HASHED =
-            PiActionId.of("FabricIngress.next.l3_routing_hashed");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0003_I10 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0003_i10");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0407_I8 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0407_i8");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0003_I0 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0003_i0");
+    public static final PiActionId FABRIC_INGRESS_NEXT_MPLS_ROUTING_SIMPLE =
+            PiActionId.of("FabricIngress.next.mpls_routing_simple");
+    public static final PiActionId FABRIC_INGRESS_ACL_PUNT_TO_CPU =
+            PiActionId.of("FabricIngress.acl.punt_to_cpu");
+    public static final PiActionId FABRIC_EGRESS_EGRESS_NEXT_POP_VLAN =
+            PiActionId.of("FabricEgress.egress_next.pop_vlan");
     public static final PiActionId FABRIC_INGRESS_PROCESS_SET_SOURCE_SINK_INT_SET_SINK =
             PiActionId.of("FabricIngress.process_set_source_sink.int_set_sink");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_SINK_INT_SINK =
-            PiActionId.of("FabricEgress.process_int_main.process_int_sink.int_sink");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0003_I1 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0003_i1");
-    public static final PiActionId FABRIC_INGRESS_NEXT_MPLS_ROUTING_V4_HASHED =
-            PiActionId.of("FabricIngress.next.mpls_routing_v4_hashed");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0407_I1 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0407_i1");
+    public static final PiActionId FABRIC_INGRESS_NEXT_MPLS_ROUTING_HASHED =
+            PiActionId.of("FabricIngress.next.mpls_routing_hashed");
     public static final PiActionId FABRIC_INGRESS_PROCESS_SET_SOURCE_SINK_INT_SET_SOURCE =
             PiActionId.of("FabricIngress.process_set_source_sink.int_set_source");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0407_I3 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0407_i3");
     public static final PiActionId NOP = PiActionId.of("nop");
-    public static final PiActionId FABRIC_INGRESS_FORWARDING_DROP =
-            PiActionId.of("FabricIngress.forwarding.drop");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0407_I6 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0407_i6");
     public static final PiActionId FABRIC_INGRESS_NEXT_OUTPUT_SIMPLE =
             PiActionId.of("FabricIngress.next.output_simple");
-    public static final PiActionId FABRIC_INGRESS_FILTERING_DROP =
-            PiActionId.of("FabricIngress.filtering.drop");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_SINK_RESTORE_HEADER =
-            PiActionId.of("FabricEgress.process_int_main.process_int_sink.restore_header");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0407_I9 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0407_i9");
+    public static final PiActionId FABRIC_INGRESS_ACL_CLONE_TO_CPU =
+            PiActionId.of("FabricIngress.acl.clone_to_cpu");
+    public static final PiActionId FABRIC_INGRESS_FILTERING_DENY =
+            PiActionId.of("FabricIngress.filtering.deny");
     public static final PiActionId FABRIC_INGRESS_FILTERING_SET_FORWARDING_TYPE =
             PiActionId.of("FabricIngress.filtering.set_forwarding_type");
-    public static final PiActionId FABRIC_INGRESS_NEXT_SET_VLAN_OUTPUT =
-            PiActionId.of("FabricIngress.next.set_vlan_output");
     public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_REPORT_DO_REPORT_ENCAPSULATION =
             PiActionId.of("FabricEgress.process_int_main.process_int_report.do_report_encapsulation");
     public static final PiActionId NO_ACTION = PiActionId.of("NoAction");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0003_I8 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0003_i8");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0003_I9 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0003_i9");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0407_I15 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0407_i15");
-    public static final PiActionId FABRIC_INGRESS_NEXT_MPLS_ROUTING_V4_SIMPLE =
-            PiActionId.of("FabricIngress.next.mpls_routing_v4_simple");
-    public static final PiActionId FABRIC_INGRESS_FORWARDING_NOP_ACL =
-            PiActionId.of("FabricIngress.forwarding.nop_acl");
-    public static final PiActionId FABRIC_INGRESS_NEXT_MPLS_ROUTING_V6_HASHED =
-            PiActionId.of("FabricIngress.next.mpls_routing_v6_hashed");
-    public static final PiActionId FABRIC_INGRESS_NEXT_L3_ROUTING_VLAN =
-            PiActionId.of("FabricIngress.next.l3_routing_vlan");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0003_I2 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0003_i2");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0003_I3 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0003_i3");
-    public static final PiActionId FABRIC_INGRESS_FORWARDING_SET_NEXT_ID_ACL =
-            PiActionId.of("FabricIngress.forwarding.set_next_id_acl");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0003_I5 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0003_i5");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0003_I6 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0003_i6");
-    public static final PiActionId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INT_SET_HEADER_0003_I7 =
-            PiActionId.of("FabricEgress.process_int_main.process_int_transit.int_set_header_0003_i7");
+    public static final PiActionId FABRIC_INGRESS_NEXT_SET_MCAST_GROUP_ID =
+            PiActionId.of("FabricIngress.next.set_mcast_group_id");
     // Action Param IDs
     public static final PiActionParamId DMAC = PiActionParamId.of("dmac");
+    public static final PiActionParamId INS_CNT = PiActionParamId.of("ins_cnt");
     public static final PiActionParamId MON_IP = PiActionParamId.of("mon_ip");
     public static final PiActionParamId TEID = PiActionParamId.of("teid");
     public static final PiActionParamId INS_MASK0407 =
@@ -322,22 +238,21 @@
     public static final PiActionParamId SMAC = PiActionParamId.of("smac");
     public static final PiActionParamId MON_PORT =
             PiActionParamId.of("mon_port");
-    public static final PiActionParamId GID = PiActionParamId.of("gid");
-    public static final PiActionParamId NEW_VLAN_ID =
-            PiActionParamId.of("new_vlan_id");
     public static final PiActionParamId FWD_TYPE =
             PiActionParamId.of("fwd_type");
     public static final PiActionParamId MON_MAC = PiActionParamId.of("mon_mac");
     public static final PiActionParamId SRC_MAC = PiActionParamId.of("src_mac");
     public static final PiActionParamId NEXT_ID = PiActionParamId.of("next_id");
-    public static final PiActionParamId INS_CNT = PiActionParamId.of("ins_cnt");
+    public static final PiActionParamId GROUP_ID =
+            PiActionParamId.of("group_id");
     public static final PiActionParamId SWITCH_ID =
             PiActionParamId.of("switch_id");
     public static final PiActionParamId MAX_HOP = PiActionParamId.of("max_hop");
+    public static final PiActionParamId VLAN_ID = PiActionParamId.of("vlan_id");
     public static final PiActionParamId SRC_IP = PiActionParamId.of("src_ip");
     // Action Profile IDs
-    public static final PiActionProfileId FABRIC_INGRESS_NEXT_ECMP_SELECTOR =
-            PiActionProfileId.of("FabricIngress.next.ecmp_selector");
+    public static final PiActionProfileId FABRIC_INGRESS_NEXT_HASHED_SELECTOR =
+            PiActionProfileId.of("FabricIngress.next.hashed_selector");
     // Packet Metadata IDs
     public static final PiControlMetadataId INGRESS_PORT =
             PiControlMetadataId.of("ingress_port");
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/IntProgrammableImpl.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricIntProgrammable.java
similarity index 95%
rename from pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/IntProgrammableImpl.java
rename to pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricIntProgrammable.java
index dec6332..a2bf569 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/IntProgrammableImpl.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricIntProgrammable.java
@@ -28,7 +28,6 @@
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.config.NetworkConfigService;
-import org.onosproject.net.driver.AbstractHandlerBehaviour;
 import org.onosproject.net.flow.DefaultFlowRule;
 import org.onosproject.net.flow.DefaultTrafficSelector;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
@@ -46,22 +45,19 @@
 import org.onosproject.net.pi.runtime.PiAction;
 import org.onosproject.net.pi.runtime.PiActionParam;
 import org.onosproject.provider.general.device.api.GeneralProviderDeviceConfig;
-import org.slf4j.Logger;
 
 import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.StreamSupport;
 
 import static org.onlab.util.ImmutableByteSequence.copyFrom;
-import static org.slf4j.LoggerFactory.getLogger;
 
 /**
  * Implementation of INT programmable behavior for fabric.p4. Currently supports
  * only SOURCE and TRANSIT functionalities.
  */
-public class IntProgrammableImpl extends AbstractHandlerBehaviour implements IntProgrammable {
-
-    private final Logger log = getLogger(getClass());
+public class FabricIntProgrammable extends AbstractFabricHandlerBehavior
+        implements IntProgrammable {
 
     // TODO: change this value to the value of diameter of a network.
     private static final int DEFAULT_PRIORITY = 10000;
@@ -145,7 +141,7 @@
                 .build();
         TrafficSelector selector = DefaultTrafficSelector.builder()
                 .matchPi(PiCriterion.builder().matchExact(
-                        FabricConstants.HDR_INT_HEADER_IS_VALID, (byte) 0x01)
+                        FabricConstants.HDR_INT_IS_VALID, (byte) 0x01)
                                  .build())
                 .build();
 
@@ -171,7 +167,7 @@
         }
 
         PiCriterion ingressCriterion = PiCriterion.builder()
-                .matchExact(FabricConstants.STANDARD_METADATA_INGRESS_PORT, port.toLong())
+                .matchExact(FabricConstants.HDR_IG_PORT, port.toLong())
                 .build();
         TrafficSelector srcSelector = DefaultTrafficSelector.builder()
                 .matchPi(ingressCriterion)
@@ -203,7 +199,7 @@
         }
 
         PiCriterion egressCriterion = PiCriterion.builder()
-                .matchExact(FabricConstants.STANDARD_METADATA_EGRESS_PORT, port.toLong())
+                .matchExact(FabricConstants.HDR_EG_PORT, port.toLong())
                 .build();
         TrafficSelector sinkSelector = DefaultTrafficSelector.builder()
                 .matchPi(egressCriterion)
@@ -316,28 +312,28 @@
                 case TCP_SRC:
                     sBuilder.matchPi(
                             PiCriterion.builder().matchTernary(
-                                    FabricConstants.FABRIC_METADATA_L4_SRC_PORT,
+                                    FabricConstants.HDR_L4_SPORT,
                                     ((TcpPortCriterion) criterion).tcpPort().toInt(), PORTMASK)
                                     .build());
                     break;
                 case UDP_SRC:
                     sBuilder.matchPi(
                             PiCriterion.builder().matchTernary(
-                                    FabricConstants.FABRIC_METADATA_L4_SRC_PORT,
+                                    FabricConstants.HDR_L4_SPORT,
                                     ((UdpPortCriterion) criterion).udpPort().toInt(), PORTMASK)
                                     .build());
                     break;
                 case TCP_DST:
                     sBuilder.matchPi(
                             PiCriterion.builder().matchTernary(
-                                    FabricConstants.FABRIC_METADATA_L4_DST_PORT,
+                                    FabricConstants.HDR_L4_DPORT,
                                     ((TcpPortCriterion) criterion).tcpPort().toInt(), PORTMASK)
                                     .build());
                     break;
                 case UDP_DST:
                     sBuilder.matchPi(
                             PiCriterion.builder().matchTernary(
-                                    FabricConstants.FABRIC_METADATA_L4_DST_PORT,
+                                    FabricConstants.HDR_L4_DPORT,
                                     ((UdpPortCriterion) criterion).udpPort().toInt(), PORTMASK)
                                     .build());
                     break;
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricInterpreter.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricInterpreter.java
index 2d5b280..a90353e 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricInterpreter.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricInterpreter.java
@@ -16,7 +16,6 @@
 
 package org.onosproject.pipelines.fabric;
 
-import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -28,7 +27,6 @@
 import org.onosproject.net.Port;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.device.DeviceService;
-import org.onosproject.net.driver.AbstractHandlerBehaviour;
 import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.flow.criteria.Criterion;
 import org.onosproject.net.flow.instructions.Instructions;
@@ -36,20 +34,12 @@
 import org.onosproject.net.packet.InboundPacket;
 import org.onosproject.net.packet.OutboundPacket;
 import org.onosproject.net.pi.model.PiMatchFieldId;
-import org.onosproject.net.pi.model.PiPipeconf;
-import org.onosproject.net.pi.model.PiPipeconfId;
 import org.onosproject.net.pi.model.PiPipelineInterpreter;
 import org.onosproject.net.pi.model.PiTableId;
 import org.onosproject.net.pi.runtime.PiAction;
 import org.onosproject.net.pi.runtime.PiControlMetadata;
 import org.onosproject.net.pi.runtime.PiPacketOperation;
-import org.onosproject.net.pi.service.PiPipeconfService;
-import org.slf4j.Logger;
 
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.nio.ByteBuffer;
 import java.util.Collection;
 import java.util.List;
@@ -63,96 +53,76 @@
 import static org.onosproject.net.PortNumber.FLOOD;
 import static org.onosproject.net.flow.instructions.Instruction.Type.OUTPUT;
 import static org.onosproject.net.pi.model.PiPacketOperationType.PACKET_OUT;
-import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.CPU_PORT_TXT;
-import static org.slf4j.LoggerFactory.getLogger;
 
 /**
  * Interpreter for fabric pipeline.
  */
-public class FabricInterpreter extends AbstractHandlerBehaviour
+public class FabricInterpreter extends AbstractFabricHandlerBehavior
         implements PiPipelineInterpreter {
 
-    private final Logger log = getLogger(getClass());
+    private static final int PORT_BITWIDTH = 9;
 
-    public static final int PORT_BITWIDTH = 9;
-
-    private static final ImmutableBiMap<Integer, PiTableId> TABLE_ID_MAP =
-            ImmutableBiMap.<Integer, PiTableId>builder()
-                    // Filtering
-                    .put(0, FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN)
-                    .put(1, FabricConstants.FABRIC_INGRESS_FILTERING_FWD_CLASSIFIER)
-                    // Forwarding
-                    .put(2, FabricConstants.FABRIC_INGRESS_FORWARDING_MPLS)
-                    .put(3, FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4)
-                    .put(4, FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V6)
-                    .put(5, FabricConstants.FABRIC_INGRESS_FORWARDING_BRIDGING)
-                    .put(6, FabricConstants.FABRIC_INGRESS_FORWARDING_ACL)
-                    // Next
-                    .put(7, FabricConstants.FABRIC_INGRESS_NEXT_VLAN_META)
-                    .put(8, FabricConstants.FABRIC_INGRESS_NEXT_SIMPLE)
-                    .put(9, FabricConstants.FABRIC_INGRESS_NEXT_HASHED)
-                    .put(10, FabricConstants.FABRIC_INGRESS_NEXT_MULTICAST)
-                    .put(11, FabricConstants.FABRIC_EGRESS_EGRESS_NEXT_EGRESS_VLAN)
-                    .build();
-
-    private static final Set<PiTableId> FILTERING_CTRL_TBLS =
-            ImmutableSet.of(FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN,
-                            FabricConstants.FABRIC_INGRESS_FILTERING_FWD_CLASSIFIER);
-    private static final Set<PiTableId> FORWARDING_CTRL_TBLS =
-            ImmutableSet.of(FabricConstants.FABRIC_INGRESS_FORWARDING_MPLS,
-                            FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4,
-                            FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V6,
-                            FabricConstants.FABRIC_INGRESS_FORWARDING_BRIDGING,
-                            FabricConstants.FABRIC_INGRESS_FORWARDING_ACL);
-    private static final Set<PiTableId> NEXT_CTRL_TBLS =
-            ImmutableSet.of(FabricConstants.FABRIC_INGRESS_NEXT_VLAN_META,
-                            FabricConstants.FABRIC_INGRESS_NEXT_SIMPLE,
-                            FabricConstants.FABRIC_INGRESS_NEXT_HASHED,
-                            FabricConstants.FABRIC_INGRESS_NEXT_MULTICAST);
-    private static final Set<PiTableId> E_NEXT_CTRL_TBLS =
-            ImmutableSet.of(FabricConstants.FABRIC_EGRESS_EGRESS_NEXT_EGRESS_VLAN);
+    // Group tables by control block.
+    private static final Set<PiTableId> FILTERING_CTRL_TBLS = ImmutableSet.of(
+            FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN,
+            FabricConstants.FABRIC_INGRESS_FILTERING_FWD_CLASSIFIER);
+    private static final Set<PiTableId> FORWARDING_CTRL_TBLS = ImmutableSet.of(
+            FabricConstants.FABRIC_INGRESS_FORWARDING_MPLS,
+            FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4,
+            FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V6,
+            FabricConstants.FABRIC_INGRESS_FORWARDING_BRIDGING);
+    private static final Set<PiTableId> ACL_CTRL_TBLS = ImmutableSet.of(
+            FabricConstants.FABRIC_INGRESS_ACL_ACL);
+    private static final Set<PiTableId> NEXT_CTRL_TBLS = ImmutableSet.of(
+            FabricConstants.FABRIC_INGRESS_NEXT_NEXT_VLAN,
+            FabricConstants.FABRIC_INGRESS_NEXT_SIMPLE,
+            FabricConstants.FABRIC_INGRESS_NEXT_HASHED);
+    private static final Set<PiTableId> E_NEXT_CTRL_TBLS = ImmutableSet.of(
+            FabricConstants.FABRIC_EGRESS_EGRESS_NEXT_EGRESS_VLAN);
 
     private static final ImmutableMap<Criterion.Type, PiMatchFieldId> CRITERION_MAP =
             ImmutableMap.<Criterion.Type, PiMatchFieldId>builder()
-                    .put(Criterion.Type.IN_PORT, FabricConstants.STANDARD_METADATA_INGRESS_PORT)
-                    .put(Criterion.Type.ETH_DST_MASKED, FabricConstants.HDR_ETHERNET_DST_ADDR)
-                    .put(Criterion.Type.ETH_SRC_MASKED, FabricConstants.HDR_ETHERNET_SRC_ADDR)
-                    .put(Criterion.Type.ETH_TYPE, FabricConstants.HDR_VLAN_TAG_ETHER_TYPE)
+                    .put(Criterion.Type.IN_PORT, FabricConstants.HDR_IG_PORT)
+                    .put(Criterion.Type.ETH_DST, FabricConstants.HDR_ETH_DST)
+                    .put(Criterion.Type.ETH_SRC, FabricConstants.HDR_ETH_SRC)
+                    .put(Criterion.Type.ETH_DST_MASKED, FabricConstants.HDR_ETH_DST)
+                    .put(Criterion.Type.ETH_SRC_MASKED, FabricConstants.HDR_ETH_SRC)
+                    .put(Criterion.Type.ETH_TYPE, FabricConstants.HDR_ETH_TYPE)
                     .put(Criterion.Type.MPLS_LABEL, FabricConstants.HDR_MPLS_LABEL)
-                    .put(Criterion.Type.VLAN_VID, FabricConstants.HDR_VLAN_TAG_VLAN_ID)
-                    .put(Criterion.Type.IPV4_DST, FabricConstants.HDR_IPV4_DST_ADDR)
-                    .put(Criterion.Type.IPV4_SRC, FabricConstants.HDR_IPV4_SRC_ADDR)
-                    .put(Criterion.Type.IPV6_DST, FabricConstants.HDR_IPV6_DST_ADDR)
-                    .put(Criterion.Type.IP_PROTO, FabricConstants.FABRIC_METADATA_IP_PROTO)
-                    .put(Criterion.Type.ICMPV6_TYPE, FabricConstants.HDR_ICMP_ICMP_TYPE)
-                    .put(Criterion.Type.ICMPV6_CODE, FabricConstants.HDR_ICMP_ICMP_CODE)
+                    .put(Criterion.Type.VLAN_VID, FabricConstants.HDR_VLAN_ID)
+                    .put(Criterion.Type.IPV4_DST, FabricConstants.HDR_IPV4_DST)
+                    .put(Criterion.Type.IPV4_SRC, FabricConstants.HDR_IPV4_SRC)
+                    .put(Criterion.Type.IPV6_DST, FabricConstants.HDR_IPV6_DST)
+                    .put(Criterion.Type.IP_PROTO, FabricConstants.HDR_IP_PROTO)
+                    .put(Criterion.Type.ICMPV6_TYPE, FabricConstants.HDR_ICMP_TYPE)
+                    .put(Criterion.Type.ICMPV6_CODE, FabricConstants.HDR_ICMP_CODE)
                     .build();
 
     private static final ImmutableMap<PiMatchFieldId, Criterion.Type> INVERSE_CRITERION_MAP =
             ImmutableMap.<PiMatchFieldId, Criterion.Type>builder()
-                    .put(FabricConstants.STANDARD_METADATA_INGRESS_PORT, Criterion.Type.IN_PORT)
-                    .put(FabricConstants.HDR_ETHERNET_DST_ADDR, Criterion.Type.ETH_DST_MASKED)
-                    .put(FabricConstants.HDR_ETHERNET_SRC_ADDR, Criterion.Type.ETH_SRC_MASKED)
-                    .put(FabricConstants.HDR_VLAN_TAG_ETHER_TYPE, Criterion.Type.ETH_TYPE)
+                    .put(FabricConstants.HDR_IG_PORT, Criterion.Type.IN_PORT)
+                    .put(FabricConstants.HDR_ETH_DST, Criterion.Type.ETH_DST_MASKED)
+                    .put(FabricConstants.HDR_ETH_SRC, Criterion.Type.ETH_SRC_MASKED)
+                    .put(FabricConstants.HDR_ETH_TYPE, Criterion.Type.ETH_TYPE)
                     .put(FabricConstants.HDR_MPLS_LABEL, Criterion.Type.MPLS_LABEL)
-                    .put(FabricConstants.HDR_VLAN_TAG_VLAN_ID, Criterion.Type.VLAN_VID)
-                    .put(FabricConstants.HDR_IPV4_DST_ADDR, Criterion.Type.IPV4_DST)
-                    .put(FabricConstants.HDR_IPV4_SRC_ADDR, Criterion.Type.IPV4_SRC)
-                    .put(FabricConstants.HDR_IPV6_DST_ADDR, Criterion.Type.IPV6_DST)
+                    .put(FabricConstants.HDR_VLAN_ID, Criterion.Type.VLAN_VID)
+                    .put(FabricConstants.HDR_IPV4_DST, Criterion.Type.IPV4_DST)
+                    .put(FabricConstants.HDR_IPV4_SRC, Criterion.Type.IPV4_SRC)
+                    .put(FabricConstants.HDR_IPV6_DST, Criterion.Type.IPV6_DST)
                     // FIXME: might be incorrect if we inverse the map....
-                    .put(FabricConstants.FABRIC_METADATA_L4_SRC_PORT, Criterion.Type.UDP_SRC)
-                    .put(FabricConstants.FABRIC_METADATA_L4_DST_PORT, Criterion.Type.UDP_DST)
-                    .put(FabricConstants.FABRIC_METADATA_IP_PROTO, Criterion.Type.IP_PROTO)
-                    .put(FabricConstants.HDR_ICMP_ICMP_TYPE, Criterion.Type.ICMPV6_TYPE)
-                    .put(FabricConstants.HDR_ICMP_ICMP_CODE, Criterion.Type.ICMPV6_CODE)
+                    .put(FabricConstants.HDR_L4_SPORT, Criterion.Type.UDP_SRC)
+                    .put(FabricConstants.HDR_L4_DPORT, Criterion.Type.UDP_DST)
+                    .put(FabricConstants.HDR_IP_PROTO, Criterion.Type.IP_PROTO)
+                    .put(FabricConstants.HDR_ICMP_TYPE, Criterion.Type.ICMPV6_TYPE)
+                    .put(FabricConstants.HDR_ICMP_CODE, Criterion.Type.ICMPV6_CODE)
                     .build();
 
-    private static final PiAction NOACTION = PiAction.builder().withId(
-            FabricConstants.NO_ACTION).build();
+    private static final PiAction NOP = PiAction.builder()
+            .withId(FabricConstants.NOP).build();
 
     private static final ImmutableMap<PiTableId, PiAction> DEFAULT_ACTIONS =
             ImmutableMap.<PiTableId, PiAction>builder()
-                    .put(FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4, NOACTION)
+                    .put(FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4, NOP)
                     .build();
 
     @Override
@@ -167,32 +137,39 @@
 
     @Override
     public Optional<PiTableId> mapFlowRuleTableId(int flowRuleTableId) {
-        return Optional.ofNullable(TABLE_ID_MAP.get(flowRuleTableId));
+        // The only use case for Index ID->PiTableId is when using the single
+        // table pipeliner. fabric.p4 is never used with such pipeliner.
+        return Optional.empty();
     }
 
     @Override
     public Optional<Integer> mapPiTableId(PiTableId piTableId) {
-        return Optional.ofNullable(TABLE_ID_MAP.inverse().get(piTableId));
+        // The only use case for Index ID->PiTableId is when using the single
+        // table pipeliner. fabric.p4 is never used with such pipeliner.
+        return Optional.empty();
     }
 
     @Override
     public PiAction mapTreatment(TrafficTreatment treatment, PiTableId piTableId)
             throws PiInterpreterException {
-
         if (FILTERING_CTRL_TBLS.contains(piTableId)) {
             return FabricTreatmentInterpreter.mapFilteringTreatment(treatment, piTableId);
         } else if (FORWARDING_CTRL_TBLS.contains(piTableId)) {
             return FabricTreatmentInterpreter.mapForwardingTreatment(treatment, piTableId);
+        } else if (ACL_CTRL_TBLS.contains(piTableId)) {
+            return FabricTreatmentInterpreter.mapAclTreatment(treatment, piTableId);
         } else if (NEXT_CTRL_TBLS.contains(piTableId)) {
             return FabricTreatmentInterpreter.mapNextTreatment(treatment, piTableId);
         } else if (E_NEXT_CTRL_TBLS.contains(piTableId)) {
             return FabricTreatmentInterpreter.mapEgressNextTreatment(treatment, piTableId);
         } else {
-            throw new PiInterpreterException(String.format("Table %s unsupported", piTableId));
+            throw new PiInterpreterException(format(
+                    "Treatment mapping not supported for table '%s'", piTableId));
         }
     }
 
-    private PiPacketOperation createPiPacketOperation(DeviceId deviceId, ByteBuffer data, long portNumber)
+    private PiPacketOperation createPiPacketOperation(
+            DeviceId deviceId, ByteBuffer data, long portNumber)
             throws PiInterpreterException {
         PiControlMetadata metadata = createPacketMetadata(portNumber);
         return PiPacketOperation.builder()
@@ -203,7 +180,8 @@
                 .build();
     }
 
-    private PiControlMetadata createPacketMetadata(long portNumber) throws PiInterpreterException {
+    private PiControlMetadata createPacketMetadata(long portNumber)
+            throws PiInterpreterException {
         try {
             return PiControlMetadata.builder()
                     .withId(FabricConstants.EGRESS_PORT)
@@ -211,7 +189,7 @@
                     .build();
         } catch (ImmutableByteSequence.ByteSequenceTrimException e) {
             throw new PiInterpreterException(format(
-                    "Port number %d too big, %s", portNumber, e.getMessage()));
+                    "Port number '%d' too big, %s", portNumber, e.getMessage()));
         }
     }
 
@@ -294,49 +272,6 @@
         if (!port.equals(CONTROLLER)) {
             return Optional.empty();
         }
-        // This is probably brittle, but needed to dynamically get the CPU port
-        // for different platforms.
-        final DeviceId deviceId = data().deviceId();
-        final PiPipeconfService pipeconfService = handler().get(
-                PiPipeconfService.class);
-        final PiPipeconfId pipeconfId = pipeconfService
-                .ofDevice(deviceId).orElse(null);
-        if (pipeconfId == null ||
-                !pipeconfService.getPipeconf(pipeconfId).isPresent()) {
-            log.error("Unable to get pipeconf of {} - BUG?");
-            return Optional.empty();
-        }
-        final PiPipeconf pipeconf = pipeconfService.getPipeconf(pipeconfId).get();
-        if (!pipeconf.extension(CPU_PORT_TXT).isPresent()) {
-            log.error("Missing {} extension from pipeconf {}",
-                      CPU_PORT_TXT, pipeconfId);
-            return Optional.empty();
-        }
-        return Optional.ofNullable(
-                readCpuPort(pipeconf.extension(CPU_PORT_TXT).get(),
-                            pipeconfId));
-    }
-
-    private Integer readCpuPort(InputStream stream, PiPipeconfId pipeconfId) {
-        try {
-            final BufferedReader buff = new BufferedReader(
-                    new InputStreamReader(stream));
-            final String str = buff.readLine();
-            buff.close();
-            if (str == null) {
-                log.error("Empty CPU port file for {}", pipeconfId);
-                return null;
-            }
-            try {
-                return Integer.parseInt(str);
-            } catch (NumberFormatException e) {
-                log.error("Invalid CPU port for {}: {}", pipeconfId, str);
-                return null;
-            }
-        } catch (IOException e) {
-            log.error("Unable to read CPU port file of {}: {}",
-                      pipeconfId, e.getMessage());
-            return null;
-        }
+        return capabilities.cpuPort();
     }
 }
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricTreatmentInterpreter.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricTreatmentInterpreter.java
index 818fb43..4248111 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricTreatmentInterpreter.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricTreatmentInterpreter.java
@@ -16,371 +16,241 @@
 
 package org.onosproject.pipelines.fabric;
 
-import com.google.common.collect.ImmutableList;
-import org.onlab.packet.MacAddress;
-import org.onlab.packet.MplsLabel;
-import org.onlab.packet.VlanId;
-import org.onlab.util.ImmutableByteSequence;
+import com.google.common.collect.ImmutableMap;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.flow.instructions.Instruction;
-import org.onosproject.net.flow.instructions.Instructions;
 import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
 import org.onosproject.net.flow.instructions.L2ModificationInstruction;
 import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction;
 import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsLabelInstruction;
 import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction;
-import org.onosproject.net.flow.instructions.L3ModificationInstruction;
 import org.onosproject.net.pi.model.PiActionId;
 import org.onosproject.net.pi.model.PiPipelineInterpreter.PiInterpreterException;
 import org.onosproject.net.pi.model.PiTableId;
 import org.onosproject.net.pi.runtime.PiAction;
 import org.onosproject.net.pi.runtime.PiActionParam;
-import org.slf4j.Logger;
-
-import java.util.List;
 
 import static java.lang.String.format;
-import static org.onosproject.net.flow.instructions.Instruction.Type.L2MODIFICATION;
+import static org.onosproject.net.flow.instructions.Instruction.Type.OUTPUT;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.ETH_DST;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.ETH_SRC;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.MPLS_LABEL;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.MPLS_PUSH;
 import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.VLAN_ID;
-import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.VLAN_PUSH;
-import static org.onosproject.pipelines.fabric.FabricUtils.getOutputPort;
-import static org.slf4j.LoggerFactory.getLogger;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.VLAN_POP;
+import static org.onosproject.pipelines.fabric.FabricUtils.instruction;
+import static org.onosproject.pipelines.fabric.FabricUtils.l2Instruction;
+import static org.onosproject.pipelines.fabric.FabricUtils.outputPort;
 
-
+/**
+ * Treatment translation logic.
+ */
 final class FabricTreatmentInterpreter {
-    private static final Logger log = getLogger(FabricTreatmentInterpreter.class);
-    private static final String INVALID_TREATMENT = "Invalid treatment for %s block [%s]";
-    private static final String INVALID_TREATMENT_WITH_EXP = "Invalid treatment for %s block: %s [%s]";
-    private static final PiAction NOP = PiAction.builder().withId(FabricConstants.NOP).build();
-    private static final PiAction NOP_INGRESS_PORT_VLAN = PiAction.builder()
-            .withId(FabricConstants.FABRIC_INGRESS_FILTERING_NOP_INGRESS_PORT_VLAN).build();
-    private static final PiAction NOP_ACL = PiAction.builder()
-            .withId(FabricConstants.FABRIC_INGRESS_FORWARDING_NOP_ACL).build();
-    private static final PiAction NOP_ROUTING_V4 = PiAction.builder()
-            .withId(FabricConstants.FABRIC_INGRESS_FORWARDING_NOP_ROUTING_V4).build();
 
-    private static final PiAction POP_VLAN = PiAction.builder()
-            .withId(FabricConstants.FABRIC_EGRESS_EGRESS_NEXT_POP_VLAN)
-            .build();
+    private static final ImmutableMap<PiTableId, PiActionId> NOP_ACTIONS =
+            ImmutableMap.<PiTableId, PiActionId>builder()
+                    .put(FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN,
+                         FabricConstants.FABRIC_INGRESS_FILTERING_PERMIT)
+                    .put(FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4,
+                         FabricConstants.FABRIC_INGRESS_FORWARDING_NOP_ROUTING_V4)
+                    .put(FabricConstants.FABRIC_INGRESS_ACL_ACL,
+                         FabricConstants.FABRIC_INGRESS_ACL_NOP_ACL)
+                    .put(FabricConstants.FABRIC_EGRESS_EGRESS_NEXT_EGRESS_VLAN,
+                         FabricConstants.FABRIC_EGRESS_EGRESS_NEXT_POP_VLAN)
+                    .build();
 
-    // Hide default constructor
-    protected FabricTreatmentInterpreter() {
+    private FabricTreatmentInterpreter() {
+        // Hide default constructor
     }
 
-    /*
-     * In Filtering block, we need to implement these actions:
-     * push_internal_vlan
-     * set_vlan
-     * nop
-     *
-     * Unsupported, using PiAction directly:
-     * set_forwarding_type
-     * drop
-     */
-
     static PiAction mapFilteringTreatment(TrafficTreatment treatment, PiTableId tableId)
             throws PiInterpreterException {
-        List<Instruction> instructions = treatment.allInstructions();
-        Instruction noActInst = Instructions.createNoAction();
-        if (instructions.isEmpty() || instructions.contains(noActInst)) {
-            // nop
-            return NOP_INGRESS_PORT_VLAN;
+
+        if (!tableId.equals(FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN)) {
+            // Mapping for other tables of the filtering block must be handled
+            // in the pipeliner.
+            tableException(tableId);
         }
 
-        L2ModificationInstruction.ModVlanHeaderInstruction pushVlanInst = null;
-        ModVlanIdInstruction setVlanInst = null;
-
-        for (Instruction inst : instructions) {
-            if (inst.type() == L2MODIFICATION) {
-                L2ModificationInstruction l2Inst = (L2ModificationInstruction) inst;
-
-                if (l2Inst.subtype() == VLAN_PUSH) {
-                    pushVlanInst = (L2ModificationInstruction.ModVlanHeaderInstruction) l2Inst;
-
-                } else if (l2Inst.subtype() == VLAN_ID) {
-                    setVlanInst = (ModVlanIdInstruction) l2Inst;
-                }
-            }
+        if (isNoAction(treatment)) {
+            // Permit action if table is ingress_port_vlan;
+            return nop(tableId);
         }
 
-        if (setVlanInst == null) {
-            throw new PiInterpreterException(format(INVALID_TREATMENT, "filtering", treatment));
-        }
-
-        VlanId vlanId = setVlanInst.vlanId();
-        PiActionParam param = new PiActionParam(FabricConstants.NEW_VLAN_ID,
-                                                ImmutableByteSequence.copyFrom(vlanId.toShort()));
-        PiActionId actionId;
-        if (pushVlanInst != null) {
-            // push_internal_vlan
-            actionId = FabricConstants.FABRIC_INGRESS_FILTERING_PUSH_INTERNAL_VLAN;
-        } else {
-            actionId = FabricConstants.FABRIC_INGRESS_FILTERING_SET_VLAN;
-        }
-
-        // set_vlan
+        final ModVlanIdInstruction setVlanInst = (ModVlanIdInstruction) l2InstructionOrFail(
+                treatment, VLAN_ID, tableId);
         return PiAction.builder()
-                .withId(actionId)
-                .withParameter(param)
+                .withId(FabricConstants.FABRIC_INGRESS_FILTERING_PERMIT_WITH_INTERNAL_VLAN)
+                .withParameter(new PiActionParam(
+                        FabricConstants.VLAN_ID, setVlanInst.vlanId().toShort()))
                 .build();
     }
 
-    /*
-     * In forwarding block, we need to implement these actions:
-     * send_to_controller
-     *
-     * Unsupported, using PiAction directly:
-     * set_next_id_bridging
-     * pop_mpls_and_next
-     * set_next_id_unicast_v4
-     * set_next_id_multicast_v4
-     * set_next_id_acl
-     * drop
-     * set_next_id_unicast_v6
-     * set_next_id_multicast_v6
-     */
 
-    public static PiAction mapForwardingTreatment(TrafficTreatment treatment, PiTableId tableId)
+    static PiAction mapForwardingTreatment(TrafficTreatment treatment, PiTableId tableId)
             throws PiInterpreterException {
-        // Empty treatment, generate table entry with no action
-        if (treatment.equals(DefaultTrafficTreatment.emptyTreatment()) ||
-                treatment.allInstructions().isEmpty()) {
-            if (tableId.equals(FabricConstants.FABRIC_INGRESS_FORWARDING_ACL)) {
-                return NOP_ACL;
-            } else if (tableId.equals(FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4)) {
-                return NOP_ROUTING_V4;
-            } else {
-                return NOP;
-            }
+        if (isNoAction(treatment)) {
+            return nop(tableId);
         }
-        PortNumber outPort = getOutputPort(treatment);
+        treatmentException(
+                tableId, treatment,
+                "supports mapping only for empty/no-action treatments");
+        return null;
+    }
+
+    static PiAction mapNextTreatment(TrafficTreatment treatment, PiTableId tableId)
+            throws PiInterpreterException {
+        if (tableId == FabricConstants.FABRIC_INGRESS_NEXT_NEXT_VLAN) {
+            return mapNextVlanTreatment(treatment, tableId);
+        } else if (tableId == FabricConstants.FABRIC_INGRESS_NEXT_HASHED) {
+            return mapNextHashedOrSimpleTreatment(treatment, tableId, false);
+        } else if (tableId == FabricConstants.FABRIC_INGRESS_NEXT_SIMPLE) {
+            return mapNextHashedOrSimpleTreatment(treatment, tableId, true);
+        }
+        throw new PiInterpreterException(format(
+                "Treatment mapping not supported for table '%s'", tableId));
+    }
+
+    private static PiAction mapNextVlanTreatment(TrafficTreatment treatment, PiTableId tableId)
+            throws PiInterpreterException {
+        final ModVlanIdInstruction modVlanIdInst = (ModVlanIdInstruction)
+                l2InstructionOrFail(treatment, VLAN_ID, tableId);
+        return PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_NEXT_SET_VLAN)
+                .withParameter(new PiActionParam(
+                        FabricConstants.VLAN_ID,
+                        modVlanIdInst.vlanId().toShort()))
+                .build();
+    }
+
+    private static PiAction mapNextHashedOrSimpleTreatment(
+            TrafficTreatment treatment, PiTableId tableId, boolean simple)
+            throws PiInterpreterException {
+        // Provide mapping for output_hashed, routing_hashed, and
+        // mpls_routing_hashed. multicast_hashed can only be invoked with
+        // PiAction, hence no mapping. outPort required for all actions. Presence
+        // of other instructions will determine which action to map to.
+        final PortNumber outPort = ((OutputInstruction) instructionOrFail(
+                treatment, OUTPUT, tableId)).port();
+        final ModEtherInstruction ethDst = (ModEtherInstruction) l2Instruction(
+                treatment, ETH_DST);
+        final ModEtherInstruction ethSrc = (ModEtherInstruction) l2Instruction(
+                treatment, ETH_SRC);
+        final Instruction mplsPush = l2Instruction(
+                treatment, MPLS_PUSH);
+        final ModMplsLabelInstruction mplsLabel = (ModMplsLabelInstruction) l2Instruction(
+                treatment, MPLS_LABEL);
+
+        final PiAction.Builder actionBuilder = PiAction.builder()
+                .withParameter(new PiActionParam(FabricConstants.PORT_NUM, outPort.toLong()));
+
+        if (ethDst != null && ethSrc != null) {
+            actionBuilder.withParameter(new PiActionParam(
+                    FabricConstants.SMAC, ethSrc.mac().toBytes()));
+            actionBuilder.withParameter(new PiActionParam(
+                    FabricConstants.DMAC, ethDst.mac().toBytes()));
+            if (mplsLabel != null) {
+                // mpls_routing_hashed
+                return actionBuilder
+                        .withParameter(new PiActionParam(FabricConstants.LABEL, mplsLabel.label().toInt()))
+                        .withId(simple ? FabricConstants.FABRIC_INGRESS_NEXT_MPLS_ROUTING_SIMPLE
+                                        : FabricConstants.FABRIC_INGRESS_NEXT_MPLS_ROUTING_HASHED)
+                        .build();
+            } else {
+                // routing_hashed
+                return actionBuilder
+                        .withId(simple ? FabricConstants.FABRIC_INGRESS_NEXT_ROUTING_SIMPLE
+                                        : FabricConstants.FABRIC_INGRESS_NEXT_ROUTING_HASHED)
+                        .build();
+            }
+        } else {
+            // output_hashed
+            return actionBuilder
+                    .withId(simple ? FabricConstants.FABRIC_INGRESS_NEXT_OUTPUT_SIMPLE
+                                    : FabricConstants.FABRIC_INGRESS_NEXT_OUTPUT_HASHED)
+                    .build();
+        }
+    }
+
+    static PiAction mapAclTreatment(TrafficTreatment treatment, PiTableId tableId)
+            throws PiInterpreterException {
+        if (isNoAction(treatment)) {
+            return nop(tableId);
+        }
+
+        final PortNumber outPort = outputPort(treatment);
         if (outPort == null
                 || !outPort.equals(PortNumber.CONTROLLER)
                 || treatment.allInstructions().size() > 1) {
-            throw new PiInterpreterException(
-                    format(INVALID_TREATMENT_WITH_EXP,
-                           "forwarding", "supports only punt/clone to CPU actions",
-                           treatment));
+            treatmentException(
+                    tableId, treatment,
+                    "supports only punt/clone to CPU actions");
         }
 
         final PiActionId actionId = treatment.clearedDeferred()
-                ? FabricConstants.FABRIC_INGRESS_FORWARDING_PUNT_TO_CPU
-                : FabricConstants.FABRIC_INGRESS_FORWARDING_CLONE_TO_CPU;
+                ? FabricConstants.FABRIC_INGRESS_ACL_PUNT_TO_CPU
+                : FabricConstants.FABRIC_INGRESS_ACL_CLONE_TO_CPU;
 
         return PiAction.builder()
                 .withId(actionId)
                 .build();
     }
 
-    /*
-     * In Next block, we need to implement these actions:
-     * set_vlan
-     * set_vlan_output
-     * output_simple
-     * output_hashed
-     * l3_routing_simple
-     * l3_routing_vlan
-     * l3_routing_hashed
-     * mpls_routing_v4_simple
-     * mpls_routing_v6_simple
-     * mpls_routing_v4_hashed
-     * mpls_routing_v6_hashed
-     *
-     * Unsupported, need to find a way to implement it
-     *
-     * set_mcast_group
-     *
-     */
 
-    public static PiAction mapNextTreatment(TrafficTreatment treatment, PiTableId tableId)
+    static PiAction mapEgressNextTreatment(
+            TrafficTreatment treatment, PiTableId tableId)
             throws PiInterpreterException {
-        // TODO: refactor this method
-        List<Instruction> insts = treatment.allInstructions();
-        OutputInstruction outInst = null;
-        ModEtherInstruction modEthDstInst = null;
-        ModEtherInstruction modEthSrcInst = null;
-        ModVlanIdInstruction modVlanIdInst = null;
-        ModMplsLabelInstruction modMplsInst = null;
+        l2InstructionOrFail(treatment, VLAN_POP, tableId);
+        return PiAction.builder()
+                .withId(FabricConstants.FABRIC_EGRESS_EGRESS_NEXT_POP_VLAN)
+                .build();
 
-        // TODO: add NextFunctionType (like ForwardingFunctionType)
-        for (Instruction inst : insts) {
-            switch (inst.type()) {
-                case L2MODIFICATION:
-                    L2ModificationInstruction l2Inst = (L2ModificationInstruction) inst;
-                    switch (l2Inst.subtype()) {
-                        case ETH_SRC:
-                            modEthSrcInst = (ModEtherInstruction) l2Inst;
-                            break;
-                        case ETH_DST:
-                            modEthDstInst = (ModEtherInstruction) l2Inst;
-                            break;
-                        case VLAN_ID:
-                            modVlanIdInst = (ModVlanIdInstruction) l2Inst;
-                            break;
-                        case MPLS_LABEL:
-                            modMplsInst = (ModMplsLabelInstruction) l2Inst;
-                            break;
-                        case VLAN_POP:
-                            // VLAN_POP will be handled by mapEgressNextTreatment()
-                            break;
-                        case MPLS_PUSH:
-                            // Ignore. fabric.p4 only needs MPLS_LABEL to push a label
-                            break;
-                        default:
-                            log.warn("Unsupported l2 instruction sub type {} [table={}, {}]",
-                                     l2Inst.subtype(), tableId, treatment);
-                            break;
-                    }
-                    break;
-                case L3MODIFICATION:
-                    L3ModificationInstruction l3Inst = (L3ModificationInstruction) inst;
-                    switch (l3Inst.subtype()) {
-                        case TTL_OUT:
-                            // Ignore TTL_OUT
-                            break;
-                        default:
-                            log.warn("Unsupported l3 instruction sub type {} [table={}, {}]",
-                                    l3Inst.subtype(), tableId, treatment);
-                            break;
-                    }
-                    break;
-                case OUTPUT:
-                    outInst = (OutputInstruction) inst;
-                    break;
-                default:
-                    log.warn("Unsupported instruction sub type {} [table={}, {}]",
-                             inst.type(), tableId, treatment);
-                    break;
-            }
-        }
-
-        if (tableId.equals(FabricConstants.FABRIC_INGRESS_NEXT_VLAN_META) &&
-                modVlanIdInst != null) {
-            // set_vlan
-            VlanId vlanId = modVlanIdInst.vlanId();
-            PiActionParam newVlanParam =
-                    new PiActionParam(FabricConstants.NEW_VLAN_ID,
-                                      ImmutableByteSequence.copyFrom(vlanId.toShort()));
-            return PiAction.builder()
-                    .withId(FabricConstants.FABRIC_INGRESS_NEXT_SET_VLAN)
-                    .withParameter(newVlanParam)
-                    .build();
-        }
-
-        if (outInst == null) {
-            throw new PiInterpreterException(format(INVALID_TREATMENT, "next", treatment));
-        }
-
-        short portNum = (short) outInst.port().toLong();
-        PiActionParam portNumParam = new PiActionParam(FabricConstants.PORT_NUM,
-                                                       ImmutableByteSequence.copyFrom(portNum));
-        if (modEthDstInst == null && modEthSrcInst == null) {
-            if (modVlanIdInst != null) {
-                VlanId vlanId = modVlanIdInst.vlanId();
-                PiActionParam vlanParam =
-                        new PiActionParam(FabricConstants.NEW_VLAN_ID,
-                                          ImmutableByteSequence.copyFrom(vlanId.toShort()));
-                // set_vlan_output (simple table)
-                return PiAction.builder()
-                        .withId(FabricConstants.FABRIC_INGRESS_NEXT_SET_VLAN_OUTPUT)
-                        .withParameters(ImmutableList.of(portNumParam, vlanParam))
-                        .build();
-            } else {
-                // output (simple or hashed table)
-                return PiAction.builder()
-                        .withId(FabricConstants.FABRIC_INGRESS_NEXT_OUTPUT_SIMPLE)
-                        .withParameter(portNumParam)
-                        .build();
-            }
-        }
-
-        if (modEthDstInst != null && modEthSrcInst != null) {
-            MacAddress srcMac = modEthSrcInst.mac();
-            MacAddress dstMac = modEthDstInst.mac();
-            PiActionParam srcMacParam = new PiActionParam(FabricConstants.SMAC,
-                                                          ImmutableByteSequence.copyFrom(srcMac.toBytes()));
-            PiActionParam dstMacParam = new PiActionParam(FabricConstants.DMAC,
-                                                          ImmutableByteSequence.copyFrom(dstMac.toBytes()));
-
-            if (modMplsInst != null) {
-                // MPLS routing
-                MplsLabel mplsLabel = modMplsInst.label();
-                try {
-                    ImmutableByteSequence mplsValue =
-                            ImmutableByteSequence.copyFrom(mplsLabel.toInt()).fit(20);
-                    PiActionParam mplsParam = new PiActionParam(FabricConstants.LABEL, mplsValue);
-
-                    PiActionId actionId;
-                    // FIXME: finds a way to determine v4 or v6
-                    if (tableId.equals(FabricConstants.FABRIC_INGRESS_NEXT_SIMPLE)) {
-                        actionId = FabricConstants.FABRIC_INGRESS_NEXT_MPLS_ROUTING_V4_SIMPLE;
-                    } else if (tableId.equals(FabricConstants.FABRIC_INGRESS_NEXT_HASHED)) {
-                        actionId = FabricConstants.FABRIC_INGRESS_NEXT_MPLS_ROUTING_V4_HASHED;
-                    } else {
-                        throw new PiInterpreterException(format(INVALID_TREATMENT, "next", treatment));
-                    }
-
-                    return PiAction.builder()
-                            .withId(actionId)
-                            .withParameters(ImmutableList.of(portNumParam,
-                                                             srcMacParam,
-                                                             dstMacParam,
-                                                             mplsParam))
-                            .build();
-                } catch (ImmutableByteSequence.ByteSequenceTrimException e) {
-                    // Basically this won't happened because we already limited
-                    // size of mpls value to 20 bits (0xFFFFF)
-                    throw new PiInterpreterException(format(INVALID_TREATMENT, "next", treatment));
-                }
-            }
-
-            if (modVlanIdInst != null) {
-                VlanId vlanId = modVlanIdInst.vlanId();
-                PiActionParam vlanParam =
-                        new PiActionParam(FabricConstants.NEW_VLAN_ID,
-                                          ImmutableByteSequence.copyFrom(vlanId.toShort()));
-                // L3 routing and set VLAN
-                return PiAction.builder()
-                        .withId(FabricConstants.FABRIC_INGRESS_NEXT_L3_ROUTING_VLAN)
-                        .withParameters(ImmutableList.of(srcMacParam, dstMacParam, portNumParam, vlanParam))
-                        .build();
-            } else {
-                PiActionId actionId;
-                if (tableId.equals(FabricConstants.FABRIC_INGRESS_NEXT_SIMPLE)) {
-                    actionId = FabricConstants.FABRIC_INGRESS_NEXT_L3_ROUTING_SIMPLE;
-                } else if (tableId.equals(FabricConstants.FABRIC_INGRESS_NEXT_HASHED)) {
-                    actionId = FabricConstants.FABRIC_INGRESS_NEXT_L3_ROUTING_HASHED;
-                } else {
-                    throw new PiInterpreterException(format(INVALID_TREATMENT, "next", treatment));
-                }
-
-                // L3 routing
-                return PiAction.builder()
-                        .withId(actionId)
-                        .withParameters(ImmutableList.of(portNumParam,
-                                                         srcMacParam,
-                                                         dstMacParam))
-                        .build();
-            }
-        }
-
-        throw new PiInterpreterException(format(INVALID_TREATMENT, "next", treatment));
     }
 
-    /*
-     * pop_vlan
-     */
-    public static PiAction mapEgressNextTreatment(TrafficTreatment treatment, PiTableId tableId) {
-        // Pop VLAN action for now, may add new action to this control block in the future.
-        return treatment.allInstructions()
-                .stream()
-                .filter(inst -> inst.type() == Instruction.Type.L2MODIFICATION)
-                .map(inst -> (L2ModificationInstruction) inst)
-                .filter(inst -> inst.subtype() == L2ModificationInstruction.L2SubType.VLAN_POP)
-                .findFirst()
-                .map(inst -> POP_VLAN)
-                .orElse(NOP);
+    private static PiAction nop(PiTableId tableId) throws PiInterpreterException {
+        if (!NOP_ACTIONS.containsKey(tableId)) {
+            throw new PiInterpreterException(format("table '%s' doe not specify a nop action", tableId));
+        }
+        return PiAction.builder().withId(NOP_ACTIONS.get(tableId)).build();
+    }
+
+    private static boolean isNoAction(TrafficTreatment treatment) {
+        return treatment.equals(DefaultTrafficTreatment.emptyTreatment()) ||
+                treatment.allInstructions().isEmpty();
+    }
+
+    private static Instruction l2InstructionOrFail(
+            TrafficTreatment treatment,
+            L2ModificationInstruction.L2SubType subType, PiTableId tableId)
+            throws PiInterpreterException {
+        final Instruction inst = l2Instruction(treatment, subType);
+        if (inst == null) {
+            treatmentException(tableId, treatment, format("missing %s instruction", subType));
+        }
+        return inst;
+    }
+
+    private static Instruction instructionOrFail(
+            TrafficTreatment treatment, Instruction.Type type, PiTableId tableId)
+            throws PiInterpreterException {
+        final Instruction inst = instruction(treatment, type);
+        if (inst == null) {
+            treatmentException(tableId, treatment, format("missing %s instruction", type));
+        }
+        return inst;
+    }
+
+    private static void tableException(PiTableId tableId)
+            throws PiInterpreterException {
+        throw new PiInterpreterException(format("Table '%s' not supported", tableId));
+    }
+
+    private static void treatmentException(
+            PiTableId tableId, TrafficTreatment treatment, String explanation)
+            throws PiInterpreterException {
+        throw new PiInterpreterException(format(
+                "Invalid treatment for table '%s', %s: %s", tableId, explanation, treatment));
     }
 }
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricUtils.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricUtils.java
index b644613..8e5caa1 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricUtils.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricUtils.java
@@ -17,14 +17,22 @@
 package org.onosproject.pipelines.fabric;
 
 import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criterion;
 import org.onosproject.net.flow.instructions.Instruction;
 import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.flow.instructions.L2ModificationInstruction;
+import org.onosproject.net.flowobjective.DefaultNextTreatment;
+import org.onosproject.net.flowobjective.NextTreatment;
 
-import java.util.Optional;
+import java.util.Collection;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.lang.String.format;
 
 /**
- * Utility class for fabric pipeliner.
+ * Utility class with methods common to fabric pipeconf operations.
  */
 public final class FabricUtils {
 
@@ -32,17 +40,57 @@
         // Hides constructor.
     }
 
-    public static Optional<Instructions.OutputInstruction> getOutputInstruction(TrafficTreatment treatment) {
-        return treatment.allInstructions()
-                .stream()
-                .filter(inst -> inst.type() == Instruction.Type.OUTPUT)
-                .map(inst -> (Instructions.OutputInstruction) inst)
-                .findFirst();
+    public static Criterion criterion(Collection<Criterion> criteria, Criterion.Type type) {
+        return criteria.stream()
+                .filter(c -> c.type().equals(type))
+                .findFirst().orElse(null);
     }
 
-    public static PortNumber getOutputPort(TrafficTreatment treatment) {
-        return getOutputInstruction(treatment)
-                .map(Instructions.OutputInstruction::port)
-                .orElse(null);
+    public static Criterion criterion(TrafficSelector selector, Criterion.Type type) {
+        return selector.getCriterion(type);
+    }
+
+    public static Criterion criterionNotNull(TrafficSelector selector, Criterion.Type type) {
+        return checkNotNull(criterion(selector, type),
+                            format("%s criterion cannot be null", type));
+    }
+
+    public static Criterion criterionNotNull(Collection<Criterion> criteria, Criterion.Type type) {
+        return checkNotNull(criterion(criteria, type),
+                            format("%s criterion cannot be null", type));
+    }
+
+    public static Instructions.OutputInstruction instruction(TrafficTreatment treatment, Instruction.Type type) {
+        return treatment.allInstructions()
+                .stream()
+                .filter(inst -> inst.type() == type)
+                .map(inst -> (Instructions.OutputInstruction) inst)
+                .findFirst().orElse(null);
+    }
+
+    public static L2ModificationInstruction l2Instruction(
+            TrafficTreatment treatment, L2ModificationInstruction.L2SubType subType) {
+        return treatment.allInstructions().stream()
+                .filter(i -> i.type().equals(Instruction.Type.L2MODIFICATION))
+                .map(i -> (L2ModificationInstruction) i)
+                .filter(i -> i.subtype().equals(subType))
+                .findFirst().orElse(null);
+    }
+
+    public static Instructions.OutputInstruction outputInstruction(TrafficTreatment treatment) {
+        return instruction(treatment, Instruction.Type.OUTPUT);
+    }
+
+    public static PortNumber outputPort(TrafficTreatment treatment) {
+        final Instructions.OutputInstruction inst = outputInstruction(treatment);
+        return inst == null ? null : inst.port();
+    }
+
+    public static PortNumber outputPort(NextTreatment treatment) {
+        if (treatment.type() == NextTreatment.Type.TREATMENT) {
+            final DefaultNextTreatment t = (DefaultNextTreatment) treatment;
+            return outputPort(t.treatment());
+        }
+        return null;
     }
 }
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 89653d8..b565c99 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
@@ -153,7 +153,7 @@
         }
         // Add IntProgrammable behaviour for INT-enabled profiles.
         if (profile.endsWith(INT_PROFILE_SUFFIX) || profile.endsWith(FULL_PROFILE_SUFFIX)) {
-            pipeconfBuilder.addBehaviour(IntProgrammable.class, IntProgrammableImpl.class);
+            pipeconfBuilder.addBehaviour(IntProgrammable.class, FabricIntProgrammable.class);
         }
         return pipeconfBuilder.build();
     }
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/package-info.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/package-info.java
index 1cd2d1f..2221ac3 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/package-info.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/package-info.java
@@ -15,6 +15,6 @@
  */
 
 /**
- * CORD underlay fabric pipeline.
+ * Trellis-compatible reference underlay pipeconf.
  */
 package org.onosproject.pipelines.fabric;
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/AbstractObjectiveTranslator.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/AbstractObjectiveTranslator.java
new file mode 100644
index 0000000..c1e2f4d
--- /dev/null
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/AbstractObjectiveTranslator.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2018-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.onosproject.net.DeviceId;
+import org.onosproject.net.flow.DefaultFlowRule;
+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.instructions.Instruction;
+import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.flowobjective.ObjectiveError;
+import org.onosproject.net.pi.model.PiPipelineInterpreter;
+import org.onosproject.net.pi.model.PiTableId;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.pipelines.fabric.FabricCapabilities;
+import org.onosproject.pipelines.fabric.FabricInterpreter;
+import org.slf4j.Logger;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.lang.String.format;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Abstract implementation of a pipeliner logic for the fabric pipeconf.
+ */
+abstract class AbstractObjectiveTranslator<T extends Objective> {
+
+    protected final Logger log = getLogger(this.getClass());
+
+    protected final FabricCapabilities capabilities;
+    protected final DeviceId deviceId;
+
+    private final PiPipelineInterpreter interpreter = new FabricInterpreter();
+
+    AbstractObjectiveTranslator(DeviceId deviceId, FabricCapabilities capabilities) {
+        this.deviceId = checkNotNull(deviceId);
+        this.capabilities = checkNotNull(capabilities);
+    }
+
+    public ObjectiveTranslation translate(T obj) {
+        try {
+            return doTranslate(obj);
+        } catch (FabricPipelinerException e) {
+            log.warn("Cannot translate {}: {} [{}]",
+                     obj.getClass().getSimpleName(), e.getMessage(), obj);
+            return ObjectiveTranslation.ofError(e.objectiveError());
+        }
+    }
+
+    public abstract ObjectiveTranslation doTranslate(T obj)
+            throws FabricPipelinerException;
+
+    public FlowRule flowRule(T obj, PiTableId tableId, TrafficSelector selector,
+                             TrafficTreatment treatment)
+            throws FabricPipelinerException {
+        return DefaultFlowRule.builder()
+                .withSelector(selector)
+                .withTreatment(mapTreatmentToPiIfNeeded(treatment, tableId))
+                .forTable(tableId)
+                .makePermanent()
+                .withPriority(obj.priority())
+                .forDevice(deviceId)
+                .fromApp(obj.appId())
+                .build();
+    }
+
+    TrafficTreatment mapTreatmentToPiIfNeeded(TrafficTreatment treatment, PiTableId tableId)
+            throws FabricPipelinerException {
+        if (isTreatmentPi(treatment)) {
+            return treatment;
+        }
+        final PiAction piAction;
+        try {
+            piAction = interpreter.mapTreatment(treatment, tableId);
+        } catch (PiPipelineInterpreter.PiInterpreterException ex) {
+            throw new FabricPipelinerException(
+                    format("Unable to map treatment for table '%s': %s",
+                           tableId, ex.getMessage()),
+                    ObjectiveError.UNSUPPORTED);
+        }
+        return DefaultTrafficTreatment.builder()
+                .piTableAction(piAction)
+                .build();
+    }
+
+    private boolean isTreatmentPi(TrafficTreatment treatment) {
+        return treatment.allInstructions().size() == 1
+                && treatment.allInstructions().get(0).type() == Instruction.Type.PROTOCOL_INDEPENDENT;
+    }
+}
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
deleted file mode 100644
index 7d6ad40..0000000
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricFilteringPipeliner.java
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
- * 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.Lists;
-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
-    static final byte FWD_BRIDGING = 0;
-    static final byte FWD_MPLS = 1;
-    static final byte FWD_IPV4_ROUTING = 2;
-    static final byte FWD_IPV6_ROUTING = 3;
-    private static final PiCriterion VLAN_VALID = PiCriterion.builder()
-            .matchExact(FabricConstants.HDR_VLAN_TAG_IS_VALID, new byte[]{1})
-            .build();
-    private static final PiCriterion VLAN_INVALID = PiCriterion.builder()
-            .matchExact(FabricConstants.HDR_VLAN_TAG_IS_VALID, 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);
-        EthCriterion ethDstMaskedCriterion = filterObjective.conditions().stream()
-                .filter(criterion -> criterion.type() == Criterion.Type.ETH_DST_MASKED)
-                .map(criterion -> (EthCriterion) criterion)
-                .findFirst()
-                .orElse(null);
-
-        FlowRule inPortVlanTableRule = createInPortVlanTable(inPortCriterion, vlanCriterion,
-                                                             filterObjective);
-        Collection<FlowRule> fwdClassifierRules = createFwdClassifierRules(inPortCriterion, ethDstCriterion,
-                                                                           ethDstMaskedCriterion, 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.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN)
-                .forDevice(deviceId)
-                .makePermanent()
-                .build();
-    }
-
-    private Collection<FlowRule> createFwdClassifierRules(PortCriterion inPortCriterion,
-                                                          EthCriterion ethDstCriterion,
-                                                          EthCriterion ethDstMaskedCriterion,
-                                                          FilteringObjective filterObjective) {
-        PortNumber port = inPortCriterion.port();
-
-        Collection<FlowRule> flowRules = Lists.newArrayList();
-        if (ethDstCriterion == null) {
-            if (ethDstMaskedCriterion == null) {
-                // Bridging table, do nothing
-                return flowRules;
-            }
-            // Masked fwd classifier rule
-            MacAddress dstMac = ethDstMaskedCriterion.mac();
-            MacAddress dstMacMask = ethDstMaskedCriterion.mask();
-            FlowRule flow = createMaskedFwdClassifierRule(port, dstMac, dstMacMask, filterObjective);
-            if (flow != null) {
-                flowRules.add(flow);
-            }
-            return flowRules;
-        }
-        MacAddress dstMac = ethDstCriterion.mac();
-        flowRules.addAll(createIpFwdClassifierRules(port, dstMac, filterObjective));
-        flowRules.add(createMplsFwdClassifierRule(port, dstMac, filterObjective));
-        return flowRules;
-    }
-
-    private FlowRule createMaskedFwdClassifierRule(PortNumber inPort, MacAddress dstMac, MacAddress dstMacMask,
-                                                   FilteringObjective filterObjective) {
-        TrafficTreatment treatment;
-        short ethType;
-        if (dstMac.equals(MacAddress.IPV4_MULTICAST) && dstMacMask.equals(MacAddress.IPV4_MULTICAST_MASK)) {
-            treatment = createFwdClassifierTreatment(FWD_IPV4_ROUTING);
-            ethType = Ethernet.TYPE_IPV4;
-        } else if (dstMac.equals(MacAddress.IPV6_MULTICAST) && dstMacMask.equals(MacAddress.IPV6_MULTICAST_MASK)) {
-            treatment = createFwdClassifierTreatment(FWD_IPV6_ROUTING);
-            ethType = Ethernet.TYPE_IPV6;
-        } else {
-            log.warn("Unsupported masked fwd classifier rule. mac={}. mask={}", dstMac, dstMacMask);
-            return null;
-        }
-        return createFwdClassifierRule(inPort, ethType, dstMac, dstMacMask, treatment, filterObjective);
-    }
-
-    private Collection<FlowRule> createIpFwdClassifierRules(PortNumber inPort,
-                                                            MacAddress dstMac,
-                                                            FilteringObjective filterObjective) {
-        Collection<FlowRule> flowRules = Lists.newArrayList();
-        TrafficTreatment treatment;
-        treatment = createFwdClassifierTreatment(FWD_IPV4_ROUTING);
-        flowRules.add(createFwdClassifierRule(inPort, Ethernet.TYPE_IPV4, dstMac, null, treatment, filterObjective));
-        treatment = createFwdClassifierTreatment(FWD_IPV6_ROUTING);
-        flowRules.add(createFwdClassifierRule(inPort, Ethernet.TYPE_IPV6, dstMac, null, 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, null, treatment, filterObjective);
-    }
-
-    private FlowRule createFwdClassifierRule(PortNumber inPort,
-                                             short ethType,
-                                             MacAddress dstMac,
-                                             MacAddress dstMacMask,
-                                             TrafficTreatment treatment,
-                                             FilteringObjective filterObjective) {
-        TrafficSelector.Builder selector = DefaultTrafficSelector.builder()
-                .matchInPort(inPort)
-                .matchEthType(ethType);
-        if (dstMacMask != null) {
-            selector.matchEthDstMasked(dstMac, dstMacMask);
-        } else {
-            selector.matchEthDstMasked(dstMac, MacAddress.EXACT_MASK);
-        }
-
-        return DefaultFlowRule.builder()
-                .withSelector(selector.build())
-                .withTreatment(treatment)
-                .fromApp(filterObjective.appId())
-                .withPriority(filterObjective.priority())
-                .forDevice(deviceId)
-                .makePermanent()
-                .forTable(FabricConstants.FABRIC_INGRESS_FILTERING_FWD_CLASSIFIER)
-                .build();
-    }
-
-    private TrafficTreatment createFwdClassifierTreatment(byte fwdType) {
-        PiActionParam param = new PiActionParam(FabricConstants.FWD_TYPE,
-                                                ImmutableByteSequence.copyFrom(fwdType));
-        PiAction action = PiAction.builder()
-                .withId(FabricConstants.FABRIC_INGRESS_FILTERING_SET_FORWARDING_TYPE)
-                .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
deleted file mode 100644
index a3d3926..0000000
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricForwardingPipeliner.java
+++ /dev/null
@@ -1,338 +0,0 @@
-/*
- * 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.IpPrefix;
-import org.onlab.packet.MacAddress;
-import org.onlab.packet.VlanId;
-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.IPCriterion;
-import org.onosproject.net.flow.criteria.MplsCriterion;
-import org.onosproject.net.flow.criteria.VlanIdCriterion;
-import org.onosproject.net.flowobjective.ForwardingObjective;
-import org.onosproject.net.flowobjective.ObjectiveError;
-import org.onosproject.net.pi.model.PiActionId;
-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) {
-        // TODO: Move IPv6 match to different ACL table
-
-        boolean unsupported = fwd.selector().criteria().stream()
-                .anyMatch(criterion -> criterion.type() == Criterion.Type.IPV6_DST);
-        unsupported |= fwd.selector().criteria().stream()
-                .anyMatch(criterion -> criterion.type() == Criterion.Type.IPV6_SRC);
-
-        if (unsupported) {
-            resultBuilder.setError(ObjectiveError.UNSUPPORTED);
-            return;
-        }
-
-        // program ACL table only
-        FlowRule flowRule = DefaultFlowRule.builder()
-                .withSelector(fwd.selector())
-                .withTreatment(fwd.treatment())
-                .forTable(FabricConstants.FABRIC_INGRESS_FORWARDING_ACL)
-                .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;
-        IPCriterion ipDstCriterion = null;
-        MplsCriterion mplsCriterion = null;
-
-        for (Criterion criterion : criteria) {
-            switch (criterion.type()) {
-                case ETH_DST:
-                    ethDstCriterion = (EthCriterion) criterion;
-                    break;
-                case VLAN_VID:
-                    vlanIdCriterion = (VlanIdCriterion) criterion;
-                    break;
-                case IPV4_DST:
-                    ipDstCriterion = (IPCriterion) criterion;
-                    break;
-                case MPLS_LABEL:
-                    mplsCriterion = (MplsCriterion) criterion;
-                    break;
-                case ETH_TYPE:
-                case MPLS_BOS:
-                    // do nothing
-                    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_ROUTING:
-                processIpv4RoutingRule(ipDstCriterion, fwd, resultBuilder);
-                break;
-            case MPLS:
-                processMplsRule(mplsCriterion, fwd, resultBuilder);
-                break;
-            case IPV6_ROUTING:
-            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");
-
-        VlanId vlanId = vlanIdCriterion.vlanId();
-        MacAddress ethDst = ethDstCriterion.mac();
-
-        TrafficSelector selector = DefaultTrafficSelector.builder()
-                .matchVlanId(vlanId)
-                .matchEthDstMasked(ethDst, MacAddress.EXACT_MASK)
-                .build();
-        TrafficTreatment treatment = fwd.treatment();
-        if (fwd.nextId() != null) {
-            treatment = buildSetNextIdTreatment(fwd.nextId(),
-                                                FabricConstants.FABRIC_INGRESS_FORWARDING_SET_NEXT_ID_BRIDGING);
-        }
-
-        FlowRule flowRule = DefaultFlowRule.builder()
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .fromApp(fwd.appId())
-                .withPriority(fwd.priority())
-                .makePermanent()
-                .forDevice(deviceId)
-                .forTable(FabricConstants.FABRIC_INGRESS_FORWARDING_BRIDGING)
-                .build();
-
-        resultBuilder.addFlowRule(flowRule);
-    }
-
-    private void processL2BroadcastRule(VlanIdCriterion vlanIdCriterion,
-                                        ForwardingObjective fwd,
-                                        PipelinerTranslationResult.Builder resultBuilder) {
-        checkNotNull(vlanIdCriterion, "VlanId criterion should not be null");
-
-        VlanId vlanId = vlanIdCriterion.vlanId();
-
-        TrafficSelector selector = DefaultTrafficSelector.builder()
-                .matchVlanId(vlanId)
-                .build();
-        TrafficTreatment treatment = fwd.treatment();
-        if (fwd.nextId() != null) {
-            treatment = buildSetNextIdTreatment(fwd.nextId(),
-                                                FabricConstants.FABRIC_INGRESS_FORWARDING_SET_NEXT_ID_BRIDGING);
-        }
-        FlowRule flowRule = DefaultFlowRule.builder()
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .fromApp(fwd.appId())
-                .withPriority(fwd.priority())
-                .makePermanent()
-                .forDevice(deviceId)
-                .forTable(FabricConstants.FABRIC_INGRESS_FORWARDING_BRIDGING)
-                .build();
-
-        resultBuilder.addFlowRule(flowRule);
-    }
-
-    private void processIpv4RoutingRule(IPCriterion ipDstCriterion, ForwardingObjective fwd,
-                                        PipelinerTranslationResult.Builder resultBuilder) {
-        checkNotNull(ipDstCriterion, "IP dst criterion should not be null");
-
-        if (ipDstCriterion.ip().prefixLength() == 0) {
-            setDefaultIpv4Route(fwd, resultBuilder);
-            return;
-        }
-
-        final TrafficSelector selector = DefaultTrafficSelector.builder()
-                    .matchIPDst(ipDstCriterion.ip())
-                    .build();
-
-        TrafficTreatment treatment = fwd.treatment();
-        if (fwd.nextId() != null) {
-            treatment = buildSetNextIdTreatment(
-                    fwd.nextId(),
-                    FabricConstants.FABRIC_INGRESS_FORWARDING_SET_NEXT_ID_ROUTING_V4);
-        }
-        FlowRule flowRule = DefaultFlowRule.builder()
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .fromApp(fwd.appId())
-                .withPriority(fwd.priority())
-                .makePermanent()
-                .forDevice(deviceId)
-                .forTable(FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4)
-                .build();
-
-        resultBuilder.addFlowRule(flowRule);
-    }
-
-    private void setDefaultIpv4Route(ForwardingObjective fwd,
-                                     PipelinerTranslationResult.Builder resultBuilder) {
-        final TrafficSelector selector1 = DefaultTrafficSelector.builder()
-                .matchIPDst(IpPrefix.valueOf("0.0.0.0/1")).build();
-        final TrafficSelector selector2 = DefaultTrafficSelector.builder()
-                .matchIPDst(IpPrefix.valueOf("128.0.0.0/1")).build();
-
-        TrafficTreatment treatment = fwd.treatment();
-        if (fwd.nextId() != null) {
-            treatment = buildSetNextIdTreatment(
-                    fwd.nextId(),
-                    FabricConstants.FABRIC_INGRESS_FORWARDING_SET_NEXT_ID_ROUTING_V4);
-        }
-
-        for (TrafficSelector selector : new TrafficSelector[]{selector1, selector2}) {
-            FlowRule rule = DefaultFlowRule.builder()
-                    .withSelector(selector)
-                    .withTreatment(treatment)
-                    .fromApp(fwd.appId())
-                    .withPriority(0)
-                    .makePermanent()
-                    .forDevice(deviceId)
-                    .forTable(FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4)
-                    .build();
-
-            resultBuilder.addFlowRule(rule);
-        }
-    }
-
-    private void processMplsRule(MplsCriterion mplsCriterion, ForwardingObjective fwd,
-                                 PipelinerTranslationResult.Builder resultBuilder) {
-        checkNotNull(mplsCriterion, "Mpls criterion should not be null");
-        TrafficTreatment treatment;
-
-        treatment = fwd.treatment();
-        if (fwd.nextId() != null) {
-            PiActionParam nextIdParam = new PiActionParam(FabricConstants.NEXT_ID, fwd.nextId());
-            PiAction nextIdAction = PiAction.builder()
-                    .withId(FabricConstants.FABRIC_INGRESS_FORWARDING_POP_MPLS_AND_NEXT)
-                    .withParameter(nextIdParam)
-                    .build();
-            treatment = DefaultTrafficTreatment.builder()
-                    .piTableAction(nextIdAction)
-                    .build();
-        }
-
-        TrafficSelector selector = DefaultTrafficSelector.builder()
-                .add(mplsCriterion)
-                .build();
-
-        FlowRule flowRule = DefaultFlowRule.builder()
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .fromApp(fwd.appId())
-                .withPriority(fwd.priority())
-                .makePermanent()
-                .forDevice(deviceId)
-                .forTable(FabricConstants.FABRIC_INGRESS_FORWARDING_MPLS)
-                .build();
-
-        resultBuilder.addFlowRule(flowRule);
-    }
-
-    /**
-     * Builds treatment with set_next_id action, returns empty treatment
-     * if next id is null.
-     *
-     * @param nextId the next id for action
-     * @return treatment with set_next_id action; empty treatment if next id is null
-     */
-    private static TrafficTreatment buildSetNextIdTreatment(Integer nextId, PiActionId actionId) {
-        PiActionParam nextIdParam = new PiActionParam(FabricConstants.NEXT_ID, nextId);
-        PiAction nextIdAction = PiAction.builder()
-                .withId(actionId)
-                .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
deleted file mode 100644
index 479b1d4..0000000
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipeliner.java
+++ /dev/null
@@ -1,386 +0,0 @@
-/*
- * 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.onlab.packet.VlanId;
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.PortNumber;
-import org.onosproject.net.driver.Driver;
-import org.onosproject.net.flow.DefaultFlowRule;
-import org.onosproject.net.flow.DefaultTrafficSelector;
-import org.onosproject.net.flow.DefaultTrafficTreatment;
-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.PiCriterion;
-import org.onosproject.net.flow.criteria.VlanIdCriterion;
-import org.onosproject.net.flow.instructions.Instruction;
-import org.onosproject.net.flow.instructions.L2ModificationInstruction;
-import org.onosproject.net.flowobjective.DefaultNextObjective;
-import org.onosproject.net.flowobjective.NextObjective;
-import org.onosproject.net.flowobjective.Objective;
-import org.onosproject.net.flowobjective.ObjectiveError;
-import org.onosproject.net.group.DefaultGroupBucket;
-import org.onosproject.net.group.DefaultGroupDescription;
-import org.onosproject.net.group.DefaultGroupKey;
-import org.onosproject.net.group.GroupBucket;
-import org.onosproject.net.group.GroupBuckets;
-import org.onosproject.net.group.GroupDescription;
-import org.onosproject.net.group.GroupKey;
-import org.onosproject.net.pi.runtime.PiAction;
-import org.onosproject.net.pi.runtime.PiActionGroupId;
-import org.onosproject.net.pi.runtime.PiActionParam;
-import org.onosproject.net.pi.runtime.PiGroupKey;
-import org.onosproject.pipelines.fabric.FabricConstants;
-import org.onosproject.pipelines.fabric.FabricUtils;
-import org.slf4j.Logger;
-
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
-import static org.onosproject.pipelines.fabric.FabricUtils.getOutputPort;
-import static org.slf4j.LoggerFactory.getLogger;
-
-/**
- * Handling next objective for fabric pipeliner.
- */
-public class FabricNextPipeliner {
-    private static final Logger log = getLogger(FabricNextPipeliner.class);
-    private static final String NO_HASHED_TABLE = "noHashedTable";
-
-    protected DeviceId deviceId;
-    protected Driver driver;
-
-    public FabricNextPipeliner(DeviceId deviceId, Driver driver) {
-        this.deviceId = deviceId;
-        this.driver = driver;
-    }
-
-    public PipelinerTranslationResult next(NextObjective nextObjective) {
-        PipelinerTranslationResult.Builder resultBuilder = PipelinerTranslationResult.builder();
-
-        if (nextObjective.op() != Objective.Operation.ADD_TO_EXISTING &&
-                nextObjective.op() != Objective.Operation.REMOVE_FROM_EXISTING) {
-            processNextVlanMeta(nextObjective, resultBuilder);
-        }
-
-        switch (nextObjective.type()) {
-            case SIMPLE:
-                processSimpleNext(nextObjective, resultBuilder);
-                break;
-            case HASHED:
-                processHashedNext(nextObjective, resultBuilder);
-                break;
-            case BROADCAST:
-                processBroadcastNext(nextObjective, resultBuilder);
-                break;
-            default:
-                log.warn("Unsupported next type {}", nextObjective);
-                resultBuilder.setError(ObjectiveError.UNSUPPORTED);
-                break;
-        }
-
-        return resultBuilder.build();
-    }
-
-    private void processNextVlanMeta(NextObjective next,
-                                     PipelinerTranslationResult.Builder resultBuilder) {
-        TrafficSelector meta = next.meta();
-        if (meta == null) {
-            // do nothing if there is no metadata in the next objective.
-            return;
-        }
-        VlanIdCriterion vlanIdCriterion =
-                (VlanIdCriterion) meta.getCriterion(Criterion.Type.VLAN_VID);
-
-        if (vlanIdCriterion == null) {
-            // do nothing if we can't find vlan from next objective metadata.
-            return;
-        }
-
-        VlanId vlanId = vlanIdCriterion.vlanId();
-        TrafficSelector selector = buildNextIdSelector(next.id());
-        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .setVlanId(vlanId)
-                .build();
-
-        resultBuilder.addFlowRule(DefaultFlowRule.builder()
-                                          .withSelector(selector)
-                                          .withTreatment(treatment)
-                                          .forTable(FabricConstants.FABRIC_INGRESS_NEXT_VLAN_META)
-                                          .makePermanent()
-                                          .withPriority(next.priority())
-                                          .forDevice(deviceId)
-                                          .fromApp(next.appId())
-                                          .build());
-    }
-
-    private void processSimpleNext(NextObjective next,
-                                   PipelinerTranslationResult.Builder resultBuilder) {
-
-        if (next.next().size() > 1) {
-            log.warn("Only one treatment in simple next objective");
-            resultBuilder.setError(ObjectiveError.BADPARAMS);
-            return;
-        }
-
-        TrafficSelector selector = buildNextIdSelector(next.id());
-        TrafficTreatment treatment = next.next().iterator().next();
-        PortNumber outputPort = getOutputPort(treatment);
-
-        if (outputPort == null) {
-            log.warn("At least one output instruction in simple next objective");
-            resultBuilder.setError(ObjectiveError.BADPARAMS);
-            return;
-        }
-
-        resultBuilder.addFlowRule(DefaultFlowRule.builder()
-                                          .withSelector(selector)
-                                          .withTreatment(treatment)
-                                          .forTable(FabricConstants.FABRIC_INGRESS_NEXT_SIMPLE)
-                                          .makePermanent()
-                                          .withPriority(next.priority())
-                                          .forDevice(deviceId)
-                                          .fromApp(next.appId())
-                                          .build());
-
-        if (includesPopVlanInst(treatment)) {
-            processVlanPopRule(outputPort, next, resultBuilder);
-        }
-    }
-
-    private boolean includesPopVlanInst(TrafficTreatment treatment) {
-        return treatment.allInstructions()
-                .stream()
-                .filter(inst -> inst.type() == Instruction.Type.L2MODIFICATION)
-                .map(inst -> (L2ModificationInstruction) inst)
-                .anyMatch(inst -> inst.subtype() == L2ModificationInstruction.L2SubType.VLAN_POP);
-    }
-
-    private void processVlanPopRule(PortNumber port, NextObjective next,
-                                    PipelinerTranslationResult.Builder resultBuilder) {
-        TrafficSelector meta = next.meta();
-        VlanIdCriterion vlanIdCriterion =
-                (VlanIdCriterion) meta.getCriterion(Criterion.Type.VLAN_VID);
-        VlanId vlanId = vlanIdCriterion.vlanId();
-
-        PiCriterion egressVlanTableMatch = PiCriterion.builder()
-                .matchExact(FabricConstants.STANDARD_METADATA_EGRESS_PORT,
-                            (short) port.toLong())
-                .build();
-        // Add VLAN pop rule to egress pipeline table
-        TrafficSelector selector = DefaultTrafficSelector.builder()
-                .matchPi(egressVlanTableMatch)
-                .matchVlanId(vlanId)
-                .build();
-        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .popVlan()
-                .build();
-        resultBuilder.addFlowRule(DefaultFlowRule.builder()
-                                          .withSelector(selector)
-                                          .withTreatment(treatment)
-                                          .forTable(FabricConstants.FABRIC_EGRESS_EGRESS_NEXT_EGRESS_VLAN)
-                                          .makePermanent()
-                                          .withPriority(next.priority())
-                                          .forDevice(deviceId)
-                                          .fromApp(next.appId())
-                                          .build());
-    }
-
-    private void processHashedNext(NextObjective next, PipelinerTranslationResult.Builder resultBuilder) {
-        boolean noHashedTable = Boolean.parseBoolean(driver.getProperty(NO_HASHED_TABLE));
-
-        if (noHashedTable) {
-            if (next.next().isEmpty()) {
-                return;
-            }
-            // use first action if not support hashed group
-            TrafficTreatment treatment = next.next().iterator().next();
-
-            NextObjective.Builder simpleNext = DefaultNextObjective.builder()
-                    .addTreatment(treatment)
-                    .withId(next.id())
-                    .fromApp(next.appId())
-                    .makePermanent()
-                    .withMeta(next.meta())
-                    .withPriority(next.priority())
-                    .withType(NextObjective.Type.SIMPLE);
-
-            if (next.context().isPresent()) {
-                processSimpleNext(simpleNext.add(next.context().get()), resultBuilder);
-            } else {
-                processSimpleNext(simpleNext.add(), resultBuilder);
-            }
-            return;
-        }
-
-        // create hash groups
-        int groupId = next.id();
-        List<GroupBucket> bucketList = next.next().stream()
-                .map(DefaultGroupBucket::createSelectGroupBucket)
-                .collect(Collectors.toList());
-
-        // Egress VLAN handling
-        next.next().forEach(treatment -> {
-            PortNumber outputPort = getOutputPort(treatment);
-            if (includesPopVlanInst(treatment) && outputPort != null) {
-                processVlanPopRule(outputPort, next, resultBuilder);
-            }
-        });
-
-        if (bucketList.size() != next.next().size()) {
-            // some action not converted
-            // set error
-            log.warn("Expected bucket size {}, got {}", next.next().size(), bucketList.size());
-            resultBuilder.setError(ObjectiveError.BADPARAMS);
-            return;
-        }
-
-        GroupBuckets buckets = new GroupBuckets(bucketList);
-        PiGroupKey groupKey = new PiGroupKey(FabricConstants.FABRIC_INGRESS_NEXT_HASHED,
-                                             FabricConstants.FABRIC_INGRESS_NEXT_ECMP_SELECTOR,
-                                             groupId);
-
-        resultBuilder.addGroup(new DefaultGroupDescription(deviceId,
-                                                           GroupDescription.Type.SELECT,
-                                                           buckets,
-                                                           groupKey,
-                                                           groupId,
-                                                           next.appId()));
-
-        // flow
-        // If operation is ADD_TO_EXIST or REMOVE_FROM_EXIST, means we modify
-        // group buckets only, no changes for flow rule
-        if (next.op() == Objective.Operation.ADD_TO_EXISTING ||
-                next.op() == Objective.Operation.REMOVE_FROM_EXISTING) {
-            return;
-        }
-        TrafficSelector selector = buildNextIdSelector(next.id());
-        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .piTableAction(PiActionGroupId.of(next.id()))
-                .build();
-
-        resultBuilder.addFlowRule(DefaultFlowRule.builder()
-                                          .withSelector(selector)
-                                          .withTreatment(treatment)
-                                          .forTable(FabricConstants.FABRIC_INGRESS_NEXT_HASHED)
-                                          .makePermanent()
-                                          .withPriority(next.priority())
-                                          .forDevice(deviceId)
-                                          .fromApp(next.appId())
-                                          .build());
-    }
-
-    private TrafficSelector buildNextIdSelector(int nextId) {
-        PiCriterion nextIdCriterion = PiCriterion.builder()
-                .matchExact(FabricConstants.FABRIC_METADATA_NEXT_ID, nextId)
-                .build();
-        return DefaultTrafficSelector.builder()
-                .matchPi(nextIdCriterion)
-                .build();
-    }
-
-    private void processBroadcastNext(NextObjective next, PipelinerTranslationResult.Builder resultBuilder) {
-        final GroupDescription allGroup = getAllGroup(next);
-        if (allGroup == null) {
-            // Error already logged.
-            resultBuilder.setError(ObjectiveError.BADPARAMS);
-            return;
-        }
-
-        resultBuilder.addGroup(allGroup);
-        //flow rule
-        final TrafficSelector selector = buildNextIdSelector(next.id());
-        final PiActionParam groupIdParam = new PiActionParam(
-                FabricConstants.GID, allGroup.givenGroupId());
-
-        final PiAction setMcGroupAction = PiAction.builder()
-                .withId(FabricConstants.FABRIC_INGRESS_NEXT_SET_MCAST_GROUP)
-                .withParameter(groupIdParam)
-                .build();
-        final TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .piTableAction(setMcGroupAction)
-                .build();
-
-        resultBuilder.addFlowRule(
-                DefaultFlowRule.builder()
-                        .withSelector(selector)
-                        .withTreatment(treatment)
-                        .forTable(FabricConstants.FABRIC_INGRESS_NEXT_MULTICAST)
-                        .makePermanent()
-                        .withPriority(next.priority())
-                        .forDevice(deviceId)
-                        .fromApp(next.appId())
-                        .build());
-
-        // Egress VLAN handling
-        next.next().forEach(t -> {
-            PortNumber outputPort = getOutputPort(t);
-            if (includesPopVlanInst(t) && outputPort != null) {
-                processVlanPopRule(outputPort, next, resultBuilder);
-            }
-            if (t.allInstructions().size() > 2) {
-                // More than OUTPUT and VLAN_POP...
-                log.warn("Some instructions of BROADCAST NextObjective might" +
-                                 "not have been applied, supported only " +
-                                 "OUTPUT and VLAN_POP, but found {}", t);
-            }
-        });
-    }
-
-    private GroupDescription getAllGroup(NextObjective next) {
-        final List<GroupBucket> bucketList = next.next().stream()
-                .map(FabricUtils::getOutputInstruction)
-                .filter(Optional::isPresent)
-                .map(Optional::get)
-                .map(i -> DefaultTrafficTreatment.builder().add(i).build())
-                .map(DefaultGroupBucket::createAllGroupBucket)
-                .collect(Collectors.toList());
-
-        if (bucketList.size() != next.next().size()) {
-            log.warn("Got BROADCAST NextObjective with {} treatments but " +
-                             "found only {} OUTPUT instructions, cannot " +
-                             "translate to ALL groups",
-                     next.next().size(), bucketList.size());
-            return null;
-        }
-
-        // FIXME: remove once support for clone sessions is available
-        // Right now we add a CPU port to all multicast groups. The egress
-        // pipeline is expected to drop replicated packets to the CPU if a clone
-        // was  not requested in the ingress pipeline.
-        bucketList.add(
-                DefaultGroupBucket.createAllGroupBucket(
-                        DefaultTrafficTreatment.builder()
-                                .setOutput(PortNumber.CONTROLLER)
-                                .build()));
-
-        final int groupId = next.id();
-        final GroupBuckets buckets = new GroupBuckets(bucketList);
-        // Used DefaultGroupKey instead of PiGroupKey
-        // as we don't have any action profile to apply to the groups of ALL type
-        final GroupKey groupKey = new DefaultGroupKey(FabricPipeliner.KRYO.serialize(groupId));
-
-        return new DefaultGroupDescription(deviceId,
-                                           GroupDescription.Type.ALL,
-                                           buckets,
-                                           groupKey,
-                                           groupId,
-                                           next.appId());
-    }
-}
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
index 7b3006d..4bf1bb5 100644
--- 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
@@ -16,56 +16,50 @@
 
 package org.onosproject.pipelines.fabric.pipeliner;
 
-import com.google.common.base.MoreObjects;
-import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import org.onlab.util.KryoNamespace;
-import org.onlab.util.Tools;
-import org.onosproject.core.GroupId;
+import org.onlab.util.SharedExecutors;
 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.driver.Driver;
-import org.onosproject.net.flow.FlowId;
 import org.onosproject.net.flow.FlowRule;
 import org.onosproject.net.flow.FlowRuleOperations;
 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.IdNextTreatment;
 import org.onosproject.net.flowobjective.NextObjective;
+import org.onosproject.net.flowobjective.NextTreatment;
 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.GroupService;
+import org.onosproject.pipelines.fabric.AbstractFabricHandlerBehavior;
 import org.onosproject.store.serializers.KryoNamespaces;
 import org.slf4j.Logger;
 
 import java.util.Collection;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
+import static java.lang.String.format;
+import static org.onosproject.pipelines.fabric.FabricUtils.outputPort;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
- * Pipeliner for fabric pipeline.
+ * Pipeliner implementation for fabric pipeline which uses ObjectiveTranslator
+ * implementations to translate flow objectives for the different blocks,
+ * filtering, forwarding and next.
  */
-public class FabricPipeliner  extends AbstractHandlerBehaviour implements Pipeliner {
+public class FabricPipeliner extends AbstractFabricHandlerBehavior
+        implements Pipeliner {
+
     private static final Logger log = getLogger(FabricPipeliner.class);
 
     protected static final KryoNamespace KRYO = new KryoNamespace.Builder()
@@ -73,270 +67,213 @@
             .register(FabricNextGroup.class)
             .build("FabricPipeliner");
 
-    private static final int NUM_CALLBACK_THREAD = 2;
-
     protected DeviceId deviceId;
     protected FlowRuleService flowRuleService;
     protected GroupService groupService;
     protected FlowObjectiveStore flowObjectiveStore;
-    FabricFilteringPipeliner pipelinerFilter;
-    FabricForwardingPipeliner pipelinerForward;
-    FabricNextPipeliner pipelinerNext;
 
-    private Map<PendingFlowKey, PendingInstallObjective> pendingInstallObjectiveFlows = new ConcurrentHashMap<>();
-    private Map<PendingGroupKey, PendingInstallObjective> pendingInstallObjectiveGroups = new ConcurrentHashMap<>();
-    private Map<Objective, PendingInstallObjective> pendingInstallObjectives = Maps.newConcurrentMap();
+    private FilteringObjectiveTranslator filteringTranslator;
+    private ForwardingObjectiveTranslator forwardingTranslator;
+    private NextObjectiveTranslator nextTranslator;
 
-    private static ExecutorService flowObjCallbackExecutor =
-            Executors.newFixedThreadPool(NUM_CALLBACK_THREAD, Tools.groupedThreads("fabric-pipeliner", "cb-", log));
-
+    private final ExecutorService callbackExecutor = SharedExecutors.getPoolThreadExecutor();
 
     @Override
     public void init(DeviceId deviceId, PipelinerContext context) {
-        Driver driver = handler().driver();
         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, driver);
+        this.filteringTranslator = new FilteringObjectiveTranslator(deviceId, capabilities);
+        this.forwardingTranslator = new ForwardingObjectiveTranslator(deviceId, capabilities);
+        this.nextTranslator = new NextObjectiveTranslator(deviceId, capabilities);
     }
 
     @Override
-    public void filter(FilteringObjective filterObjective) {
-        PipelinerTranslationResult result = pipelinerFilter.filter(filterObjective);
-        if (result.error().isPresent()) {
-            fail(filterObjective, result.error().get());
-            return;
-        }
-
-        applyTranslationResult(filterObjective, result, error -> {
-            if (error == null) {
-                success(filterObjective);
-            } else {
-                log.info("Ignore error {}. Let flow subsystem retry", error);
-                success(filterObjective);
-            }
-        });
+    public void filter(FilteringObjective obj) {
+        final ObjectiveTranslation result = filteringTranslator.translate(obj);
+        handleResult(obj, result);
     }
 
     @Override
-    public void forward(ForwardingObjective forwardObjective) {
-        PipelinerTranslationResult result = pipelinerForward.forward(forwardObjective);
-        if (result.error().isPresent()) {
-            fail(forwardObjective, result.error().get());
-            return;
-        }
-
-        applyTranslationResult(forwardObjective, result, error -> {
-            if (error == null) {
-                success(forwardObjective);
-            } else {
-                log.info("Ignore error {}. Let flow subsystem retry", error);
-                success(forwardObjective);
-            }
-        });
+    public void forward(ForwardingObjective obj) {
+        final ObjectiveTranslation result = forwardingTranslator.translate(obj);
+        handleResult(obj, result);
     }
 
     @Override
-    public void next(NextObjective nextObjective) {
-        PipelinerTranslationResult result = pipelinerNext.next(nextObjective);
-
-        if (result.error().isPresent()) {
-            fail(nextObjective, result.error().get());
-            return;
-        }
-
-        if (nextObjective.op() == Objective.Operation.VERIFY) {
+    public void next(NextObjective obj) {
+        if (obj.op() == Objective.Operation.VERIFY) {
             // TODO: support VERIFY operation
-            log.debug("Currently we don't support VERIFY operation, return success directly to the context");
-            success(nextObjective);
+            log.debug("VERIFY operation not yet supported for NextObjective, will return success");
+            success(obj);
             return;
         }
 
-        if (nextObjective.op() == Objective.Operation.MODIFY) {
+        if (obj.op() == Objective.Operation.MODIFY) {
             // TODO: support MODIFY operation
-            log.debug("Currently we don't support MODIFY operation, return failure directly to the context");
-            fail(nextObjective, ObjectiveError.UNSUPPORTED);
+            log.warn("MODIFY operation not yet supported for NextObjective, will return failure :(");
+            fail(obj, ObjectiveError.UNSUPPORTED);
             return;
         }
 
-        applyTranslationResult(nextObjective, result, error -> {
-            if (error != null) {
-                log.info("Ignore error {}. Let flow/group subsystem retry", error);
-                success(nextObjective);
-                return;
-            }
-
-            if (nextObjective.op() == Objective.Operation.REMOVE) {
-                if (flowObjectiveStore.getNextGroup(nextObjective.id()) == null) {
-                    log.warn("Can not find next obj {} from store", nextObjective.id());
-                    return;
-                }
-                flowObjectiveStore.removeNextGroup(nextObjective.id());
-            } else {
-                // Success, put next group to objective store
-                List<PortNumber> portNumbers = Lists.newArrayList();
-                nextObjective.next().forEach(treatment ->
-                        treatment.allInstructions()
-                                .stream()
-                                .filter(inst -> inst.type() == Instruction.Type.OUTPUT)
-                                .map(inst -> (Instructions.OutputInstruction) inst)
-                                .findFirst()
-                                .map(Instructions.OutputInstruction::port)
-                                .ifPresent(portNumbers::add)
-                );
-                FabricNextGroup nextGroup = new FabricNextGroup(nextObjective.type(),
-                                                                portNumbers);
-                flowObjectiveStore.putNextGroup(nextObjective.id(), nextGroup);
-            }
-
-            success(nextObjective);
-        });
+        final ObjectiveTranslation result = nextTranslator.translate(obj);
+        handleResult(obj, result);
     }
 
     @Override
     public List<String> getNextMappings(NextGroup nextGroup) {
-        FabricNextGroup fabricNextGroup = KRYO.deserialize(nextGroup.data());
-        NextObjective.Type type = fabricNextGroup.type();
-        Collection<PortNumber> outputPorts = fabricNextGroup.outputPorts();
-
-        return outputPorts.stream()
-                .map(port -> String.format("%s -> %s", type, port))
+        final FabricNextGroup fabricNextGroup = KRYO.deserialize(nextGroup.data());
+        return fabricNextGroup.nextMappings().stream()
+                .map(m -> format("%s -> %s", fabricNextGroup.type(), m))
                 .collect(Collectors.toList());
     }
 
-    private void applyTranslationResult(Objective objective,
-                                        PipelinerTranslationResult result,
-                                        Consumer<ObjectiveError> callback) {
-        Collection<GroupDescription> groups = result.groups();
-        Collection<FlowRule> flowRules = result.flowRules();
-
-        Set<FlowId> flowIds = flowRules.stream().map(FlowRule::id).collect(Collectors.toSet());
-        Set<PendingGroupKey> pendingGroupKeys = groups.stream().map(GroupDescription::givenGroupId)
-                .map(GroupId::new)
-                .map(gid -> new PendingGroupKey(gid, objective.op()))
-                .collect(Collectors.toSet());
-
-        PendingInstallObjective pio =
-                new PendingInstallObjective(objective, flowIds, pendingGroupKeys, callback);
-
-        flowIds.forEach(flowId -> {
-            PendingFlowKey pfk = new PendingFlowKey(flowId, objective.id());
-            pendingInstallObjectiveFlows.put(pfk, pio);
-        });
-
-        pendingGroupKeys.forEach(pendingGroupKey ->
-            pendingInstallObjectiveGroups.put(pendingGroupKey, pio)
-        );
-
-        pendingInstallObjectives.put(objective, pio);
-        installGroups(objective, groups);
-        installFlows(objective, flowRules);
+    private void handleResult(Objective obj, ObjectiveTranslation result) {
+        if (result.error().isPresent()) {
+            fail(obj, result.error().get());
+            return;
+        }
+        processGroups(obj, result.groups());
+        processFlows(obj, result.flowRules());
+        if (obj instanceof NextObjective) {
+            handleNextGroup((NextObjective) obj);
+        }
+        success(obj);
     }
 
-    private void installFlows(Objective objective, Collection<FlowRule> flowRules) {
+    private void handleNextGroup(NextObjective obj) {
+        switch (obj.op()) {
+            case REMOVE:
+                removeNextGroup(obj);
+                break;
+            case ADD:
+            case ADD_TO_EXISTING:
+            case REMOVE_FROM_EXISTING:
+            case MODIFY:
+                putNextGroup(obj);
+                break;
+            case VERIFY:
+                break;
+            default:
+                log.error("Unknown NextObjective operation '{}'", obj.op());
+        }
+    }
+
+    private void processFlows(Objective objective, Collection<FlowRule> flowRules) {
         if (flowRules.isEmpty()) {
             return;
         }
-
-        FlowRuleOperations ops = buildFlowRuleOps(objective, flowRules);
-        if (ops == null) {
-            return;
+        final FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
+        switch (objective.op()) {
+            case ADD:
+            case ADD_TO_EXISTING:
+                flowRules.forEach(ops::add);
+                break;
+            case REMOVE:
+            case REMOVE_FROM_EXISTING:
+                flowRules.forEach(ops::remove);
+                break;
+            default:
+                log.warn("Unsupported Objective operation '{}'", objective.op());
+                return;
         }
-        flowRuleService.apply(ops);
-
-        flowRules.forEach(flow -> {
-            PendingFlowKey pfk = new PendingFlowKey(flow.id(), objective.id());
-            PendingInstallObjective pio = pendingInstallObjectiveFlows.remove(pfk);
-
-            if (pio != null) {
-                pio.flowInstalled(flow.id());
-            }
-        });
+        flowRuleService.apply(ops.build());
     }
 
-    private void installGroups(Objective objective, Collection<GroupDescription> groups) {
+    private void processGroups(Objective objective, Collection<GroupDescription> groups) {
         if (groups.isEmpty()) {
             return;
         }
-
         switch (objective.op()) {
             case ADD:
                 groups.forEach(groupService::addGroup);
                 break;
             case REMOVE:
-                groups.forEach(group -> groupService.removeGroup(deviceId, group.appCookie(), objective.appId()));
+                groups.forEach(group -> groupService.removeGroup(
+                        deviceId, group.appCookie(), objective.appId()));
                 break;
             case ADD_TO_EXISTING:
-                groups.forEach(group -> groupService.addBucketsToGroup(deviceId, group.appCookie(),
-                        group.buckets(), group.appCookie(), group.appId())
+                groups.forEach(group -> groupService.addBucketsToGroup(
+                        deviceId, group.appCookie(), group.buckets(),
+                        group.appCookie(), group.appId())
                 );
                 break;
             case REMOVE_FROM_EXISTING:
-                groups.forEach(group -> groupService.removeBucketsFromGroup(deviceId, group.appCookie(),
-                        group.buckets(), group.appCookie(), group.appId())
+                groups.forEach(group -> groupService.removeBucketsFromGroup(
+                        deviceId, group.appCookie(), group.buckets(),
+                        group.appCookie(), group.appId())
                 );
                 break;
             default:
-                log.warn("Unsupported objective operation {}", objective.op());
-                return;
+                log.warn("Unsupported Objective operation {}", objective.op());
         }
+    }
 
-        groups.forEach(group -> {
-            PendingGroupKey pendingGroupKey = new PendingGroupKey(new GroupId(group.givenGroupId()), objective.op());
-            PendingInstallObjective pio = pendingInstallObjectiveGroups.remove(pendingGroupKey);
-            pio.groupInstalled(pendingGroupKey);
-        });
+    private void fail(Objective objective, ObjectiveError error) {
+        CompletableFuture.runAsync(
+                () -> objective.context().ifPresent(
+                        ctx -> ctx.onError(objective, error)), callbackExecutor);
 
     }
 
-    private static void fail(Objective objective, ObjectiveError error) {
-        CompletableFuture.runAsync(() -> objective.context().ifPresent(ctx -> ctx.onError(objective, error)),
-                flowObjCallbackExecutor);
 
+    private void success(Objective objective) {
+        CompletableFuture.runAsync(
+                () -> objective.context().ifPresent(
+                        ctx -> ctx.onSuccess(objective)), callbackExecutor);
     }
 
-    private static void success(Objective objective) {
-        CompletableFuture.runAsync(() -> objective.context().ifPresent(ctx -> ctx.onSuccess(objective)),
-                flowObjCallbackExecutor);
+    private void removeNextGroup(NextObjective obj) {
+        final NextGroup removed = flowObjectiveStore.removeNextGroup(obj.id());
+        if (removed == null) {
+            log.debug("NextGroup {} was not found in FlowObjectiveStore");
+        }
     }
 
-    private static FlowRuleOperations buildFlowRuleOps(Objective objective, Collection<FlowRule> flowRules) {
-        FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
-        switch (objective.op()) {
-            case ADD:
-            case ADD_TO_EXISTING: // For egress VLAN
-                flowRules.forEach(ops::add);
-                break;
-            case REMOVE:
-            case REMOVE_FROM_EXISTING: // For egress VLAN
-                flowRules.forEach(ops::remove);
-                break;
+    private void putNextGroup(NextObjective obj) {
+        final List<String> nextMappings = obj.nextTreatments().stream()
+                .map(this::nextTreatmentToMappingString)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+        final FabricNextGroup nextGroup = new FabricNextGroup(obj.type(), nextMappings);
+        flowObjectiveStore.putNextGroup(obj.id(), nextGroup);
+    }
+
+    private String nextTreatmentToMappingString(NextTreatment n) {
+        switch (n.type()) {
+            case TREATMENT:
+                final PortNumber p = outputPort(n);
+                return p == null ? "UNKNOWN"
+                        : format("OUTPUT:%s", p.toString());
+            case ID:
+                final IdNextTreatment id = (IdNextTreatment) n;
+                return format("NEXT_ID:%d", id.nextId());
             default:
-                log.warn("Unsupported op {} for {}", objective.op(), objective);
-                fail(objective, ObjectiveError.BADPARAMS);
-                return null;
+                log.warn("Unknown NextTreatment type '{}'", n.type());
+                return "???";
         }
-        return ops.build();
     }
 
-    class FabricNextGroup implements NextGroup {
-        private NextObjective.Type type;
-        private Collection<PortNumber> outputPorts;
+    /**
+     * NextGroup implementation.
+     */
+    private static class FabricNextGroup implements NextGroup {
 
-        FabricNextGroup(NextObjective.Type type, Collection<PortNumber> outputPorts) {
+        private final NextObjective.Type type;
+        private final List<String> nextMappings;
+
+        FabricNextGroup(NextObjective.Type type, List<String> nextMappings) {
             this.type = type;
-            this.outputPorts = ImmutableList.copyOf(outputPorts);
+            this.nextMappings = ImmutableList.copyOf(nextMappings);
         }
 
         NextObjective.Type type() {
             return type;
         }
 
-        Collection<PortNumber> outputPorts() {
-            return outputPorts;
+        Collection<String> nextMappings() {
+            return nextMappings;
         }
 
         @Override
@@ -344,168 +281,4 @@
             return KRYO.serialize(this);
         }
     }
-
-    class PendingInstallObjective {
-        Objective objective;
-        Collection<FlowId> flowIds;
-        Collection<PendingGroupKey> pendingGroupKeys;
-        Consumer<ObjectiveError> callback;
-
-        PendingInstallObjective(Objective objective, Collection<FlowId> flowIds,
-                                       Collection<PendingGroupKey> pendingGroupKeys,
-                                       Consumer<ObjectiveError> callback) {
-            this.objective = objective;
-            this.flowIds = flowIds;
-            this.pendingGroupKeys = pendingGroupKeys;
-            this.callback = callback;
-        }
-
-        void flowInstalled(FlowId flowId) {
-            synchronized (this) {
-                flowIds.remove(flowId);
-                checkIfFinished();
-            }
-        }
-
-        void groupInstalled(PendingGroupKey pendingGroupKey) {
-            synchronized (this) {
-                pendingGroupKeys.remove(pendingGroupKey);
-                checkIfFinished();
-            }
-        }
-
-        private void checkIfFinished() {
-            if (flowIds.isEmpty() && pendingGroupKeys.isEmpty()) {
-                pendingInstallObjectives.remove(objective);
-                callback.accept(null);
-            }
-        }
-
-        void failed(Objective obj, ObjectiveError error) {
-            flowIds.forEach(flowId -> {
-                PendingFlowKey pfk = new PendingFlowKey(flowId, obj.id());
-                pendingInstallObjectiveFlows.remove(pfk);
-            });
-            pendingGroupKeys.forEach(pendingInstallObjectiveGroups::remove);
-            pendingInstallObjectives.remove(objective);
-            callback.accept(error);
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-            PendingInstallObjective pio = (PendingInstallObjective) o;
-            return Objects.equal(objective, pio.objective) &&
-                    Objects.equal(flowIds, pio.flowIds) &&
-                    Objects.equal(pendingGroupKeys, pio.pendingGroupKeys) &&
-                    Objects.equal(callback, pio.callback);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hashCode(objective, flowIds, pendingGroupKeys, callback);
-        }
-
-        @Override
-        public String toString() {
-            return MoreObjects.toStringHelper(this)
-                    .add("obj", objective)
-                    .add("flowIds", flowIds)
-                    .add("pendingGroupKeys", pendingGroupKeys)
-                    .add("callback", callback)
-                    .toString();
-        }
-    }
-
-    class PendingFlowKey {
-        private FlowId flowId;
-        private int objId;
-
-        PendingFlowKey(FlowId flowId, int objId) {
-            this.flowId = flowId;
-            this.objId = objId;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-            PendingFlowKey pendingFlowKey = (PendingFlowKey) o;
-            return Objects.equal(flowId, pendingFlowKey.flowId) &&
-                    objId == pendingFlowKey.objId;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hashCode(flowId, objId);
-        }
-
-        @Override
-        public String toString() {
-            return MoreObjects.toStringHelper(this)
-                    .add("flowId", flowId)
-                    .add("objId", objId)
-                    .toString();
-        }
-    }
-
-    class PendingGroupKey {
-        private GroupId groupId;
-        private GroupEvent.Type expectedEventType;
-
-        PendingGroupKey(GroupId groupId, NextObjective.Operation objOp) {
-            this.groupId = groupId;
-
-            switch (objOp) {
-                case ADD:
-                    expectedEventType = GroupEvent.Type.GROUP_ADDED;
-                    break;
-                case REMOVE:
-                    expectedEventType = GroupEvent.Type.GROUP_REMOVED;
-                    break;
-                case MODIFY:
-                case ADD_TO_EXISTING:
-                case REMOVE_FROM_EXISTING:
-                    expectedEventType = GroupEvent.Type.GROUP_UPDATED;
-                    break;
-                default:
-                    expectedEventType = null;
-            }
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-            PendingGroupKey pendingGroupKey = (PendingGroupKey) o;
-            return Objects.equal(groupId, pendingGroupKey.groupId) &&
-                    expectedEventType == pendingGroupKey.expectedEventType;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hashCode(groupId, expectedEventType);
-        }
-
-        @Override
-        public String toString() {
-            return MoreObjects.toStringHelper(this)
-                    .add("groupId", groupId)
-                    .add("expectedEventType", expectedEventType)
-                    .toString();
-        }
-    }
 }
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricPipelinerException.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricPipelinerException.java
new file mode 100644
index 0000000..7f6d71e
--- /dev/null
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricPipelinerException.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2018-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.onosproject.net.flowobjective.ObjectiveError;
+
+/**
+ * Signals an exception when translating a flow objective.
+ */
+class FabricPipelinerException extends Exception {
+
+    private final ObjectiveError error;
+
+    /**
+     * Creates a new exception for the given message. Sets ObjectiveError to
+     * UNSUPPORTED.
+     *
+     * @param message message
+     */
+    FabricPipelinerException(String message) {
+        super(message);
+        this.error = ObjectiveError.UNSUPPORTED;
+    }
+
+    /**
+     * Creates a new exception for the given message and ObjectiveError.
+     *
+     * @param message message
+     * @param error objective error
+     */
+    FabricPipelinerException(String message, ObjectiveError error) {
+        super(message);
+        this.error = error;
+    }
+
+    /**
+     * Returns the ObjectiveError of this exception.
+     *
+     * @return objective error
+     */
+    ObjectiveError objectiveError() {
+        return error;
+    }
+}
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FilteringObjectiveTranslator.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FilteringObjectiveTranslator.java
new file mode 100644
index 0000000..67d7b3a
--- /dev/null
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FilteringObjectiveTranslator.java
@@ -0,0 +1,237 @@
+/*
+ * 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.Lists;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+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.FabricCapabilities;
+import org.onosproject.pipelines.fabric.FabricConstants;
+
+import java.util.Collection;
+import java.util.List;
+
+import static java.lang.String.format;
+import static org.onosproject.pipelines.fabric.FabricUtils.criterion;
+
+/**
+ * ObjectiveTranslator implementation for FilteringObjective.
+ */
+class FilteringObjectiveTranslator
+        extends AbstractObjectiveTranslator<FilteringObjective> {
+
+    // Forwarding types from fabric.p4.
+    static final byte FWD_MPLS = 1;
+    static final byte FWD_IPV4_ROUTING = 2;
+    static final byte FWD_IPV6_ROUTING = 3;
+
+    private static final byte[] ONE = new byte[]{1};
+    private static final byte[] ZERO = new byte[]{0};
+
+    private static final PiAction DENY = PiAction.builder()
+            .withId(FabricConstants.FABRIC_INGRESS_FILTERING_DENY)
+            .build();
+
+    private static final PiCriterion VLAN_VALID = PiCriterion.builder()
+            .matchExact(FabricConstants.HDR_VLAN_IS_VALID, ONE)
+            .build();
+    private static final PiCriterion VLAN_INVALID = PiCriterion.builder()
+            .matchExact(FabricConstants.HDR_VLAN_IS_VALID, ZERO)
+            .build();
+
+    FilteringObjectiveTranslator(DeviceId deviceId, FabricCapabilities capabilities) {
+        super(deviceId, capabilities);
+    }
+
+    @Override
+    public ObjectiveTranslation doTranslate(FilteringObjective obj)
+            throws FabricPipelinerException {
+
+        final ObjectiveTranslation.Builder resultBuilder =
+                ObjectiveTranslation.builder();
+
+        if (obj.key() == null || obj.key().type() != Criterion.Type.IN_PORT) {
+            throw new FabricPipelinerException(
+                    format("Unsupported or missing filtering key: key=%s", obj.key()),
+                    ObjectiveError.BADPARAMS);
+        }
+
+        final PortCriterion inPort = (PortCriterion) obj.key();
+        final VlanIdCriterion vlan = (VlanIdCriterion) criterion(
+                obj.conditions(), Criterion.Type.VLAN_VID);
+        final EthCriterion ethDst = (EthCriterion) criterion(
+                obj.conditions(), Criterion.Type.ETH_DST);
+        final EthCriterion ethDstMasked = (EthCriterion) criterion(
+                obj.conditions(), Criterion.Type.ETH_DST_MASKED);
+
+        ingressPortVlanRule(obj, inPort, vlan, resultBuilder);
+        fwdClassifierRules(obj, inPort, ethDst, ethDstMasked, resultBuilder);
+
+        return resultBuilder.build();
+    }
+
+    private void ingressPortVlanRule(
+            FilteringObjective obj,
+            Criterion inPortCriterion,
+            VlanIdCriterion vlanCriterion,
+            ObjectiveTranslation.Builder resultBuilder)
+            throws FabricPipelinerException {
+
+        final boolean vlanValid = vlanCriterion != null
+                && !vlanCriterion.vlanId().equals(VlanId.NONE);
+
+        final TrafficSelector.Builder selector = DefaultTrafficSelector.builder()
+                .add(inPortCriterion)
+                .add(vlanValid ? VLAN_VALID : VLAN_INVALID);
+        if (vlanValid) {
+            selector.add(vlanCriterion);
+        }
+
+        final TrafficTreatment treatment;
+        if (obj.type().equals(FilteringObjective.Type.DENY)) {
+            treatment = DefaultTrafficTreatment.builder()
+                    .piTableAction(DENY)
+                    .build();
+        } else {
+            treatment = obj.meta() == null
+                    ? DefaultTrafficTreatment.emptyTreatment() : obj.meta();
+        }
+
+        resultBuilder.addFlowRule(flowRule(
+                obj, FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN,
+                selector.build(), treatment));
+    }
+
+    private void fwdClassifierRules(
+            FilteringObjective obj,
+            PortCriterion inPortCriterion,
+            EthCriterion ethDstCriterion,
+            EthCriterion ethDstMaskedCriterion,
+            ObjectiveTranslation.Builder resultBuilder)
+            throws FabricPipelinerException {
+
+        final List<FlowRule> flowRules = Lists.newArrayList();
+
+        final PortNumber inPort = inPortCriterion.port();
+        if (ethDstCriterion == null) {
+            if (ethDstMaskedCriterion == null) {
+                // No match. Do bridging (default action).
+                return;
+            }
+            // Masked fwd classifier rule
+            final MacAddress dstMac = ethDstMaskedCriterion.mac();
+            final MacAddress dstMacMask = ethDstMaskedCriterion.mask();
+            flowRules.add(maskedFwdClassifierRule(inPort, dstMac, dstMacMask, obj));
+        } else {
+            final MacAddress dstMac = ethDstCriterion.mac();
+            flowRules.addAll(ipFwdClassifierRules(inPort, dstMac, obj));
+            flowRules.add(mplsFwdClassifierRule(inPort, dstMac, obj));
+        }
+
+        for (FlowRule f : flowRules) {
+            resultBuilder.addFlowRule(f);
+        }
+    }
+
+    private FlowRule maskedFwdClassifierRule(
+            PortNumber inPort, MacAddress dstMac, MacAddress dstMacMask,
+            FilteringObjective obj)
+            throws FabricPipelinerException {
+        final TrafficTreatment treatment;
+        final short ethType;
+        if (dstMac.equals(MacAddress.IPV4_MULTICAST)
+                && dstMacMask.equals(MacAddress.IPV4_MULTICAST_MASK)) {
+            treatment = fwdClassifierTreatment(FWD_IPV4_ROUTING);
+            ethType = Ethernet.TYPE_IPV4;
+        } else if (dstMac.equals(MacAddress.IPV6_MULTICAST)
+                && dstMacMask.equals(MacAddress.IPV6_MULTICAST_MASK)) {
+            treatment = fwdClassifierTreatment(FWD_IPV6_ROUTING);
+            ethType = Ethernet.TYPE_IPV6;
+        } else {
+            throw new FabricPipelinerException(format(
+                    "Unsupported masked Ethernet address for fwd " +
+                            "classifier rule (mac=%s, mask=%s)",
+                    dstMac, dstMacMask));
+        }
+        return fwdClassifierRule(inPort, ethType, dstMac, dstMacMask, treatment, obj);
+    }
+
+    private Collection<FlowRule> ipFwdClassifierRules(
+            PortNumber inPort, MacAddress dstMac, FilteringObjective obj)
+            throws FabricPipelinerException {
+        final Collection<FlowRule> flowRules = Lists.newArrayList();
+        flowRules.add(fwdClassifierRule(
+                inPort, Ethernet.TYPE_IPV4, dstMac, null,
+                fwdClassifierTreatment(FWD_IPV4_ROUTING), obj));
+        flowRules.add(fwdClassifierRule(
+                inPort, Ethernet.TYPE_IPV6, dstMac, null,
+                fwdClassifierTreatment(FWD_IPV6_ROUTING), obj));
+        return flowRules;
+    }
+
+    private FlowRule mplsFwdClassifierRule(
+            PortNumber inPort, MacAddress dstMac, FilteringObjective obj)
+            throws FabricPipelinerException {
+        return fwdClassifierRule(
+                inPort, Ethernet.MPLS_UNICAST, dstMac, null,
+                fwdClassifierTreatment(FWD_MPLS), obj);
+    }
+
+    private FlowRule fwdClassifierRule(
+            PortNumber inPort, short ethType, MacAddress dstMac, MacAddress dstMacMask,
+            TrafficTreatment treatment, FilteringObjective obj)
+            throws FabricPipelinerException {
+        final TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchInPort(inPort)
+                .matchEthType(ethType)
+                .matchEthDstMasked(dstMac, dstMacMask == null
+                        ? MacAddress.EXACT_MASK : dstMacMask)
+                .build();
+        return flowRule(
+                obj, FabricConstants.FABRIC_INGRESS_FILTERING_FWD_CLASSIFIER,
+                selector, treatment);
+    }
+
+    private TrafficTreatment fwdClassifierTreatment(byte fwdType) {
+        final PiActionParam param = new PiActionParam(FabricConstants.FWD_TYPE, fwdType);
+        final PiAction action = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_FILTERING_SET_FORWARDING_TYPE)
+                .withParameter(param)
+                .build();
+        return DefaultTrafficTreatment.builder()
+                .piTableAction(action)
+                .build();
+
+    }
+}
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
index 65e3a25..4e13a74 100644
--- 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
@@ -18,14 +18,19 @@
 
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
-import org.onlab.packet.MacAddress;
 import org.onosproject.net.flow.criteria.Criterion;
-import org.onosproject.net.flow.criteria.EthCriterion;
 import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.slf4j.Logger;
 
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 import static org.onosproject.net.flow.criteria.Criterion.Type.ETH_DST;
 import static org.onosproject.net.flow.criteria.Criterion.Type.ETH_TYPE;
@@ -34,107 +39,214 @@
 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;
+import static org.onosproject.pipelines.fabric.pipeliner.ForwardingFunctionTypeCommons.MATCH_ETH_DST_NONE;
+import static org.onosproject.pipelines.fabric.pipeliner.ForwardingFunctionTypeCommons.MATCH_ETH_TYPE_IPV4;
+import static org.onosproject.pipelines.fabric.pipeliner.ForwardingFunctionTypeCommons.MATCH_ETH_TYPE_IPV6;
+import static org.onosproject.pipelines.fabric.pipeliner.ForwardingFunctionTypeCommons.MATCH_ETH_TYPE_MPLS;
+import static org.onosproject.pipelines.fabric.pipeliner.ForwardingFunctionTypeCommons.MATCH_MPLS_BOS_FALSE;
+import static org.onosproject.pipelines.fabric.pipeliner.ForwardingFunctionTypeCommons.MATCH_MPLS_BOS_TRUE;
+import static org.slf4j.LoggerFactory.getLogger;
 
-public enum ForwardingFunctionType {
+/**
+ * Forwarding function types (FFTs) that can represent a given forwarding
+ * objective. Each FFT is defined by a subset of criterion types expected to be
+ * found in the selector of the given objective, and, optionally, by their
+ * respective values (criterion instances) to match or to mismatch.
+ */
+enum ForwardingFunctionType {
     /**
-     * L2 unicast, with vlan id + mac address criterion.
+     * L2 unicast.
      */
-    L2_UNICAST,
+    L2_UNICAST(
+            Sets.newHashSet(VLAN_VID, ETH_DST), // Expected criterion types.
+            Collections.emptyList(), // Criteria to match.
+            Lists.newArrayList(MATCH_ETH_DST_NONE)), // Criteria NOT to match.
 
     /**
-     * L2 broadcast, with vlan id criterion only.
+     * L2 broadcast.
      */
-    L2_BROADCAST,
+    L2_BROADCAST(
+            Sets.newHashSet(VLAN_VID, ETH_DST),
+            Lists.newArrayList(MATCH_ETH_DST_NONE),
+            Collections.emptyList()),
+    L2_BROADCAST_ALIAS(
+            Sets.newHashSet(VLAN_VID),
+            Collections.emptyList(),
+            Collections.emptyList(),
+            L2_BROADCAST), // (Optional) FFT to return if selected.
 
     /**
-     * IPv4 unicast, with EtherType and IPv4 unicast destination address.
+     * IPv4 unicast.
      */
-    IPV4_ROUTING,
+    IPV4_ROUTING(
+            Sets.newHashSet(ETH_TYPE, IPV4_DST),
+            Lists.newArrayList(MATCH_ETH_TYPE_IPV4),
+            Collections.emptyList()),
 
     /**
-     * IPv6 unicast, with EtherType and IPv6 unicast destination address.
+     * IPv4 multicast.
      */
-    IPV6_ROUTING,
+    IPV4_ROUTING_MULTICAST(
+            Sets.newHashSet(ETH_TYPE, VLAN_VID, IPV4_DST),
+            Lists.newArrayList(MATCH_ETH_TYPE_IPV4),
+            Collections.emptyList()),
 
     /**
-     * MPLS, with EtherType, MPLS label and MPLS BOS(true) criterion.
+     * IPv6 unicast.
      */
-    MPLS,
+    IPV6_ROUTING(
+            Sets.newHashSet(ETH_TYPE, IPV6_DST),
+            Lists.newArrayList(MATCH_ETH_TYPE_IPV6),
+            Collections.emptyList()),
 
     /**
-     * Pseudo-wire, with EtherType, MPLS label and MPLS BOS(false) criterion.
+     * IPv6 multicast.
      */
-    PW,
+    IPV6_ROUTING_MULTICAST(
+            Sets.newHashSet(ETH_TYPE, VLAN_VID, IPV6_DST),
+            Lists.newArrayList(MATCH_ETH_TYPE_IPV6),
+            Collections.emptyList()),
+
+    /**
+     * MPLS segment routing.
+     */
+    MPLS_SEGMENT_ROUTING(
+            Sets.newHashSet(ETH_TYPE, MPLS_LABEL, MPLS_BOS),
+            Lists.newArrayList(MATCH_ETH_TYPE_MPLS, MATCH_MPLS_BOS_TRUE),
+            Collections.emptyList()),
+
+    /**
+     * Pseudo-wire.
+     */
+    PSEUDO_WIRE(
+            Sets.newHashSet(ETH_TYPE, MPLS_LABEL, MPLS_BOS),
+            Lists.newArrayList(MATCH_ETH_TYPE_MPLS, MATCH_MPLS_BOS_FALSE),
+            Collections.emptyList()),
 
     /**
      * Unsupported type.
      */
-    UNSUPPORTED;
+    UNKNOWN(
+            Collections.emptySet(),
+            Collections.emptyList(),
+            Collections.emptyList());
 
-    // 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 Logger log = getLogger(ForwardingFunctionType.class);
 
-    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_ROUTING)
-                    .put(IPV4_MCAST_CRITERIA_TYPE, IPV4_ROUTING)
-                    .put(IPV6_UNI_CRITERIA_TYPE, IPV6_ROUTING)
-                    .put(IPV6_MCAST_CRITERIA_TYPE, IPV6_ROUTING)
-                    .put(MPLS_UNI_CRITERIA_TYPE, MPLS)
-                    .build();
+    private final Set<Criterion.Type> expectedCriterionTypes;
+    private final Map<Criterion.Type, List<Criterion>> matchCriteria;
+    private final Map<Criterion.Type, List<Criterion>> mismatchCriteria;
+    private final ForwardingFunctionType originalType;
 
     /**
-     * Gets forwarding function type of the forwarding objective.
+     * Creates a new FFT.
      *
-     * @param fwd the forwarding objective
-     * @return forwarding function type of the forwarding objective
+     * @param expectedCriterionTypes expected criterion types
+     * @param matchCriteria          criterion instances to match
+     * @param mismatchCriteria       criterion instance not to be matched
      */
-    public static ForwardingFunctionType getForwardingFunctionType(ForwardingObjective fwd) {
-        Set<Criterion.Type> criteriaType = Sets.newHashSet();
-        //omit the criterion of ethDst type with the value of MacAddress.NONE since it indicates L2 broadcast
-        //if not, the objective is treated as L2 unicast
-        fwd.selector().criteria().stream().filter(criterion -> !ethDstIndicatesBroadcast(criterion))
-                .map(Criterion::type)
-                .forEach(criteriaType::add);
-
-        if (fwd.meta() != null) {
-            fwd.meta().criteria().stream().filter(criterion -> !ethDstIndicatesBroadcast(criterion))
-                    .map(Criterion::type)
-                    .forEach(criteriaType::add);
-        }
-
-        return FFT_MAP.getOrDefault(criteriaType, UNSUPPORTED);
+    ForwardingFunctionType(Set<Criterion.Type> expectedCriterionTypes,
+                           Collection<Criterion> matchCriteria,
+                           Collection<Criterion> mismatchCriteria) {
+        this(expectedCriterionTypes, matchCriteria, mismatchCriteria, null);
     }
 
     /**
-     * Segment Routing (SR) app. sets ethDst criterion to MacAddress.NONE for broadcast rules
-     * and demands drivers to treat the flow rules containing this criterion as broadcast rule.
+     * Creates a new alias FFT that if matched, should return the given original
+     * FFT.
      *
-     * This method checks the type and value of the criterion and detects whether it constitutes
-     * broadcast or not.
-     *
-     * For more details check RoutingRulePopulator.updateSubnetBroadcastRule method of SR app.
-     *
-     * @param criterion Criterion object
-     * @return true if the type is ETH_DST and the mac value equals to MacAddress.NONE; false otherwise.
+     * @param expectedCriterionTypes expected criterion types
+     * @param matchCriteria          criterion instances to match
+     * @param mismatchCriteria       criterion instance not to be matched
+     * @param original               original FFT to return
      */
-    private static boolean ethDstIndicatesBroadcast(Criterion criterion) {
-        return criterion.type().equals(Criterion.Type.ETH_DST) &&
-                ((EthCriterion) criterion).mac().equals(MacAddress.NONE);
+    ForwardingFunctionType(Set<Criterion.Type> expectedCriterionTypes,
+                           Collection<Criterion> matchCriteria,
+                           Collection<Criterion> mismatchCriteria,
+                           ForwardingFunctionType original) {
+        this.expectedCriterionTypes = ImmutableSet.copyOf(expectedCriterionTypes);
+        this.matchCriteria = typeToCriteriaMap(matchCriteria);
+        this.mismatchCriteria = typeToCriteriaMap(mismatchCriteria);
+        this.originalType = original == null ? this : original;
+    }
+
+    /**
+     * Attempts to guess the forwarding function type of the given forwarding
+     * objective.
+     *
+     * @param fwd the forwarding objective
+     * @return forwarding function type. {@link #UNKNOWN} if the FFT cannot be
+     * determined.
+     */
+    public static ForwardingFunctionType getForwardingFunctionType(ForwardingObjective fwd) {
+        final Set<Criterion> criteria = criteriaIncludingMeta(fwd);
+        final Set<Criterion.Type> criterionTypes = criteria.stream()
+                .map(Criterion::type).collect(Collectors.toSet());
+
+        final List<ForwardingFunctionType> candidates = Arrays.stream(ForwardingFunctionType.values())
+                // Keep FFTs which expected criterion types are the same found
+                // in the fwd objective.
+                .filter(fft -> fft.expectedCriterionTypes.equals(criterionTypes))
+                // Keep FFTs which match criteria are found in the fwd objective.
+                .filter(fft -> matchFft(criteria, fft))
+                // Keep FFTs which mismatch criteria are NOT found in the objective.
+                .filter(fft -> mismatchFft(criteria, fft))
+                .collect(Collectors.toList());
+
+        switch (candidates.size()) {
+            case 1:
+                return candidates.get(0).originalType;
+            case 0:
+                return UNKNOWN;
+            default:
+                log.warn("Multiple FFT candidates found: {} [{}]", candidates, fwd);
+                return UNKNOWN;
+        }
+    }
+
+    private static boolean matchFft(Collection<Criterion> criteria, ForwardingFunctionType fft) {
+        return matchOrMismatchFft(criteria, fft.matchCriteria, false);
+    }
+
+    private static boolean mismatchFft(Collection<Criterion> criteria, ForwardingFunctionType fft) {
+        return matchOrMismatchFft(criteria, fft.mismatchCriteria, true);
+    }
+
+    private static boolean matchOrMismatchFft(
+            Collection<Criterion> criteria,
+            Map<Criterion.Type, List<Criterion>> criteriaToMatch,
+            boolean mismatch) {
+        final Map<Criterion.Type, Criterion> givenCriteria = typeToCriterionMap(criteria);
+        for (Criterion.Type typeToMatch : criteriaToMatch.keySet()) {
+            if (!givenCriteria.containsKey(typeToMatch)) {
+                return false;
+            }
+            final boolean matchFound = criteriaToMatch.get(typeToMatch).stream()
+                    .anyMatch(c -> mismatch != givenCriteria.get(c.type()).equals(c));
+            if (!matchFound) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static Set<Criterion> criteriaIncludingMeta(ForwardingObjective fwd) {
+        final Set<Criterion> criteria = Sets.newHashSet();
+        criteria.addAll(fwd.selector().criteria());
+        // FIXME: Is this really needed? Meta is such an ambiguous field...
+        if (fwd.meta() != null) {
+            criteria.addAll(fwd.meta().criteria());
+        }
+        return criteria;
+    }
+
+    private static Map<Criterion.Type, List<Criterion>> typeToCriteriaMap(Collection<Criterion> criteria) {
+        return criteria.stream().collect(Collectors.groupingBy(Criterion::type));
+    }
+
+    private static Map<Criterion.Type, Criterion> typeToCriterionMap(Collection<Criterion> criteria) {
+        final ImmutableMap.Builder<Criterion.Type, Criterion> mapBuilder = ImmutableMap.builder();
+        criteria.forEach(c -> mapBuilder.put(c.type(), c));
+        return mapBuilder.build();
     }
 }
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/ForwardingFunctionTypeCommons.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/ForwardingFunctionTypeCommons.java
new file mode 100644
index 0000000..0c898c5
--- /dev/null
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/ForwardingFunctionTypeCommons.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2018-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.onlab.packet.EthType;
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.flow.criteria.Criteria;
+import org.onosproject.net.flow.criteria.Criterion;
+
+/**
+ * Constants common to ForwardingFunctionType operations.
+ */
+final class ForwardingFunctionTypeCommons {
+
+    static final Criterion MATCH_ETH_TYPE_IPV4 = Criteria.matchEthType(
+      EthType.EtherType.IPV4.ethType());
+    static final Criterion MATCH_ETH_TYPE_IPV6 = Criteria.matchEthType(
+      EthType.EtherType.IPV6.ethType());
+    static final Criterion MATCH_ETH_DST_NONE = Criteria.matchEthDst(
+      MacAddress.NONE);
+    static final Criterion MATCH_ETH_TYPE_MPLS = Criteria.matchEthType(
+      EthType.EtherType.MPLS_UNICAST.ethType());
+    static final Criterion MATCH_MPLS_BOS_TRUE = Criteria.matchMplsBos(true);
+    static final Criterion MATCH_MPLS_BOS_FALSE = Criteria.matchMplsBos(false);
+
+    private ForwardingFunctionTypeCommons() {
+        // hides constructor.
+    }
+}
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/ForwardingObjectiveTranslator.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/ForwardingObjectiveTranslator.java
new file mode 100644
index 0000000..5647d26
--- /dev/null
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/ForwardingObjectiveTranslator.java
@@ -0,0 +1,284 @@
+/*
+ * 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.Lists;
+import com.google.common.collect.Sets;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.DeviceId;
+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.IPCriterion;
+import org.onosproject.net.flow.criteria.MplsCriterion;
+import org.onosproject.net.flow.criteria.VlanIdCriterion;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.ObjectiveError;
+import org.onosproject.net.pi.model.PiActionId;
+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.FabricCapabilities;
+import org.onosproject.pipelines.fabric.FabricConstants;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static java.lang.String.format;
+import static org.onosproject.pipelines.fabric.FabricUtils.criterionNotNull;
+
+/**
+ * ObjectiveTranslator implementation ForwardingObjective.
+ */
+class ForwardingObjectiveTranslator
+        extends AbstractObjectiveTranslator<ForwardingObjective> {
+
+    private static final List<String> DEFAULT_ROUTE_PREFIXES = Lists.newArrayList(
+            "0.0.0.0/1", "128.0.0.0/1");
+
+    private static final Set<Criterion.Type> ACL_CRITERIA = ImmutableSet.of(
+            Criterion.Type.IN_PORT,
+            Criterion.Type.IN_PHY_PORT,
+            Criterion.Type.ETH_DST,
+            Criterion.Type.ETH_DST_MASKED,
+            Criterion.Type.ETH_SRC,
+            Criterion.Type.ETH_SRC_MASKED,
+            Criterion.Type.ETH_TYPE,
+            Criterion.Type.VLAN_VID,
+            Criterion.Type.IP_PROTO,
+            Criterion.Type.IPV4_SRC,
+            Criterion.Type.IPV4_DST,
+            Criterion.Type.TCP_SRC,
+            Criterion.Type.TCP_SRC_MASKED,
+            Criterion.Type.TCP_DST,
+            Criterion.Type.TCP_DST_MASKED,
+            Criterion.Type.UDP_SRC,
+            Criterion.Type.UDP_SRC_MASKED,
+            Criterion.Type.UDP_DST,
+            Criterion.Type.UDP_DST_MASKED,
+            Criterion.Type.ICMPV4_TYPE,
+            Criterion.Type.ICMPV4_CODE,
+            Criterion.Type.PROTOCOL_INDEPENDENT);
+
+    private static final Map<PiTableId, PiActionId> NEXT_ID_ACTIONS = ImmutableMap.<PiTableId, PiActionId>builder()
+            .put(FabricConstants.FABRIC_INGRESS_FORWARDING_BRIDGING,
+                 FabricConstants.FABRIC_INGRESS_FORWARDING_SET_NEXT_ID_BRIDGING)
+            .put(FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4,
+                 FabricConstants.FABRIC_INGRESS_FORWARDING_SET_NEXT_ID_ROUTING_V4)
+            .put(FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V6,
+                 FabricConstants.FABRIC_INGRESS_FORWARDING_SET_NEXT_ID_ROUTING_V6)
+            .put(FabricConstants.FABRIC_INGRESS_FORWARDING_MPLS,
+                 FabricConstants.FABRIC_INGRESS_FORWARDING_POP_MPLS_AND_NEXT)
+            .build();
+
+    ForwardingObjectiveTranslator(DeviceId deviceId, FabricCapabilities capabilities) {
+        super(deviceId, capabilities);
+    }
+
+    @Override
+    public ObjectiveTranslation doTranslate(ForwardingObjective obj)
+            throws FabricPipelinerException {
+        final ObjectiveTranslation.Builder resultBuilder =
+                ObjectiveTranslation.builder();
+        switch (obj.flag()) {
+            case SPECIFIC:
+                processSpecificFwd(obj, resultBuilder);
+                break;
+            case VERSATILE:
+                processVersatileFwd(obj, resultBuilder);
+                break;
+            case EGRESS:
+            default:
+                log.warn("Unsupported ForwardingObjective type '{}'", obj.flag());
+                return ObjectiveTranslation.ofError(ObjectiveError.UNSUPPORTED);
+        }
+        return resultBuilder.build();
+    }
+
+    private void processVersatileFwd(ForwardingObjective obj,
+                                     ObjectiveTranslation.Builder resultBuilder)
+            throws FabricPipelinerException {
+
+        final Set<Criterion.Type> unsupportedCriteria = obj.selector().criteria()
+                .stream()
+                .map(Criterion::type)
+                .filter(t -> !ACL_CRITERIA.contains(t))
+                .collect(Collectors.toSet());
+
+        if (!unsupportedCriteria.isEmpty()) {
+            throw new FabricPipelinerException(format(
+                    "unsupported ACL criteria %s", unsupportedCriteria.toString()));
+        }
+
+        aclRule(obj, resultBuilder);
+    }
+
+    private void processSpecificFwd(ForwardingObjective obj,
+                                    ObjectiveTranslation.Builder resultBuilder)
+            throws FabricPipelinerException {
+
+        final Set<Criterion> criteriaWithMeta = Sets.newHashSet(obj.selector().criteria());
+
+        // FIXME: Is this really needed? Meta is such an ambiguous field...
+        // Why would we match on a META field?
+        if (obj.meta() != null) {
+            criteriaWithMeta.addAll(obj.meta().criteria());
+        }
+
+        final ForwardingFunctionType fft = ForwardingFunctionType.getForwardingFunctionType(obj);
+
+        switch (fft) {
+            case UNKNOWN:
+                throw new FabricPipelinerException(
+                        "unable to detect forwarding function type");
+            case L2_UNICAST:
+                bridgingRule(obj, criteriaWithMeta, resultBuilder, false);
+                break;
+            case L2_BROADCAST:
+                bridgingRule(obj, criteriaWithMeta, resultBuilder, true);
+                break;
+            case IPV4_ROUTING:
+            case IPV4_ROUTING_MULTICAST:
+                ipv4RoutingRule(obj, criteriaWithMeta, resultBuilder);
+                break;
+            case MPLS_SEGMENT_ROUTING:
+                mplsRule(obj, criteriaWithMeta, resultBuilder);
+                break;
+            case IPV6_ROUTING:
+            case IPV6_ROUTING_MULTICAST:
+            default:
+                throw new FabricPipelinerException(format(
+                        "unsupported forwarding function type '%s'",
+                        fft));
+        }
+    }
+
+    private void bridgingRule(ForwardingObjective obj, Set<Criterion> criteriaWithMeta,
+                              ObjectiveTranslation.Builder resultBuilder,
+                              boolean broadcast)
+            throws FabricPipelinerException {
+
+        final VlanIdCriterion vlanIdCriterion = (VlanIdCriterion) criterionNotNull(
+                criteriaWithMeta, Criterion.Type.VLAN_VID);
+        final TrafficSelector.Builder selector = DefaultTrafficSelector.builder()
+                .add(vlanIdCriterion);
+
+        if (!broadcast) {
+            final EthCriterion ethDstCriterion = (EthCriterion) criterionNotNull(
+                    obj.selector(), Criterion.Type.ETH_DST);
+            selector.matchEthDstMasked(ethDstCriterion.mac(), MacAddress.EXACT_MASK);
+        }
+
+        resultBuilder.addFlowRule(flowRule(
+                obj, FabricConstants.FABRIC_INGRESS_FORWARDING_BRIDGING, selector.build()));
+    }
+
+    private void ipv4RoutingRule(ForwardingObjective obj, Set<Criterion> criteriaWithMeta,
+                                 ObjectiveTranslation.Builder resultBuilder)
+            throws FabricPipelinerException {
+        final IPCriterion ipDstCriterion = (IPCriterion) criterionNotNull(
+                criteriaWithMeta, Criterion.Type.IPV4_DST);
+
+        if (ipDstCriterion.ip().prefixLength() == 0) {
+            defaultIpv4Route(obj, resultBuilder);
+            return;
+        }
+
+        final TrafficSelector selector = DefaultTrafficSelector.builder()
+                .add(ipDstCriterion)
+                .build();
+
+        resultBuilder.addFlowRule(flowRule(
+                obj, FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4, selector));
+    }
+
+    private void defaultIpv4Route(ForwardingObjective obj,
+                                  ObjectiveTranslation.Builder resultBuilder)
+            throws FabricPipelinerException {
+
+        // Hack to work around the inability to program default rules.
+        for (String prefix : DEFAULT_ROUTE_PREFIXES) {
+            final TrafficSelector selector = DefaultTrafficSelector.builder()
+                    .matchIPDst(IpPrefix.valueOf(prefix)).build();
+            resultBuilder.addFlowRule(flowRule(
+                    obj, FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4, selector));
+        }
+    }
+
+    private void mplsRule(ForwardingObjective obj, Set<Criterion> criteriaWithMeta,
+                          ObjectiveTranslation.Builder resultBuilder)
+            throws FabricPipelinerException {
+
+        final MplsCriterion mplsCriterion = (MplsCriterion) criterionNotNull(
+                criteriaWithMeta, Criterion.Type.MPLS_LABEL);
+        final TrafficSelector selector = DefaultTrafficSelector.builder()
+                .add(mplsCriterion)
+                .build();
+
+        resultBuilder.addFlowRule(flowRule(
+                obj, FabricConstants.FABRIC_INGRESS_FORWARDING_MPLS, selector));
+    }
+
+    private void aclRule(ForwardingObjective obj,
+                         ObjectiveTranslation.Builder resultBuilder)
+            throws FabricPipelinerException {
+
+        resultBuilder.addFlowRule(flowRule(
+                obj, FabricConstants.FABRIC_INGRESS_ACL_ACL, obj.selector()));
+    }
+
+    private FlowRule flowRule(
+            ForwardingObjective obj, PiTableId tableId, TrafficSelector selector)
+            throws FabricPipelinerException {
+        return flowRule(obj, tableId, selector, nextIdOrTreatment(obj, tableId));
+    }
+
+    private static TrafficTreatment nextIdOrTreatment(
+            ForwardingObjective obj, PiTableId tableId)
+            throws FabricPipelinerException {
+        if (obj.nextId() == null) {
+            return obj.treatment();
+        } else {
+            if (!NEXT_ID_ACTIONS.containsKey(tableId)) {
+                throw new FabricPipelinerException(format(
+                        "BUG? no next_id action set for table %s", tableId));
+            }
+            return DefaultTrafficTreatment.builder()
+                    .piTableAction(
+                            setNextIdAction(obj.nextId(),
+                                            NEXT_ID_ACTIONS.get(tableId)))
+                    .build();
+        }
+    }
+
+    private static PiAction setNextIdAction(Integer nextId, PiActionId actionId) {
+        final PiActionParam nextIdParam = new PiActionParam(FabricConstants.NEXT_ID, nextId);
+        return PiAction.builder()
+                .withId(actionId)
+                .withParameter(nextIdParam)
+                .build();
+    }
+}
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/NextObjectiveTranslator.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/NextObjectiveTranslator.java
new file mode 100644
index 0000000..e45768a
--- /dev/null
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/NextObjectiveTranslator.java
@@ -0,0 +1,413 @@
+/*
+ * 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.Lists;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+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.PiCriterion;
+import org.onosproject.net.flow.criteria.VlanIdCriterion;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flowobjective.DefaultNextTreatment;
+import org.onosproject.net.flowobjective.NextObjective;
+import org.onosproject.net.flowobjective.NextTreatment;
+import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.flowobjective.ObjectiveError;
+import org.onosproject.net.group.DefaultGroupBucket;
+import org.onosproject.net.group.DefaultGroupDescription;
+import org.onosproject.net.group.DefaultGroupKey;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.pi.model.PiTableId;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionGroupId;
+import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.net.pi.runtime.PiGroupKey;
+import org.onosproject.pipelines.fabric.FabricCapabilities;
+import org.onosproject.pipelines.fabric.FabricConstants;
+import org.onosproject.pipelines.fabric.FabricUtils;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static java.lang.String.format;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.VLAN_POP;
+import static org.onosproject.pipelines.fabric.FabricUtils.criterion;
+import static org.onosproject.pipelines.fabric.FabricUtils.l2Instruction;
+import static org.onosproject.pipelines.fabric.FabricUtils.outputPort;
+
+/**
+ * ObjectiveTranslator implementation for NextObjective.
+ */
+class NextObjectiveTranslator
+        extends AbstractObjectiveTranslator<NextObjective> {
+
+    NextObjectiveTranslator(DeviceId deviceId, FabricCapabilities capabilities) {
+        super(deviceId, capabilities);
+    }
+
+    @Override
+    public ObjectiveTranslation doTranslate(NextObjective obj)
+            throws FabricPipelinerException {
+
+        final ObjectiveTranslation.Builder resultBuilder =
+                ObjectiveTranslation.builder();
+
+        switch (obj.type()) {
+            case SIMPLE:
+                simpleNext(obj, resultBuilder, false);
+                break;
+            case HASHED:
+                hashedNext(obj, resultBuilder);
+                break;
+            case BROADCAST:
+                multicastNext(obj, resultBuilder);
+                break;
+            default:
+                log.warn("Unsupported NextObjective type '{}'", obj);
+                return ObjectiveTranslation.ofError(ObjectiveError.UNSUPPORTED);
+        }
+
+        if (!isGroupModifyOp(obj)) {
+            // Generate next VLAN rules.
+            nextVlan(obj, resultBuilder);
+        }
+
+        return resultBuilder.build();
+    }
+
+    private void nextVlan(NextObjective obj,
+                          ObjectiveTranslation.Builder resultBuilder)
+            throws FabricPipelinerException {
+        if (obj.meta() == null) {
+            // Do nothing if there is no metadata in the NextObjective.
+            return;
+        }
+
+        final VlanIdCriterion vlanIdCriterion = (VlanIdCriterion) criterion(
+                obj.meta().criteria(), Criterion.Type.VLAN_VID);
+        if (vlanIdCriterion == null) {
+            // Do nothing if we can't find vlan from NextObjective metadata.
+            return;
+        }
+
+        // A VLAN ID as meta of a NextObjective indicates that packets matching
+        // the given next ID should be set with such VLAN ID.
+        final TrafficSelector selector = nextIdSelector(obj.id());
+        final TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setVlanId(vlanIdCriterion.vlanId())
+                .build();
+
+        resultBuilder.addFlowRule(flowRule(
+                obj, FabricConstants.FABRIC_INGRESS_NEXT_NEXT_VLAN,
+                selector, treatment));
+    }
+
+    private void simpleNext(NextObjective obj,
+                            ObjectiveTranslation.Builder resultBuilder,
+                            boolean forceSimple)
+            throws FabricPipelinerException {
+
+        if (capabilities.hasHashedTable()) {
+            // Use hashed table when possible.
+            hashedNext(obj, resultBuilder);
+            return;
+        }
+
+        if (obj.nextTreatments().isEmpty()) {
+            // Do nothing.
+            return;
+        } else if (!forceSimple && obj.nextTreatments().size() != 1) {
+            throw new FabricPipelinerException(format(
+                    "SIMPLE NextObjective should contain only 1 treatment, found %d",
+                    obj.nextTreatments().size()), ObjectiveError.BADPARAMS);
+        }
+
+        final TrafficSelector selector = nextIdSelector(obj.id());
+
+        final List<DefaultNextTreatment> treatments = defaultNextTreatmentsOrFail(
+                obj.nextTreatments());
+
+        if (forceSimple && treatments.size() > 1) {
+            log.warn("Forcing SIMPLE behavior for NextObjective with {} treatments []",
+                     treatments.size(), obj);
+        }
+
+        // If not forcing, we are essentially extracting the only available treatment.
+        final TrafficTreatment treatment = defaultNextTreatmentsOrFail(
+                obj.nextTreatments()).get(0).treatment();
+
+        resultBuilder.addFlowRule(flowRule(
+                obj, FabricConstants.FABRIC_INGRESS_NEXT_SIMPLE,
+                selector, treatment));
+
+        handleEgress(obj, treatment, resultBuilder, false);
+    }
+
+    private void hashedNext(NextObjective obj,
+                            ObjectiveTranslation.Builder resultBuilder)
+            throws FabricPipelinerException {
+
+        if (!capabilities.hasHashedTable()) {
+            simpleNext(obj, resultBuilder, true);
+            return;
+        }
+
+        // Updated result builder with hashed group.
+        final int groupId = selectGroup(obj, resultBuilder);
+
+        if (isGroupModifyOp(obj)) {
+            // No changes to flow rules.
+            return;
+        }
+
+        final TrafficSelector selector = nextIdSelector(obj.id());
+        final TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .piTableAction(PiActionGroupId.of(groupId))
+                .build();
+
+        resultBuilder.addFlowRule(flowRule(
+                obj, FabricConstants.FABRIC_INGRESS_NEXT_HASHED,
+                selector, treatment));
+    }
+
+    private void handleEgress(NextObjective obj, TrafficTreatment treatment,
+                              ObjectiveTranslation.Builder resultBuilder,
+                              boolean strict)
+            throws FabricPipelinerException {
+        final PortNumber outPort = outputPort(treatment);
+        final Instruction popVlanInst = l2Instruction(treatment, VLAN_POP);
+        if (popVlanInst != null && outPort != null) {
+            if (strict && treatment.allInstructions().size() > 2) {
+                throw new FabricPipelinerException(
+                        "Treatment contains instructions other " +
+                                "than OUTPUT and VLAN_POP, cannot generate " +
+                                "egress rules");
+            }
+            egressVlanPop(outPort, obj, resultBuilder);
+        }
+    }
+
+    private void egressVlanPop(PortNumber outPort, NextObjective obj,
+                               ObjectiveTranslation.Builder resultBuilder)
+            throws FabricPipelinerException {
+
+        if (obj.meta() == null) {
+            throw new FabricPipelinerException(
+                    "Cannot process egress pop VLAN rule, NextObjective has null meta",
+                    ObjectiveError.BADPARAMS);
+        }
+
+        final VlanIdCriterion vlanIdCriterion = (VlanIdCriterion) criterion(
+                obj.meta(), Criterion.Type.VLAN_VID);
+        if (vlanIdCriterion == null) {
+            throw new FabricPipelinerException(
+                    "Cannot process egress pop VLAN rule, missing VLAN_VID criterion " +
+                            "in NextObjective meta",
+                    ObjectiveError.BADPARAMS);
+        }
+
+        final PiCriterion egressVlanTableMatch = PiCriterion.builder()
+                .matchExact(FabricConstants.HDR_EG_PORT, outPort.toLong())
+                .build();
+        final TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchPi(egressVlanTableMatch)
+                .matchVlanId(vlanIdCriterion.vlanId())
+                .build();
+        final TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .popVlan()
+                .build();
+
+        resultBuilder.addFlowRule(flowRule(
+                obj, FabricConstants.FABRIC_EGRESS_EGRESS_NEXT_EGRESS_VLAN,
+                selector, treatment));
+    }
+
+    private TrafficSelector nextIdSelector(int nextId) {
+        final PiCriterion nextIdCriterion = PiCriterion.builder()
+                .matchExact(FabricConstants.HDR_NEXT_ID, nextId)
+                .build();
+        return DefaultTrafficSelector.builder()
+                .matchPi(nextIdCriterion)
+                .build();
+    }
+
+    private void multicastNext(NextObjective obj,
+                               ObjectiveTranslation.Builder resultBuilder)
+            throws FabricPipelinerException {
+
+        // Create ALL group that will be translated to a PRE multicast entry.
+        final int groupId = allGroup(obj, resultBuilder);
+
+        if (isGroupModifyOp(obj)) {
+            // No changes to flow rules.
+            return;
+        }
+
+        final TrafficSelector selector = nextIdSelector(obj.id());
+        final PiActionParam groupIdParam = new PiActionParam(
+                FabricConstants.GROUP_ID, groupId);
+        final PiAction setMcGroupAction = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_NEXT_SET_MCAST_GROUP_ID)
+                .withParameter(groupIdParam)
+                .build();
+        final TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .piTableAction(setMcGroupAction)
+                .build();
+
+        resultBuilder.addFlowRule(flowRule(
+                obj, FabricConstants.FABRIC_INGRESS_NEXT_MULTICAST,
+                selector, treatment));
+    }
+
+    private int selectGroup(NextObjective obj,
+                            ObjectiveTranslation.Builder resultBuilder)
+            throws FabricPipelinerException {
+
+        final PiTableId hashedTableId = FabricConstants.FABRIC_INGRESS_NEXT_HASHED;
+        final List<DefaultNextTreatment> defaultNextTreatments =
+                defaultNextTreatmentsOrFail(obj.nextTreatments());
+        final List<TrafficTreatment> piTreatments = Lists.newArrayList();
+
+        for (DefaultNextTreatment t : defaultNextTreatments) {
+            // Map treatment to PI...
+            piTreatments.add(mapTreatmentToPiIfNeeded(t.treatment(), hashedTableId));
+            // ...and handle egress if necessary.
+            handleEgress(obj, t.treatment(), resultBuilder, false);
+        }
+
+        final List<GroupBucket> bucketList = piTreatments.stream()
+                .map(DefaultGroupBucket::createSelectGroupBucket)
+                .collect(Collectors.toList());
+
+        final int groupId = obj.id();
+        final PiGroupKey groupKey = new PiGroupKey(
+                hashedTableId,
+                FabricConstants.FABRIC_INGRESS_NEXT_HASHED_SELECTOR,
+                groupId);
+
+        resultBuilder.addGroup(new DefaultGroupDescription(
+                deviceId,
+                GroupDescription.Type.SELECT,
+                new GroupBuckets(bucketList),
+                groupKey,
+                groupId,
+                obj.appId()));
+
+        return groupId;
+    }
+
+    private int allGroup(NextObjective obj,
+                         ObjectiveTranslation.Builder resultBuilder)
+            throws FabricPipelinerException {
+
+        final Collection<DefaultNextTreatment> defaultNextTreatments =
+                defaultNextTreatmentsOrFail(obj.nextTreatments());
+        // No need to map treatments to PI as translation of ALL groups to PRE
+        // multicast entries is based solely on the output port.
+        for (DefaultNextTreatment t : defaultNextTreatments) {
+            handleEgress(obj, t.treatment(), resultBuilder, true);
+        }
+
+        // FIXME: this implementation supports only the case in which each
+        // switch interface is associated with only one VLAN, otherwise we would
+        // need to support replicating multiple times the same packet for the
+        // same port while setting different VLAN IDs. Hence, collect in a set.
+        final Set<PortNumber> outPorts = defaultNextTreatments.stream()
+                .map(DefaultNextTreatment::treatment)
+                .map(FabricUtils::outputPort)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+
+        if (outPorts.size() != defaultNextTreatments.size()) {
+            throw new FabricPipelinerException(format(
+                    "Found BROADCAST NextObjective with %d treatments but " +
+                            "found only %d distinct OUTPUT port numbers, cannot " +
+                            "translate to ALL groups",
+                    defaultNextTreatments.size(), outPorts.size()),
+                                               ObjectiveError.UNSUPPORTED);
+        }
+
+        final List<GroupBucket> bucketList = outPorts.stream()
+                .map(p -> DefaultTrafficTreatment.builder().setOutput(p).build())
+                .map(DefaultGroupBucket::createAllGroupBucket)
+                .collect(Collectors.toList());
+        // FIXME: remove once support for clone sessions is available
+        // Right now we add a CPU port to all multicast groups. The egress
+        // pipeline is expected to drop replicated packets to the CPU if a clone
+        // was  not requested in the ingress pipeline.
+        bucketList.add(
+                DefaultGroupBucket.createAllGroupBucket(
+                        DefaultTrafficTreatment.builder()
+                                .setOutput(PortNumber.CONTROLLER)
+                                .build()));
+
+        final int groupId = obj.id();
+        // Use DefaultGroupKey instead of PiGroupKey as we don't have any
+        // action profile to apply to the groups of ALL type.
+        final GroupKey groupKey = new DefaultGroupKey(
+                FabricPipeliner.KRYO.serialize(groupId));
+
+        resultBuilder.addGroup(
+                new DefaultGroupDescription(
+                        deviceId,
+                        GroupDescription.Type.ALL,
+                        new GroupBuckets(bucketList),
+                        groupKey,
+                        groupId,
+                        obj.appId()));
+
+        return groupId;
+    }
+
+    private List<DefaultNextTreatment> defaultNextTreatmentsOrFail(
+            Collection<NextTreatment> nextTreatments)
+            throws FabricPipelinerException {
+        final List<DefaultNextTreatment> defaultNextTreatments = Lists.newArrayList();
+        final List<NextTreatment> unsupportedNextTreatments = Lists.newArrayList();
+        for (NextTreatment n : nextTreatments) {
+            if (n.type() == NextTreatment.Type.TREATMENT) {
+                defaultNextTreatments.add((DefaultNextTreatment) n);
+            } else {
+                unsupportedNextTreatments.add(n);
+            }
+        }
+        if (!unsupportedNextTreatments.isEmpty()) {
+            throw new FabricPipelinerException(format(
+                    "Unsupported NextTreatments: %s",
+                    unsupportedNextTreatments));
+        }
+        return defaultNextTreatments;
+    }
+
+    private boolean isGroupModifyOp(NextObjective obj) {
+        // If operation is ADD_TO_EXIST or REMOVE_FROM_EXIST, it means we modify
+        // group buckets only, no changes for flow rules.
+        return obj.op() == Objective.Operation.ADD_TO_EXISTING ||
+                obj.op() == Objective.Operation.REMOVE_FROM_EXISTING;
+    }
+}
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/ObjectiveTranslation.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/ObjectiveTranslation.java
new file mode 100644
index 0000000..33ffeac
--- /dev/null
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/ObjectiveTranslation.java
@@ -0,0 +1,209 @@
+/*
+ * 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.ImmutableMap;
+import com.google.common.collect.Maps;
+import org.onosproject.net.flow.FlowId;
+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.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.lang.String.format;
+
+/**
+ * Result of a pipeliner translation from an objective to flows and groups.
+ */
+final class ObjectiveTranslation {
+
+    private final ImmutableMap<FlowId, FlowRule> flowRules;
+    private final ImmutableMap<Integer, GroupDescription> groups;
+    private final ObjectiveError error;
+
+    private ObjectiveTranslation(Map<FlowId, FlowRule> flowRules,
+                                 Map<Integer, GroupDescription> groups,
+                                 ObjectiveError error) {
+        this.flowRules = ImmutableMap.copyOf(flowRules);
+        this.groups = ImmutableMap.copyOf(groups);
+        this.error = error;
+    }
+
+    /**
+     * Returns flow rules of this translation.
+     *
+     * @return flow rules
+     */
+    Collection<FlowRule> flowRules() {
+        return flowRules.values();
+    }
+
+    /**
+     * Returns groups of this translation.
+     *
+     * @return groups
+     */
+    Collection<GroupDescription> groups() {
+        return groups.values();
+    }
+
+    /**
+     * Returns the error of this translation, is any.
+     *
+     * @return optional error
+     */
+    Optional<ObjectiveError> error() {
+        return Optional.ofNullable(error);
+    }
+
+    /**
+     * Creates a new builder.
+     *
+     * @return the builder
+     */
+    static Builder builder() {
+        return new Builder();
+    }
+
+    /**
+     * Creates a new translation that signals the given error.
+     *
+     * @param error objective error
+     * @return new objective translation
+     */
+    static ObjectiveTranslation ofError(ObjectiveError error) {
+        checkNotNull(error);
+        return new ObjectiveTranslation(
+                Collections.emptyMap(), Collections.emptyMap(), error);
+    }
+
+    @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 ObjectiveTranslation other = (ObjectiveTranslation) obj;
+        return flowRulesExactMatch(other.flowRules)
+                && Objects.equals(this.groups, other.groups)
+                && Objects.equals(this.error, other.error);
+    }
+
+    private boolean flowRulesExactMatch(Map<FlowId, FlowRule> otherFlowRules) {
+        if (otherFlowRules == null || otherFlowRules.size() != this.flowRules.size()) {
+            return false;
+        }
+        return this.flowRules.values().stream()
+                .allMatch(f -> otherFlowRules.containsKey(f.id())
+                        && otherFlowRules.get(f.id()).exactMatch(f));
+    }
+
+    /**
+     * Builder for ObjectiveTranslation. This implementation checks that flow
+     * and groups are not added when an existing one with same ID (FlowId or
+     * GroupId) has already been added.
+     */
+    static final class Builder {
+
+        private final Map<FlowId, FlowRule> flowRules = Maps.newHashMap();
+        private final Map<Integer, GroupDescription> groups = Maps.newHashMap();
+
+        // Hide default constructor
+        private Builder() {
+        }
+
+        /**
+         * Adds a flow rule to this translation.
+         *
+         * @param flowRule flow rule
+         * @return this
+         * @throws FabricPipelinerException if a FlowRule with same FlowId
+         *                                  already exists in this translation
+         */
+        Builder addFlowRule(FlowRule flowRule)
+                throws FabricPipelinerException {
+            checkNotNull(flowRule);
+            if (flowRules.containsKey(flowRule.id())) {
+                final FlowRule existingFlowRule = flowRules.get(flowRule.id());
+                if (!existingFlowRule.exactMatch(flowRule)) {
+                    throw new FabricPipelinerException(format(
+                            "Another FlowRule with same ID has already been " +
+                                    "added to this translation: existing=%s, new=%s",
+                            existingFlowRule, flowRule));
+                }
+            }
+            flowRules.put(flowRule.id(), flowRule);
+            return this;
+        }
+
+        /**
+         * Adds group to this translation.
+         *
+         * @param group group
+         * @return this
+         * @throws FabricPipelinerException if a FlowRule with same GroupId
+         *                                  already exists in this translation
+         */
+        Builder addGroup(GroupDescription group)
+                throws FabricPipelinerException {
+            checkNotNull(group);
+            if (groups.containsKey(group.givenGroupId())) {
+                final GroupDescription existingGroup = groups.get(group.givenGroupId());
+                if (!existingGroup.equals(group)) {
+                    throw new FabricPipelinerException(format(
+                            "Another Group with same ID has already been " +
+                                    "added to this translation: existing=%s, new=%s",
+                            existingGroup, group));
+                }
+            }
+            groups.put(group.givenGroupId(), group);
+            return this;
+        }
+
+        /**
+         * Creates ane translation.
+         *
+         * @return translation instance
+         */
+        ObjectiveTranslation build() {
+            return new ObjectiveTranslation(flowRules, groups, null);
+        }
+    }
+}
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
deleted file mode 100644
index 575d590..0000000
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/PipelinerTranslationResult.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * 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
index 75bf40e..de2d2ee 100644
--- 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
@@ -15,6 +15,6 @@
  */
 
 /**
- * Pipeliner for fabric.p4.
+ * Pipeliner implementation classes for fabric.p4.
  */
-package org.onosproject.pipelines.fabric.pipeliner;
\ No newline at end of file
+package org.onosproject.pipelines.fabric.pipeliner;