Support creation of vendor-specific versions of the fabric pipeconf

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

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

Change-Id: I487cb806541eb9e6877ebf398a94f057613df8cc
(cherry picked from commit 36d5e7a2337c242e45ee57beacd82bba07a0851d)
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/FabricPipeconfLoader.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/FabricPipeconfLoader.java
new file mode 100644
index 0000000..1b6c2ce
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/FabricPipeconfLoader.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.pipelines.fabric.impl;
+
+import org.onosproject.core.CoreService;
+import org.onosproject.net.device.PortStatisticsDiscovery;
+import org.onosproject.net.pi.model.DefaultPiPipeconf;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.model.PiPipeconfId;
+import org.onosproject.net.pi.service.PiPipeconfService;
+import org.onosproject.pipelines.fabric.impl.behaviour.FabricPortStatisticsDiscovery;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.wiring.BundleWiring;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static java.lang.String.format;
+import static org.onosproject.pipelines.fabric.impl.FabricPipeconfManager.build;
+import static org.osgi.framework.wiring.BundleWiring.LISTRESOURCES_RECURSE;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Component responsible of building and registering fabric pipeconfs at app
+ * activation.
+ * <p>
+ * This implementation looks at the content of the resource path for p4c output,
+ * automatically building different pipeconfs for different profiles, target and
+ * platforms.
+ */
+@Component(immediate = true)
+public final class FabricPipeconfLoader {
+
+    public static final String PIPELINE_APP_NAME = "org.onosproject.pipelines.fabric";
+
+    private static Logger log = getLogger(FabricPipeconfLoader.class);
+
+    private static final String SEP = File.separator;
+    private static final String SPECTRUM = "spectrum";
+    private static final String BMV2 = "bmv2";
+    private static final String DEFAULT_PLATFORM = "default";
+    private static final String BMV2_JSON = "bmv2.json";
+    private static final String P4INFO_TXT = "p4info.txt";
+    private static final String CPU_PORT_TXT = "cpu_port.txt";
+    private static final String SPECTRUM_BIN = "spectrum.bin";
+
+    private static final String BASE_PIPECONF_ID = "org.onosproject.pipelines";
+    private static final String P4C_OUT_PATH = "/p4c-out";
+    // p4c-out/<profile>/<target>/<platform>
+    private static final String P4C_RES_BASE_PATH = P4C_OUT_PATH + "/%s/%s/%s/";
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    private PiPipeconfService piPipeconfService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    private CoreService coreService;
+
+    private Collection<PiPipeconf> pipeconfs;
+
+
+    @Activate
+    public void activate() {
+        coreService.registerApplication(PIPELINE_APP_NAME);
+        // Registers all pipeconf at component activation.
+        pipeconfs = buildAllPipeconfs();
+        pipeconfs.forEach(piPipeconfService::register);
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        pipeconfs.stream()
+                .map(PiPipeconf::id)
+                .forEach(piPipeconfService::unregister);
+        pipeconfs = null;
+        log.info("Stopped");
+    }
+
+    private Collection<PiPipeconf> buildAllPipeconfs() {
+        return FrameworkUtil
+                .getBundle(this.getClass())
+                .adapt(BundleWiring.class)
+                // List all resource files in /p4c-out
+                .listResources(P4C_OUT_PATH, "*", LISTRESOURCES_RECURSE)
+                .stream()
+                // Filter only directories
+                .filter(name -> name.endsWith(SEP))
+                // Derive profile, target, and platform and build pipeconf.
+                .map(this::buildPipeconfFromPath)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+    }
+
+    private PiPipeconf buildPipeconfFromPath(String path) {
+        String[] pieces = path.split(SEP);
+        // We expect a path of 4 elements, e.g.
+        // p4c-out/<profile>/<target>/<platform>
+        if (pieces.length != 4) {
+            return null;
+        }
+        String profile = pieces[1];
+        String target = pieces[2];
+        String platform = pieces[3];
+        final PiPipeconf pipeconf;
+        try {
+            switch (target) {
+                case BMV2:
+                    pipeconf = bmv2Pipeconf(profile, platform);
+                    break;
+                case SPECTRUM:
+                    pipeconf = spectrumPipeconf(profile, platform);
+                    break;
+                default:
+                    log.warn("Unknown target '{}', skipping pipeconf build...",
+                             target);
+                    return null;
+            }
+        } catch (FileNotFoundException e) {
+            log.warn("Unable to build pipeconf at {} because file is missing: {}",
+                     path, e.getMessage());
+            return null;
+        }
+
+        return pipeconf;
+    }
+
+    private PiPipeconf bmv2Pipeconf(String profile, String platform)
+            throws FileNotFoundException {
+        final URL bmv2JsonUrl = this.getClass().getResource(format(
+                P4C_RES_BASE_PATH + BMV2_JSON, profile, BMV2, platform));
+        final URL p4InfoUrl = this.getClass().getResource(format(
+                P4C_RES_BASE_PATH + P4INFO_TXT, profile, BMV2, platform));
+        final URL cpuPortUrl = this.getClass().getResource(format(
+                P4C_RES_BASE_PATH + CPU_PORT_TXT, profile, BMV2, platform));
+
+        checkFileExists(bmv2JsonUrl, BMV2_JSON);
+        checkFileExists(p4InfoUrl, P4INFO_TXT);
+        checkFileExists(cpuPortUrl, CPU_PORT_TXT);
+
+        final DefaultPiPipeconf.Builder builder = DefaultPiPipeconf.builder()
+                .withId(makePipeconfId(platform, profile))
+                .addBehaviour(PortStatisticsDiscovery.class,
+                              FabricPortStatisticsDiscovery.class)
+                .addExtension(PiPipeconf.ExtensionType.BMV2_JSON, bmv2JsonUrl);
+
+        return build(builder, profile, p4InfoUrl, cpuPortUrl);
+    }
+
+    // FIXME: this method should be removed. Instead, there should be a
+    //  third-party app using the FabricPipeconfService to register a
+    //  Mellanox-specific version of the fabric pipeconfs.
+    private PiPipeconf spectrumPipeconf(String profile, String platform)
+            throws FileNotFoundException {
+
+        final URL spectrumBinUrl = this.getClass().getResource(format(
+                P4C_RES_BASE_PATH + SPECTRUM_BIN, profile, SPECTRUM, platform));
+        final URL p4InfoUrl = this.getClass().getResource(format(
+                P4C_RES_BASE_PATH + P4INFO_TXT, profile, SPECTRUM, platform));
+        final URL cpuPortUrl = this.getClass().getResource(format(
+                P4C_RES_BASE_PATH + CPU_PORT_TXT, profile, SPECTRUM, platform));
+
+        checkFileExists(spectrumBinUrl, SPECTRUM_BIN);
+        checkFileExists(p4InfoUrl, P4INFO_TXT);
+        checkFileExists(cpuPortUrl, CPU_PORT_TXT);
+
+        final DefaultPiPipeconf.Builder builder = DefaultPiPipeconf.builder()
+                .withId(makePipeconfId(platform, profile))
+                .addExtension(PiPipeconf.ExtensionType.SPECTRUM_BIN, spectrumBinUrl);
+
+        return build(builder, profile, p4InfoUrl, cpuPortUrl);
+    }
+
+    private void checkFileExists(URL url, String name)
+            throws FileNotFoundException {
+        if (url == null) {
+            throw new FileNotFoundException(name);
+        }
+    }
+
+    private PiPipeconfId makePipeconfId(String platform, String profile) {
+        final String id = platform.equals(DEFAULT_PLATFORM)
+                // Omit platform if default, e.g. with BMv2 pipeconf
+                ? format("%s.%s", BASE_PIPECONF_ID, profile)
+                : format("%s.%s.%s", BASE_PIPECONF_ID, profile, platform);
+        return new PiPipeconfId(id);
+    }
+}
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/FabricPipeconfManager.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/FabricPipeconfManager.java
new file mode 100644
index 0000000..76122d9
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/FabricPipeconfManager.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.pipelines.fabric.impl;
+
+import org.onosproject.inbandtelemetry.api.IntProgrammable;
+import org.onosproject.net.behaviour.Pipeliner;
+import org.onosproject.net.pi.model.DefaultPiPipeconf;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.model.PiPipelineInterpreter;
+import org.onosproject.net.pi.model.PiPipelineModel;
+import org.onosproject.p4runtime.model.P4InfoParser;
+import org.onosproject.p4runtime.model.P4InfoParserException;
+import org.onosproject.pipelines.fabric.FabricPipeconfService;
+import org.onosproject.pipelines.fabric.impl.behaviour.FabricIntProgrammable;
+import org.onosproject.pipelines.fabric.impl.behaviour.FabricInterpreter;
+import org.onosproject.pipelines.fabric.impl.behaviour.pipeliner.FabricPipeliner;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.slf4j.Logger;
+
+import java.net.URL;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of the FabricPipeconfService.
+ */
+@Component(immediate = true, service = FabricPipeconfService.class)
+public final class FabricPipeconfManager implements FabricPipeconfService {
+
+    private static final String INT_PROFILE_SUFFIX = "-int";
+    private static final String FULL_PROFILE_SUFFIX = "-full";
+
+    private static Logger log = getLogger(FabricPipeconfLoader.class);
+
+    @Activate
+    public void activate() {
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        log.info("Stopped");
+    }
+
+    @Override
+    public PiPipeconf buildFabricPipeconf(
+            DefaultPiPipeconf.Builder builder, String profile, URL p4InfoUrl, URL cpuPortUrl) {
+        return build(builder, profile, p4InfoUrl, cpuPortUrl);
+    }
+
+    static PiPipeconf build(
+            DefaultPiPipeconf.Builder pipeconfBuilder,
+            String profileName, URL p4InfoUrl, URL cpuPortUrl) {
+        checkNotNull(pipeconfBuilder,
+                     "pipeconfBuilder cannot be null");
+        checkArgument(profileName != null && !profileName.isEmpty(),
+                      "profileName cannot be null or empty");
+        checkNotNull(p4InfoUrl,
+                     "p4InfoUrl cannot be null (check if file exists)");
+        checkNotNull(cpuPortUrl,
+                     "cpuPortUrl cannot be null (check if file exists)");
+
+        pipeconfBuilder
+                .withPipelineModel(parseP4Info(p4InfoUrl))
+                .addBehaviour(PiPipelineInterpreter.class, FabricInterpreter.class)
+                .addBehaviour(Pipeliner.class, FabricPipeliner.class)
+                .addExtension(PiPipeconf.ExtensionType.P4_INFO_TEXT, p4InfoUrl)
+                .addExtension(PiPipeconf.ExtensionType.CPU_PORT_TXT, cpuPortUrl);
+
+        // Add IntProgrammable behaviour for INT-enabled profiles.
+        if (profileName.endsWith(INT_PROFILE_SUFFIX) ||
+                profileName.endsWith(FULL_PROFILE_SUFFIX)) {
+            pipeconfBuilder.addBehaviour(IntProgrammable.class, FabricIntProgrammable.class);
+        }
+
+        return pipeconfBuilder.build();
+    }
+
+    private static PiPipelineModel parseP4Info(URL p4InfoUrl) {
+        try {
+            return P4InfoParser.parse(p4InfoUrl);
+        } catch (P4InfoParserException e) {
+            // FIXME: propagate exception that can be handled by whoever is
+            //  trying to build pipeconfs.
+            throw new IllegalStateException(e);
+        }
+    }
+}
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/AbstractFabricHandlerBehavior.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/AbstractFabricHandlerBehavior.java
new file mode 100644
index 0000000..fe07576
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/AbstractFabricHandlerBehavior.java
@@ -0,0 +1,91 @@
+/*
+ * 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.impl.behaviour;
+
+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.
+ * <p>
+ * All sub-classes must implement a default constructor, used by the abstract
+ * projectable model (i.e., {@link org.onosproject.net.Device#as(Class)}.
+ */
+public abstract class AbstractFabricHandlerBehavior extends AbstractHandlerBehaviour {
+
+    protected final Logger log = getLogger(getClass());
+
+    protected FabricCapabilities capabilities;
+
+    /**
+     * Creates a new instance of this behavior with the given capabilities.
+     * Note: this constructor should be invoked only by other classes of this
+     * package that can retrieve capabilities on their own.
+     * <p>
+     * When using the abstract projectable model (i.e., {@link
+     * org.onosproject.net.Device#as(Class)}, capabilities will be set by the
+     * driver manager when calling {@link #setHandler(DriverHandler)})
+     *
+     * @param capabilities capabilities
+     */
+    protected AbstractFabricHandlerBehavior(FabricCapabilities capabilities) {
+        this.capabilities = capabilities;
+    }
+
+    /**
+     * Create a new instance of this behaviour. Used by the abstract projectable
+     * model (i.e., {@link org.onosproject.net.Device#as(Class)}.
+     */
+    public AbstractFabricHandlerBehavior() {
+        // Do nothing
+    }
+
+    @Override
+    public void setHandler(DriverHandler handler) {
+        super.setHandler(handler);
+        final PiPipeconfService pipeconfService = handler().get(PiPipeconfService.class);
+        setCapabilitiesFromHandler(handler().data().deviceId(), pipeconfService);
+    }
+
+    private void setCapabilitiesFromHandler(
+            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/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricCapabilities.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricCapabilities.java
new file mode 100644
index 0000000..6def74f
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricCapabilities.java
@@ -0,0 +1,89 @@
+/*
+ * 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.impl.behaviour;
+
+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;
+
+    public 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();
+        }
+    }
+
+    public boolean supportDoubleVlanTerm() {
+        if (pipeconf.pipelineModel()
+                .table(FabricConstants.FABRIC_INGRESS_NEXT_NEXT_VLAN).isPresent()) {
+            return pipeconf.pipelineModel().table(FabricConstants.FABRIC_INGRESS_NEXT_NEXT_VLAN)
+                    .get().action(FabricConstants.FABRIC_INGRESS_NEXT_SET_DOUBLE_VLAN)
+                    .isPresent();
+        }
+        return false;
+    }
+}
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricConstants.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricConstants.java
new file mode 100644
index 0000000..c10f3f5
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricConstants.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.pipelines.fabric.impl.behaviour;
+
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiActionParamId;
+import org.onosproject.net.pi.model.PiActionProfileId;
+import org.onosproject.net.pi.model.PiCounterId;
+import org.onosproject.net.pi.model.PiMatchFieldId;
+import org.onosproject.net.pi.model.PiMeterId;
+import org.onosproject.net.pi.model.PiPacketMetadataId;
+import org.onosproject.net.pi.model.PiTableId;
+/**
+ * Constants for fabric pipeline.
+ */
+public final class FabricConstants {
+
+    // hide default constructor
+    private FabricConstants() {
+    }
+
+    // Header field IDs
+    public static final PiMatchFieldId HDR_IG_PORT =
+            PiMatchFieldId.of("ig_port");
+    public static final PiMatchFieldId HDR_VLAN_IS_VALID =
+            PiMatchFieldId.of("vlan_is_valid");
+    public static final PiMatchFieldId HDR_EG_PORT =
+            PiMatchFieldId.of("eg_port");
+    public static final PiMatchFieldId HDR_IPV6_SRC_NET_ID =
+            PiMatchFieldId.of("ipv6_src_net_id");
+    public static final PiMatchFieldId HDR_C_TAG = PiMatchFieldId.of("c_tag");
+    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_IS_MPLS =
+            PiMatchFieldId.of("is_mpls");
+    public static final PiMatchFieldId HDR_L4_DPORT =
+            PiMatchFieldId.of("l4_dport");
+    public static final PiMatchFieldId HDR_PPPOE_CODE =
+            PiMatchFieldId.of("pppoe_code");
+    public static final PiMatchFieldId HDR_IPV6_SRC =
+            PiMatchFieldId.of("ipv6_src");
+    public static final PiMatchFieldId HDR_ETH_SRC =
+            PiMatchFieldId.of("eth_src");
+    public static final PiMatchFieldId HDR_S_TAG = PiMatchFieldId.of("s_tag");
+    public static final PiMatchFieldId HDR_VLAN_ID =
+            PiMatchFieldId.of("vlan_id");
+    public static final PiMatchFieldId HDR_ETH_DST =
+            PiMatchFieldId.of("eth_dst");
+    public static final PiMatchFieldId HDR_ICMP_TYPE =
+            PiMatchFieldId.of("icmp_type");
+    public static final PiMatchFieldId HDR_IPV4_DST =
+            PiMatchFieldId.of("ipv4_dst");
+    public static final PiMatchFieldId HDR_IPV6_TRAFFIC_CLASS =
+            PiMatchFieldId.of("ipv6_traffic_class");
+    public static final PiMatchFieldId HDR_ETH_TYPE =
+            PiMatchFieldId.of("eth_type");
+    public static final PiMatchFieldId HDR_NEXT_ID =
+            PiMatchFieldId.of("next_id");
+    public static final PiMatchFieldId HDR_L4_SPORT =
+            PiMatchFieldId.of("l4_sport");
+    public static final PiMatchFieldId HDR_ICMP_CODE =
+            PiMatchFieldId.of("icmp_code");
+    public static final PiMatchFieldId HDR_INNER_VLAN_ID =
+            PiMatchFieldId.of("inner_vlan_id");
+    public static final PiMatchFieldId HDR_IPV4_ECN =
+            PiMatchFieldId.of("ipv4_ecn");
+    public static final PiMatchFieldId HDR_PPPOE_SESSION_ID =
+            PiMatchFieldId.of("pppoe_session_id");
+    public static final PiMatchFieldId HDR_EG_SPEC =
+            PiMatchFieldId.of("eg_spec");
+    public static final PiMatchFieldId HDR_LINE_ID =
+            PiMatchFieldId.of("line_id");
+    public static final PiMatchFieldId HDR_IPV4_DSCP =
+            PiMatchFieldId.of("ipv4_dscp");
+    public static final PiMatchFieldId HDR_IS_IPV4 =
+            PiMatchFieldId.of("is_ipv4");
+    public static final PiMatchFieldId HDR_IS_IPV6 =
+            PiMatchFieldId.of("is_ipv6");
+    public static final PiMatchFieldId HDR_GTP_IPV4_DST =
+            PiMatchFieldId.of("gtp_ipv4_dst");
+    public static final PiMatchFieldId HDR_INT_IS_VALID =
+            PiMatchFieldId.of("int_is_valid");
+    public static final PiMatchFieldId HDR_MPLS_LABEL =
+            PiMatchFieldId.of("mpls_label");
+    public static final PiMatchFieldId HDR_IP_PROTO =
+            PiMatchFieldId.of("ip_proto");
+    public static final PiMatchFieldId HDR_PPPOE_PROTOCOL =
+            PiMatchFieldId.of("pppoe_protocol");
+    // Table IDs
+    public static final PiTableId FABRIC_INGRESS_NEXT_HASHED =
+            PiTableId.of("FabricIngress.next.hashed");
+    public static final PiTableId FABRIC_INGRESS_BNG_INGRESS_T_LINE_MAP =
+            PiTableId.of("FabricIngress.bng_ingress.t_line_map");
+    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_FILTERING_FWD_CLASSIFIER =
+            PiTableId.of("FabricIngress.filtering.fwd_classifier");
+    public static final PiTableId FABRIC_INGRESS_NEXT_XCONNECT =
+            PiTableId.of("FabricIngress.next.xconnect");
+    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_NEXT_MULTICAST =
+            PiTableId.of("FabricIngress.next.multicast");
+    public static final PiTableId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_SOURCE_TB_INT_SOURCE =
+            PiTableId.of("FabricEgress.process_int_main.process_int_source.tb_int_source");
+    public static final PiTableId FABRIC_INGRESS_FORWARDING_ROUTING_V6 =
+            PiTableId.of("FabricIngress.forwarding.routing_v6");
+    public static final PiTableId FABRIC_INGRESS_FORWARDING_MPLS =
+            PiTableId.of("FabricIngress.forwarding.mpls");
+    public static final PiTableId FABRIC_INGRESS_FORWARDING_ROUTING_V4 =
+            PiTableId.of("FabricIngress.forwarding.routing_v4");
+    public static final PiTableId FABRIC_INGRESS_ACL_ACL =
+            PiTableId.of("FabricIngress.acl.acl");
+    public static final PiTableId FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN =
+            PiTableId.of("FabricIngress.filtering.ingress_port_vlan");
+    public static final PiTableId FABRIC_INGRESS_BNG_INGRESS_UPSTREAM_T_PPPOE_CP =
+            PiTableId.of("FabricIngress.bng_ingress.upstream.t_pppoe_cp");
+    public static final PiTableId FABRIC_INGRESS_BNG_INGRESS_UPSTREAM_T_PPPOE_TERM_V4 =
+            PiTableId.of("FabricIngress.bng_ingress.upstream.t_pppoe_term_v4");
+    public static final PiTableId FABRIC_INGRESS_SPGW_INGRESS_S1U_FILTER_TABLE =
+            PiTableId.of("FabricIngress.spgw_ingress.s1u_filter_table");
+    public static final PiTableId FABRIC_INGRESS_FORWARDING_BRIDGING =
+            PiTableId.of("FabricIngress.forwarding.bridging");
+    public static final PiTableId FABRIC_INGRESS_SPGW_INGRESS_DL_SESS_LOOKUP =
+            PiTableId.of("FabricIngress.spgw_ingress.dl_sess_lookup");
+    public static final PiTableId FABRIC_INGRESS_BNG_INGRESS_DOWNSTREAM_T_LINE_SESSION_MAP =
+            PiTableId.of("FabricIngress.bng_ingress.downstream.t_line_session_map");
+    public static final PiTableId FABRIC_EGRESS_EGRESS_NEXT_EGRESS_VLAN =
+            PiTableId.of("FabricEgress.egress_next.egress_vlan");
+    public static final PiTableId FABRIC_INGRESS_PROCESS_SET_SOURCE_SINK_TB_SET_SINK =
+            PiTableId.of("FabricIngress.process_set_source_sink.tb_set_sink");
+    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_PROCESS_SET_SOURCE_SINK_TB_SET_SOURCE =
+            PiTableId.of("FabricIngress.process_set_source_sink.tb_set_source");
+    public static final PiTableId FABRIC_INGRESS_BNG_INGRESS_DOWNSTREAM_T_QOS_V6 =
+            PiTableId.of("FabricIngress.bng_ingress.downstream.t_qos_v6");
+    public static final PiTableId FABRIC_INGRESS_BNG_INGRESS_DOWNSTREAM_T_QOS_V4 =
+            PiTableId.of("FabricIngress.bng_ingress.downstream.t_qos_v4");
+    public static final PiTableId FABRIC_INGRESS_BNG_INGRESS_UPSTREAM_T_PPPOE_TERM_V6 =
+            PiTableId.of("FabricIngress.bng_ingress.upstream.t_pppoe_term_v6");
+    // Indirect Counter IDs
+    public static final PiCounterId FABRIC_EGRESS_BNG_EGRESS_DOWNSTREAM_C_LINE_TX =
+            PiCounterId.of("FabricEgress.bng_egress.downstream.c_line_tx");
+    public static final PiCounterId FABRIC_INGRESS_PORT_COUNTERS_CONTROL_EGRESS_PORT_COUNTER =
+            PiCounterId.of("FabricIngress.port_counters_control.egress_port_counter");
+    public static final PiCounterId FABRIC_INGRESS_BNG_INGRESS_UPSTREAM_C_DROPPED =
+            PiCounterId.of("FabricIngress.bng_ingress.upstream.c_dropped");
+    public static final PiCounterId FABRIC_INGRESS_BNG_INGRESS_UPSTREAM_C_CONTROL =
+            PiCounterId.of("FabricIngress.bng_ingress.upstream.c_control");
+    public static final PiCounterId FABRIC_INGRESS_BNG_INGRESS_UPSTREAM_C_TERMINATED =
+            PiCounterId.of("FabricIngress.bng_ingress.upstream.c_terminated");
+    public static final PiCounterId FABRIC_INGRESS_BNG_INGRESS_DOWNSTREAM_C_LINE_RX =
+            PiCounterId.of("FabricIngress.bng_ingress.downstream.c_line_rx");
+    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_NEXT_MULTICAST_COUNTER =
+            PiCounterId.of("FabricIngress.next.multicast_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 =
+            PiCounterId.of("FabricIngress.forwarding.bridging_counter");
+    public static final PiCounterId FABRIC_INGRESS_FORWARDING_ROUTING_V4_COUNTER =
+            PiCounterId.of("FabricIngress.forwarding.routing_v4_counter");
+    public static final PiCounterId FABRIC_INGRESS_PROCESS_SET_SOURCE_SINK_COUNTER_SET_SOURCE =
+            PiCounterId.of("FabricIngress.process_set_source_sink.counter_set_source");
+    public static final PiCounterId FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_SOURCE_COUNTER_INT_SOURCE =
+            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_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 =
+            PiCounterId.of("FabricIngress.filtering.ingress_port_vlan_counter");
+    public static final PiCounterId FABRIC_INGRESS_FORWARDING_MPLS_COUNTER =
+            PiCounterId.of("FabricIngress.forwarding.mpls_counter");
+    public static final PiCounterId FABRIC_INGRESS_NEXT_HASHED_COUNTER =
+            PiCounterId.of("FabricIngress.next.hashed_counter");
+    // Action IDs
+    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_INGRESS_BNG_INGRESS_DOWNSTREAM_QOS_BESTEFF =
+            PiActionId.of("FabricIngress.bng_ingress.downstream.qos_besteff");
+    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_INGRESS_NEXT_SET_DOUBLE_VLAN =
+            PiActionId.of("FabricIngress.next.set_double_vlan");
+    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_INGRESS_ACL_DROP =
+            PiActionId.of("FabricIngress.acl.drop");
+    public static final PiActionId FABRIC_INGRESS_ACL_SET_CLONE_SESSION_ID =
+            PiActionId.of("FabricIngress.acl.set_clone_session_id");
+    public static final PiActionId FABRIC_INGRESS_BNG_INGRESS_UPSTREAM_PUNT_TO_CPU =
+            PiActionId.of("FabricIngress.bng_ingress.upstream.punt_to_cpu");
+    public static final PiActionId FABRIC_INGRESS_BNG_INGRESS_DOWNSTREAM_DROP =
+            PiActionId.of("FabricIngress.bng_ingress.downstream.drop");
+    public static final PiActionId FABRIC_INGRESS_NEXT_SET_VLAN =
+            PiActionId.of("FabricIngress.next.set_vlan");
+    public static final PiActionId FABRIC_INGRESS_ACL_NOP_ACL =
+            PiActionId.of("FabricIngress.acl.nop_acl");
+    public static final PiActionId FABRIC_INGRESS_BNG_INGRESS_SET_LINE =
+            PiActionId.of("FabricIngress.bng_ingress.set_line");
+    public static final PiActionId FABRIC_INGRESS_BNG_INGRESS_UPSTREAM_TERM_DISABLED =
+            PiActionId.of("FabricIngress.bng_ingress.upstream.term_disabled");
+    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_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_BNG_EGRESS_DOWNSTREAM_ENCAP_V4 =
+            PiActionId.of("FabricEgress.bng_egress.downstream.encap_v4");
+    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 FABRIC_EGRESS_BNG_EGRESS_DOWNSTREAM_ENCAP_V6 =
+            PiActionId.of("FabricEgress.bng_egress.downstream.encap_v6");
+    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_INGRESS_BNG_INGRESS_DOWNSTREAM_QOS_PRIO =
+            PiActionId.of("FabricIngress.bng_ingress.downstream.qos_prio");
+    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_INGRESS_NEXT_MPLS_ROUTING_HASHED =
+            PiActionId.of("FabricIngress.next.mpls_routing_hashed");
+    public static final PiActionId FABRIC_INGRESS_BNG_INGRESS_UPSTREAM_TERM_ENABLED_V6 =
+            PiActionId.of("FabricIngress.bng_ingress.upstream.term_enabled_v6");
+    public static final PiActionId FABRIC_INGRESS_BNG_INGRESS_UPSTREAM_TERM_ENABLED_V4 =
+            PiActionId.of("FabricIngress.bng_ingress.upstream.term_enabled_v4");
+    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 NOP = PiActionId.of("nop");
+    public static final PiActionId FABRIC_INGRESS_NEXT_OUTPUT_SIMPLE =
+            PiActionId.of("FabricIngress.next.output_simple");
+    public static final PiActionId FABRIC_INGRESS_FILTERING_DENY =
+            PiActionId.of("FabricIngress.filtering.deny");
+    public static final PiActionId FABRIC_INGRESS_BNG_INGRESS_DOWNSTREAM_SET_SESSION =
+            PiActionId.of("FabricIngress.bng_ingress.downstream.set_session");
+    public static final PiActionId FABRIC_INGRESS_NEXT_SET_MCAST_GROUP_ID =
+            PiActionId.of("FabricIngress.next.set_mcast_group_id");
+    public static final PiActionId FABRIC_INGRESS_FILTERING_SET_FORWARDING_TYPE =
+            PiActionId.of("FabricIngress.filtering.set_forwarding_type");
+    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_INGRESS_NEXT_OUTPUT_XCONNECT =
+            PiActionId.of("FabricIngress.next.output_xconnect");
+    // Action Param IDs
+    public static final PiActionParamId DMAC = PiActionParamId.of("dmac");
+    public static final PiActionParamId MON_PORT =
+            PiActionParamId.of("mon_port");
+    public static final PiActionParamId S1U_SGW_ADDR =
+            PiActionParamId.of("s1u_sgw_addr");
+    public static final PiActionParamId SMAC = PiActionParamId.of("smac");
+    public static final PiActionParamId CLONE_ID =
+            PiActionParamId.of("clone_id");
+    public static final PiActionParamId VLAN_ID = PiActionParamId.of("vlan_id");
+    public static final PiActionParamId LABEL = PiActionParamId.of("label");
+    public static final PiActionParamId SRC_IP = PiActionParamId.of("src_ip");
+    public static final PiActionParamId NEXT_ID = PiActionParamId.of("next_id");
+    public static final PiActionParamId INS_CNT = PiActionParamId.of("ins_cnt");
+    public static final PiActionParamId SRC_MAC = PiActionParamId.of("src_mac");
+    public static final PiActionParamId INNER_VLAN_ID =
+            PiActionParamId.of("inner_vlan_id");
+    public static final PiActionParamId PPPOE_SESSION_ID =
+            PiActionParamId.of("pppoe_session_id");
+    public static final PiActionParamId MON_MAC = PiActionParamId.of("mon_mac");
+    public static final PiActionParamId MON_IP = PiActionParamId.of("mon_ip");
+    public static final PiActionParamId SWITCH_ID =
+            PiActionParamId.of("switch_id");
+    public static final PiActionParamId INS_MASK0003 =
+            PiActionParamId.of("ins_mask0003");
+    public static final PiActionParamId LINE_ID = PiActionParamId.of("line_id");
+    public static final PiActionParamId FWD_TYPE =
+            PiActionParamId.of("fwd_type");
+    public static final PiActionParamId OUTER_VLAN_ID =
+            PiActionParamId.of("outer_vlan_id");
+    public static final PiActionParamId INS_MASK0407 =
+            PiActionParamId.of("ins_mask0407");
+    public static final PiActionParamId TEID = PiActionParamId.of("teid");
+    public static final PiActionParamId S1U_ENB_ADDR =
+            PiActionParamId.of("s1u_enb_addr");
+    public static final PiActionParamId PORT_NUM =
+            PiActionParamId.of("port_num");
+    public static final PiActionParamId GROUP_ID =
+            PiActionParamId.of("group_id");
+    public static final PiActionParamId MAX_HOP = PiActionParamId.of("max_hop");
+    // Action Profile IDs
+    public static final PiActionProfileId FABRIC_INGRESS_NEXT_HASHED_SELECTOR =
+            PiActionProfileId.of("FabricIngress.next.hashed_selector");
+    // Packet Metadata IDs
+    public static final PiPacketMetadataId INGRESS_PORT =
+            PiPacketMetadataId.of("ingress_port");
+    public static final PiPacketMetadataId EGRESS_PORT =
+            PiPacketMetadataId.of("egress_port");
+    // Meter IDs
+    public static final PiMeterId FABRIC_INGRESS_BNG_INGRESS_DOWNSTREAM_M_BESTEFF =
+            PiMeterId.of("FabricIngress.bng_ingress.downstream.m_besteff");
+    public static final PiMeterId FABRIC_INGRESS_BNG_INGRESS_DOWNSTREAM_M_PRIO =
+            PiMeterId.of("FabricIngress.bng_ingress.downstream.m_prio");
+}
\ No newline at end of file
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricIntProgrammable.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricIntProgrammable.java
new file mode 100644
index 0000000..4fc8286
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricIntProgrammable.java
@@ -0,0 +1,501 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.pipelines.fabric.impl.behaviour;
+
+import com.google.common.collect.Sets;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.inbandtelemetry.api.IntConfig;
+import org.onosproject.inbandtelemetry.api.IntIntent;
+import org.onosproject.inbandtelemetry.api.IntObjective;
+import org.onosproject.inbandtelemetry.api.IntProgrammable;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.net.device.DeviceService;
+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.FlowRuleService;
+import org.onosproject.net.flow.TableId;
+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.IPCriterion;
+import org.onosproject.net.flow.criteria.PiCriterion;
+import org.onosproject.net.flow.criteria.TcpPortCriterion;
+import org.onosproject.net.flow.criteria.UdpPortCriterion;
+import org.onosproject.net.pi.model.PiTableId;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.pipelines.fabric.impl.FabricPipeconfLoader;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import static org.onlab.util.ImmutableByteSequence.copyFrom;
+
+/**
+ * Implementation of INT programmable behavior for fabric.p4. Currently supports
+ * only SOURCE and TRANSIT functionalities.
+ */
+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;
+    private static final int MAXHOP = 64;
+    private static final int PORTMASK = 0xffff;
+    private static final int PKT_INSTANCE_TYPE_INGRESS_CLONE = 1;
+
+    private static final Set<Criterion.Type> SUPPORTED_CRITERION = Sets.newHashSet(
+            Criterion.Type.IPV4_DST, Criterion.Type.IPV4_SRC,
+            Criterion.Type.UDP_SRC, Criterion.Type.UDP_DST,
+            Criterion.Type.TCP_SRC, Criterion.Type.TCP_DST,
+            Criterion.Type.IP_PROTO);
+
+    private static final Set<PiTableId> TABLES_TO_CLEANUP = Sets.newHashSet(
+            FabricConstants.FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_TB_INT_INSERT,
+            FabricConstants.FABRIC_INGRESS_PROCESS_SET_SOURCE_SINK_TB_SET_SOURCE,
+            FabricConstants.FABRIC_INGRESS_PROCESS_SET_SOURCE_SINK_TB_SET_SINK,
+            FabricConstants.FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_SOURCE_TB_INT_SOURCE,
+            FabricConstants.FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_REPORT_TB_GENERATE_REPORT
+    );
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    private FlowRuleService flowRuleService;
+
+    private CoreService coreService;
+    private NetworkConfigService cfgService;
+    private DeviceId deviceId;
+    private ApplicationId appId;
+
+    /**
+     * Creates a new instance of this behavior with the given capabilities.
+     *
+     * @param capabilities capabilities
+     */
+    protected FabricIntProgrammable(FabricCapabilities capabilities) {
+        super(capabilities);
+    }
+
+    /**
+     * Create a new instance of this behaviour. Used by the abstract projectable
+     * model (i.e., {@link org.onosproject.net.Device#as(Class)}.
+     */
+    public FabricIntProgrammable() {
+        super();
+    }
+
+    private boolean setupBehaviour() {
+        deviceId = this.data().deviceId();
+        flowRuleService = handler().get(FlowRuleService.class);
+        coreService = handler().get(CoreService.class);
+        cfgService = handler().get(NetworkConfigService.class);
+        appId = coreService.getAppId(FabricPipeconfLoader.PIPELINE_APP_NAME);
+        if (appId == null) {
+            log.warn("Application ID is null. Cannot initialize behaviour.");
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean init() {
+
+        if (!setupBehaviour()) {
+            return false;
+        }
+
+        // FIXME: create config class for INT to allow specifying arbitrary
+        //  switch IDs. The one for the GeneralDeviceProvider was temporary and
+        //  now has been removed. For now we use the chassis ID.
+        // final GeneralProviderDeviceConfig cfg = cfgService.getConfig(
+        //         deviceId, GeneralProviderDeviceConfig.class);
+        // if (cfg == null) {
+        //     log.warn("Missing GeneralProviderDevice config for {}", deviceId);
+        //     return false;
+        // }
+        // final String switchId = cfg.protocolsInfo().containsKey("int") ?
+        //         cfg.protocolsInfo().get("int").configValues().get("switchId")
+        //         : null;
+        // if (switchId == null || switchId.isEmpty()) {
+        //     log.warn("Missing INT device config for {}", deviceId);
+        //     return false;
+        // }
+
+        PiActionParam transitIdParam = new PiActionParam(
+                FabricConstants.SWITCH_ID,
+                copyFrom(handler().get(DeviceService.class)
+                                 .getDevice(deviceId).chassisId().id()));
+
+        PiAction transitAction = PiAction.builder()
+                .withId(FabricConstants.FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INIT_METADATA)
+                .withParameter(transitIdParam)
+                .build();
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .piTableAction(transitAction)
+                .build();
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchPi(PiCriterion.builder().matchExact(
+                        FabricConstants.HDR_INT_IS_VALID, (byte) 0x01)
+                                 .build())
+                .build();
+
+        FlowRule transitFlowRule = DefaultFlowRule.builder()
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .fromApp(appId)
+                .withPriority(DEFAULT_PRIORITY)
+                .makePermanent()
+                .forDevice(deviceId)
+                .forTable(FabricConstants.FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_TB_INT_INSERT)
+                .build();
+
+        flowRuleService.applyFlowRules(transitFlowRule);
+        return true;
+    }
+
+    @Override
+    public boolean setSourcePort(PortNumber port) {
+
+        if (!setupBehaviour()) {
+            return false;
+        }
+
+        PiCriterion ingressCriterion = PiCriterion.builder()
+                .matchExact(FabricConstants.HDR_IG_PORT, port.toLong())
+                .build();
+        TrafficSelector srcSelector = DefaultTrafficSelector.builder()
+                .matchPi(ingressCriterion)
+                .build();
+        PiAction setSourceAct = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_PROCESS_SET_SOURCE_SINK_INT_SET_SOURCE)
+                .build();
+        TrafficTreatment srcTreatment = DefaultTrafficTreatment.builder()
+                .piTableAction(setSourceAct)
+                .build();
+        FlowRule srcFlowRule = DefaultFlowRule.builder()
+                .withSelector(srcSelector)
+                .withTreatment(srcTreatment)
+                .fromApp(appId)
+                .withPriority(DEFAULT_PRIORITY)
+                .makePermanent()
+                .forDevice(deviceId)
+                .forTable(FabricConstants.FABRIC_INGRESS_PROCESS_SET_SOURCE_SINK_TB_SET_SOURCE)
+                .build();
+        flowRuleService.applyFlowRules(srcFlowRule);
+        return true;
+    }
+
+    @Override
+    public boolean setSinkPort(PortNumber port) {
+
+        if (!setupBehaviour()) {
+            return false;
+        }
+
+        PiCriterion egressCriterion = PiCriterion.builder()
+                .matchExact(FabricConstants.HDR_EG_PORT, port.toLong())
+                .build();
+        TrafficSelector sinkSelector = DefaultTrafficSelector.builder()
+                .matchPi(egressCriterion)
+                .build();
+        PiAction setSinkAct = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_PROCESS_SET_SOURCE_SINK_INT_SET_SINK)
+                .build();
+        TrafficTreatment sinkTreatment = DefaultTrafficTreatment.builder()
+                .piTableAction(setSinkAct)
+                .build();
+        FlowRule sinkFlowRule = DefaultFlowRule.builder()
+                .withSelector(sinkSelector)
+                .withTreatment(sinkTreatment)
+                .fromApp(appId)
+                .withPriority(DEFAULT_PRIORITY)
+                .makePermanent()
+                .forDevice(deviceId)
+                .forTable(FabricConstants.FABRIC_INGRESS_PROCESS_SET_SOURCE_SINK_TB_SET_SINK)
+                .build();
+        flowRuleService.applyFlowRules(sinkFlowRule);
+        return true;
+    }
+
+    @Override
+    public boolean addIntObjective(IntObjective obj) {
+
+        if (!setupBehaviour()) {
+            return false;
+        }
+
+        return processIntObjective(obj, true);
+    }
+
+    @Override
+    public boolean removeIntObjective(IntObjective obj) {
+
+        if (!setupBehaviour()) {
+            return false;
+        }
+
+        return processIntObjective(obj, false);
+    }
+
+    @Override
+    public boolean setupIntConfig(IntConfig config) {
+
+        if (!setupBehaviour()) {
+            return false;
+        }
+
+        return setupIntReportInternal(config);
+    }
+
+    @Override
+    public void cleanup() {
+
+        if (!setupBehaviour()) {
+            return;
+        }
+
+        StreamSupport.stream(flowRuleService.getFlowEntries(
+                data().deviceId()).spliterator(), false)
+                .filter(f -> f.table().type() == TableId.Type.PIPELINE_INDEPENDENT)
+                .filter(f -> TABLES_TO_CLEANUP.contains((PiTableId) f.table()))
+                .forEach(flowRuleService::removeFlowRules);
+    }
+
+    @Override
+    public boolean supportsFunctionality(IntFunctionality functionality) {
+        // Sink not fully supported yet.
+        return functionality == IntFunctionality.SOURCE || functionality == IntFunctionality.TRANSIT;
+    }
+
+    private FlowRule buildWatchlistEntry(IntObjective obj) {
+        int instructionBitmap = buildInstructionBitmap(obj.metadataTypes());
+        PiActionParam maxHopParam = new PiActionParam(
+                FabricConstants.MAX_HOP,
+                copyFrom(MAXHOP));
+        PiActionParam instCntParam = new PiActionParam(
+                FabricConstants.INS_CNT,
+                copyFrom(Integer.bitCount(instructionBitmap)));
+        PiActionParam inst0003Param = new PiActionParam(
+                FabricConstants.INS_MASK0003,
+                copyFrom((instructionBitmap >> 12) & 0xF));
+        PiActionParam inst0407Param = new PiActionParam(
+                FabricConstants.INS_MASK0407,
+                copyFrom((instructionBitmap >> 8) & 0xF));
+
+        PiAction intSourceAction = PiAction.builder()
+                .withId(FabricConstants.FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_SOURCE_INT_SOURCE_DSCP)
+                .withParameter(maxHopParam)
+                .withParameter(instCntParam)
+                .withParameter(inst0003Param)
+                .withParameter(inst0407Param)
+                .build();
+
+        TrafficTreatment instTreatment = DefaultTrafficTreatment.builder()
+                .piTableAction(intSourceAction)
+                .build();
+
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        for (Criterion criterion : obj.selector().criteria()) {
+            switch (criterion.type()) {
+                case IPV4_SRC:
+                    sBuilder.matchIPSrc(((IPCriterion) criterion).ip());
+                    break;
+                case IPV4_DST:
+                    sBuilder.matchIPDst(((IPCriterion) criterion).ip());
+                    break;
+                case TCP_SRC:
+                    sBuilder.matchPi(
+                            PiCriterion.builder().matchTernary(
+                                    FabricConstants.HDR_L4_SPORT,
+                                    ((TcpPortCriterion) criterion).tcpPort().toInt(), PORTMASK)
+                                    .build());
+                    break;
+                case UDP_SRC:
+                    sBuilder.matchPi(
+                            PiCriterion.builder().matchTernary(
+                                    FabricConstants.HDR_L4_SPORT,
+                                    ((UdpPortCriterion) criterion).udpPort().toInt(), PORTMASK)
+                                    .build());
+                    break;
+                case TCP_DST:
+                    sBuilder.matchPi(
+                            PiCriterion.builder().matchTernary(
+                                    FabricConstants.HDR_L4_DPORT,
+                                    ((TcpPortCriterion) criterion).tcpPort().toInt(), PORTMASK)
+                                    .build());
+                    break;
+                case UDP_DST:
+                    sBuilder.matchPi(
+                            PiCriterion.builder().matchTernary(
+                                    FabricConstants.HDR_L4_DPORT,
+                                    ((UdpPortCriterion) criterion).udpPort().toInt(), PORTMASK)
+                                    .build());
+                    break;
+                default:
+                    log.warn("Unsupported criterion type: {}", criterion.type());
+            }
+        }
+
+        return DefaultFlowRule.builder()
+                .forDevice(deviceId)
+                .withSelector(sBuilder.build())
+                .withTreatment(instTreatment)
+                .withPriority(DEFAULT_PRIORITY)
+                .forTable(FabricConstants.FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_SOURCE_TB_INT_SOURCE)
+                .fromApp(appId)
+                .makePermanent()
+                .build();
+    }
+
+    private int buildInstructionBitmap(Set<IntIntent.IntMetadataType> metadataTypes) {
+        int instBitmap = 0;
+        for (IntIntent.IntMetadataType metadataType : metadataTypes) {
+            switch (metadataType) {
+                case SWITCH_ID:
+                    instBitmap |= (1 << 15);
+                    break;
+                case L1_PORT_ID:
+                    instBitmap |= (1 << 14);
+                    break;
+                case HOP_LATENCY:
+                    instBitmap |= (1 << 13);
+                    break;
+                case QUEUE_OCCUPANCY:
+                    instBitmap |= (1 << 12);
+                    break;
+                case INGRESS_TIMESTAMP:
+                    instBitmap |= (1 << 11);
+                    break;
+                case EGRESS_TIMESTAMP:
+                    instBitmap |= (1 << 10);
+                    break;
+                case L2_PORT_ID:
+                    instBitmap |= (1 << 9);
+                    break;
+                case EGRESS_TX_UTIL:
+                    instBitmap |= (1 << 8);
+                    break;
+                default:
+                    log.info("Unsupported metadata type {}. Ignoring...", metadataType);
+                    break;
+            }
+        }
+        return instBitmap;
+    }
+
+    /**
+     * Returns a subset of Criterion from given selector, which is unsupported
+     * by this INT pipeline.
+     *
+     * @param selector a traffic selector
+     * @return a subset of Criterion from given selector, unsupported by this
+     * INT pipeline, empty if all criteria are supported.
+     */
+    private Set<Criterion> unsupportedSelectors(TrafficSelector selector) {
+        return selector.criteria().stream()
+                .filter(criterion -> !SUPPORTED_CRITERION.contains(criterion.type()))
+                .collect(Collectors.toSet());
+    }
+
+    private boolean processIntObjective(IntObjective obj, boolean install) {
+        if (install && !unsupportedSelectors(obj.selector()).isEmpty()) {
+            log.warn("Criteria {} not supported by {} for INT watchlist",
+                     unsupportedSelectors(obj.selector()), deviceId);
+            return false;
+        }
+
+        FlowRule flowRule = buildWatchlistEntry(obj);
+        if (flowRule != null) {
+            if (install) {
+                flowRuleService.applyFlowRules(flowRule);
+            } else {
+                flowRuleService.removeFlowRules(flowRule);
+            }
+            log.debug("IntObjective {} has been {} {}",
+                      obj, install ? "installed to" : "removed from", deviceId);
+            return true;
+        } else {
+            log.warn("Failed to {} IntObjective {} on {}",
+                     install ? "install" : "remove", obj, deviceId);
+            return false;
+        }
+    }
+
+    private boolean setupIntReportInternal(IntConfig cfg) {
+        // Report not fully supported yet.
+        return true;
+        // FlowRule reportRule = buildReportEntry(cfg, PKT_INSTANCE_TYPE_INGRESS_CLONE);
+        // if (reportRule != null) {
+        //     flowRuleService.applyFlowRules(reportRule);
+        //     log.info("Report entry {} has been added to {}", reportRule, this.data().deviceId());
+        //     return true;
+        // } else {
+        //     log.warn("Failed to add report entry on {}", this.data().deviceId());
+        //     return false;
+        // }
+    }
+
+    private FlowRule buildReportEntry(IntConfig cfg, int type) {
+
+        if (!setupBehaviour()) {
+            return null;
+        }
+
+        PiActionParam srcMacParam = new PiActionParam(
+                FabricConstants.SRC_MAC,
+                copyFrom(cfg.sinkMac().toBytes()));
+        PiActionParam nextHopMacParam = new PiActionParam(
+                FabricConstants.MON_MAC,
+                copyFrom(cfg.collectorNextHopMac().toBytes()));
+        PiActionParam srcIpParam = new PiActionParam(
+                FabricConstants.SRC_IP,
+                copyFrom(cfg.sinkIp().toOctets()));
+        PiActionParam monIpParam = new PiActionParam(
+                FabricConstants.MON_IP,
+                copyFrom(cfg.collectorIp().toOctets()));
+        PiActionParam monPortParam = new PiActionParam(
+                FabricConstants.MON_PORT,
+                copyFrom(cfg.collectorPort().toInt()));
+        PiAction reportAction = PiAction.builder()
+                .withId(FabricConstants.FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_REPORT_DO_REPORT_ENCAPSULATION)
+                .withParameter(srcMacParam)
+                .withParameter(nextHopMacParam)
+                .withParameter(srcIpParam)
+                .withParameter(monIpParam)
+                .withParameter(monPortParam)
+                .build();
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .piTableAction(reportAction)
+                .build();
+
+        return DefaultFlowRule.builder()
+                .withTreatment(treatment)
+                .fromApp(appId)
+                .withPriority(DEFAULT_PRIORITY)
+                .makePermanent()
+                .forDevice(this.data().deviceId())
+                .forTable(FabricConstants.FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_REPORT_TB_GENERATE_REPORT)
+                .build();
+    }
+
+}
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricInterpreter.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricInterpreter.java
new file mode 100644
index 0000000..816c0dd
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricInterpreter.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.pipelines.fabric.impl.behaviour;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Ethernet;
+import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.DriverHandler;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.packet.DefaultInboundPacket;
+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.PiPipelineInterpreter;
+import org.onosproject.net.pi.model.PiTableId;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiPacketMetadata;
+import org.onosproject.net.pi.runtime.PiPacketOperation;
+
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import static java.lang.String.format;
+import static java.util.stream.Collectors.toList;
+import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.onosproject.net.PortNumber.CONTROLLER;
+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;
+
+/**
+ * Interpreter for fabric pipeline.
+ */
+public class FabricInterpreter extends AbstractFabricHandlerBehavior
+        implements PiPipelineInterpreter {
+
+    private static final int PORT_BITWIDTH = 9;
+
+    // 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,
+            FabricConstants.FABRIC_INGRESS_NEXT_XCONNECT);
+    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.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_ID)
+                    .put(Criterion.Type.INNER_VLAN_VID, FabricConstants.HDR_INNER_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)
+                    .put(Criterion.Type.UDP_DST, FabricConstants.HDR_L4_DPORT)
+                    .put(Criterion.Type.UDP_SRC, FabricConstants.HDR_L4_SPORT)
+                    .put(Criterion.Type.UDP_DST_MASKED, FabricConstants.HDR_L4_DPORT)
+                    .put(Criterion.Type.UDP_SRC_MASKED, FabricConstants.HDR_L4_SPORT)
+                    .put(Criterion.Type.TCP_DST, FabricConstants.HDR_L4_DPORT)
+                    .put(Criterion.Type.TCP_SRC, FabricConstants.HDR_L4_SPORT)
+                    .put(Criterion.Type.TCP_DST_MASKED, FabricConstants.HDR_L4_DPORT)
+                    .put(Criterion.Type.TCP_SRC_MASKED, FabricConstants.HDR_L4_SPORT)
+                    .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, NOP)
+                    .build();
+
+    private FabricTreatmentInterpreter treatmentInterpreter;
+
+    /**
+     * Creates a new instance of this behavior with the given capabilities.
+     *
+     * @param capabilities capabilities
+     */
+    public FabricInterpreter(FabricCapabilities capabilities) {
+        super(capabilities);
+        instantiateTreatmentInterpreter();
+    }
+
+    /**
+     * Create a new instance of this behaviour. Used by the abstract projectable
+     * model (i.e., {@link org.onosproject.net.Device#as(Class)}.
+     */
+    public FabricInterpreter() {
+        super();
+    }
+
+    private void instantiateTreatmentInterpreter() {
+        this.treatmentInterpreter = new FabricTreatmentInterpreter(this.capabilities);
+    }
+
+    @Override
+    public void setHandler(DriverHandler handler) {
+        super.setHandler(handler);
+        instantiateTreatmentInterpreter();
+    }
+
+    @Override
+    public Optional<PiMatchFieldId> mapCriterionType(Criterion.Type type) {
+        return Optional.ofNullable(CRITERION_MAP.get(type));
+    }
+
+    @Override
+    public Optional<PiTableId> mapFlowRuleTableId(int 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 PiAction mapTreatment(TrafficTreatment treatment, PiTableId piTableId)
+            throws PiInterpreterException {
+        if (FILTERING_CTRL_TBLS.contains(piTableId)) {
+            return treatmentInterpreter.mapFilteringTreatment(treatment, piTableId);
+        } else if (FORWARDING_CTRL_TBLS.contains(piTableId)) {
+            return treatmentInterpreter.mapForwardingTreatment(treatment, piTableId);
+        } else if (ACL_CTRL_TBLS.contains(piTableId)) {
+            return treatmentInterpreter.mapAclTreatment(treatment, piTableId);
+        } else if (NEXT_CTRL_TBLS.contains(piTableId)) {
+            return treatmentInterpreter.mapNextTreatment(treatment, piTableId);
+        } else if (E_NEXT_CTRL_TBLS.contains(piTableId)) {
+            return treatmentInterpreter.mapEgressNextTreatment(treatment, piTableId);
+        } else {
+            throw new PiInterpreterException(format(
+                    "Treatment mapping not supported for table '%s'", piTableId));
+        }
+    }
+
+    private PiPacketOperation createPiPacketOperation(
+            DeviceId deviceId, ByteBuffer data, long portNumber)
+            throws PiInterpreterException {
+        PiPacketMetadata metadata = createPacketMetadata(portNumber);
+        return PiPacketOperation.builder()
+                .withType(PACKET_OUT)
+                .withData(copyFrom(data))
+                .withMetadatas(ImmutableList.of(metadata))
+                .build();
+    }
+
+    private PiPacketMetadata createPacketMetadata(long portNumber)
+            throws PiInterpreterException {
+        try {
+            return PiPacketMetadata.builder()
+                    .withId(FabricConstants.EGRESS_PORT)
+                    .withValue(copyFrom(portNumber).fit(PORT_BITWIDTH))
+                    .build();
+        } catch (ImmutableByteSequence.ByteSequenceTrimException e) {
+            throw new PiInterpreterException(format(
+                    "Port number '%d' too big, %s", portNumber, e.getMessage()));
+        }
+    }
+
+    @Override
+    public Collection<PiPacketOperation> mapOutboundPacket(OutboundPacket packet)
+            throws PiInterpreterException {
+        DeviceId deviceId = packet.sendThrough();
+        TrafficTreatment treatment = packet.treatment();
+
+        // fabric.p4 supports only OUTPUT instructions.
+        List<Instructions.OutputInstruction> outInstructions = treatment
+                .allInstructions()
+                .stream()
+                .filter(i -> i.type().equals(OUTPUT))
+                .map(i -> (Instructions.OutputInstruction) i)
+                .collect(toList());
+
+        if (treatment.allInstructions().size() != outInstructions.size()) {
+            // There are other instructions that are not of type OUTPUT.
+            throw new PiInterpreterException("Treatment not supported: " + treatment);
+        }
+
+        ImmutableList.Builder<PiPacketOperation> builder = ImmutableList.builder();
+        for (Instructions.OutputInstruction outInst : outInstructions) {
+            if (outInst.port().isLogical() && !outInst.port().equals(FLOOD)) {
+                throw new PiInterpreterException(format(
+                        "Output on logical port '%s' not supported", outInst.port()));
+            } else if (outInst.port().equals(FLOOD)) {
+                // Since fabric.p4 does not support flooding, we create a packet
+                // operation for each switch port.
+                final DeviceService deviceService = handler().get(DeviceService.class);
+                for (Port port : deviceService.getPorts(packet.sendThrough())) {
+                    builder.add(createPiPacketOperation(deviceId, packet.data(), port.number().toLong()));
+                }
+            } else {
+                builder.add(createPiPacketOperation(deviceId, packet.data(), outInst.port().toLong()));
+            }
+        }
+        return builder.build();
+    }
+
+    @Override
+    public InboundPacket mapInboundPacket(PiPacketOperation packetIn, DeviceId deviceId) throws PiInterpreterException {
+        // Assuming that the packet is ethernet, which is fine since fabric.p4
+        // can deparse only ethernet packets.
+        Ethernet ethPkt;
+        try {
+            ethPkt = Ethernet.deserializer().deserialize(packetIn.data().asArray(), 0,
+                                                         packetIn.data().size());
+        } catch (DeserializationException dex) {
+            throw new PiInterpreterException(dex.getMessage());
+        }
+
+        // Returns the ingress port packet metadata.
+        Optional<PiPacketMetadata> packetMetadata = packetIn.metadatas()
+                .stream().filter(m -> m.id().equals(FabricConstants.INGRESS_PORT))
+                .findFirst();
+
+        if (packetMetadata.isPresent()) {
+            ImmutableByteSequence portByteSequence = packetMetadata.get().value();
+            short s = portByteSequence.asReadOnlyBuffer().getShort();
+            ConnectPoint receivedFrom = new ConnectPoint(deviceId, PortNumber.portNumber(s));
+            ByteBuffer rawData = ByteBuffer.wrap(packetIn.data().asArray());
+            return new DefaultInboundPacket(receivedFrom, ethPkt, rawData);
+        } else {
+            throw new PiInterpreterException(format(
+                    "Missing metadata '%s' in packet-in received from '%s': %s",
+                    FabricConstants.INGRESS_PORT, deviceId, packetIn));
+        }
+    }
+
+    @Override
+    public Optional<PiAction> getOriginalDefaultAction(PiTableId tableId) {
+        return Optional.ofNullable(DEFAULT_ACTIONS.get(tableId));
+    }
+
+    @Override
+    public Optional<Integer> mapLogicalPortNumber(PortNumber port) {
+        if (!port.equals(CONTROLLER)) {
+            return Optional.empty();
+        }
+        return capabilities.cpuPort();
+    }
+}
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricPortStatisticsDiscovery.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricPortStatisticsDiscovery.java
new file mode 100644
index 0000000..b461ad3
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricPortStatisticsDiscovery.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.pipelines.fabric.impl.behaviour;
+
+
+import org.onosproject.net.pi.model.PiCounterId;
+import org.onosproject.pipelines.basic.PortStatisticsDiscoveryImpl;
+
+/**
+ * Implementation of the PortStatisticsBehaviour for fabric.p4.
+ */
+public class FabricPortStatisticsDiscovery extends PortStatisticsDiscoveryImpl {
+
+    /**
+     * Returns the ID of the ingress port counter.
+     *
+     * @return counter ID
+     */
+    @Override
+    public PiCounterId ingressCounterId() {
+        return FabricConstants.FABRIC_INGRESS_PORT_COUNTERS_CONTROL_INGRESS_PORT_COUNTER;
+    }
+
+    /**
+     * Returns the ID of the egress port counter.
+     *
+     * @return counter ID
+     */
+    @Override
+    public PiCounterId egressCounterId() {
+        return FabricConstants.FABRIC_INGRESS_PORT_COUNTERS_CONTROL_EGRESS_PORT_COUNTER;
+    }
+}
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricTreatmentInterpreter.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricTreatmentInterpreter.java
new file mode 100644
index 0000000..78ef9a7
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricTreatmentInterpreter.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.pipelines.fabric.impl.behaviour;
+
+import com.google.common.collect.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.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.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 java.util.List;
+import java.util.stream.Collectors;
+
+import static java.lang.String.format;
+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_POP;
+import static org.onosproject.pipelines.fabric.impl.behaviour.FabricUtils.instruction;
+import static org.onosproject.pipelines.fabric.impl.behaviour.FabricUtils.l2Instruction;
+import static org.onosproject.pipelines.fabric.impl.behaviour.FabricUtils.l2Instructions;
+
+/**
+ * Treatment translation logic.
+ */
+final class FabricTreatmentInterpreter {
+
+    private final FabricCapabilities capabilities;
+    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();
+
+
+    FabricTreatmentInterpreter(FabricCapabilities capabilities) {
+        this.capabilities = capabilities;
+    }
+
+    static PiAction mapFilteringTreatment(TrafficTreatment treatment, PiTableId tableId)
+            throws PiInterpreterException {
+
+        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);
+        }
+
+        // VLAN_POP action is equivalent to the permit action (VLANs pop is done anyway)
+        if (isNoAction(treatment) || isFilteringPopAction(treatment)) {
+            // Permit action if table is ingress_port_vlan;
+            return nop(tableId);
+        }
+
+        final ModVlanIdInstruction setVlanInst = (ModVlanIdInstruction) l2InstructionOrFail(
+                treatment, VLAN_ID, tableId);
+        return PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_FILTERING_PERMIT_WITH_INTERNAL_VLAN)
+                .withParameter(new PiActionParam(
+                        FabricConstants.VLAN_ID, setVlanInst.vlanId().toShort()))
+                .build();
+    }
+
+
+    static PiAction mapForwardingTreatment(TrafficTreatment treatment, PiTableId tableId)
+            throws PiInterpreterException {
+        if (isNoAction(treatment)) {
+            return nop(tableId);
+        }
+        treatmentException(
+                tableId, treatment,
+                "supports mapping only for empty/no-action treatments");
+        return null;
+    }
+
+    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);
+        } else if (tableId == FabricConstants.FABRIC_INGRESS_NEXT_XCONNECT) {
+            return mapNextXconnect(treatment, tableId);
+        }
+        throw new PiInterpreterException(format(
+                "Treatment mapping not supported for table '%s'", tableId));
+    }
+
+    private PiAction mapNextVlanTreatment(TrafficTreatment treatment, PiTableId tableId)
+            throws PiInterpreterException {
+        final List<ModVlanIdInstruction> modVlanIdInst = l2InstructionsOrFail(treatment, VLAN_ID, tableId)
+                .stream().map(i -> (ModVlanIdInstruction) i).collect(Collectors.toList());
+        if (modVlanIdInst.size() == 1) {
+            return PiAction.builder().withId(FabricConstants.FABRIC_INGRESS_NEXT_SET_VLAN)
+                    .withParameter(new PiActionParam(
+                            FabricConstants.VLAN_ID,
+                            modVlanIdInst.get(0).vlanId().toShort()))
+                    .build();
+        }
+        if (modVlanIdInst.size() == 2 && capabilities.supportDoubleVlanTerm()) {
+            return PiAction.builder()
+                    .withId(FabricConstants.FABRIC_INGRESS_NEXT_SET_DOUBLE_VLAN)
+                    .withParameter(new PiActionParam(
+                            FabricConstants.INNER_VLAN_ID,
+                            modVlanIdInst.get(0).vlanId().toShort()))
+                    .withParameter(new PiActionParam(
+                            FabricConstants.OUTER_VLAN_ID,
+                            modVlanIdInst.get(1).vlanId().toShort()))
+                    .build();
+        }
+        throw new PiInterpreterException("Too many VLAN instructions");
+    }
+
+    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();
+        }
+    }
+
+    private static PiAction mapNextXconnect(
+            TrafficTreatment treatment, PiTableId tableId)
+            throws PiInterpreterException {
+        final PortNumber outPort = ((OutputInstruction) instructionOrFail(
+                treatment, OUTPUT, tableId)).port();
+        return PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_NEXT_OUTPUT_XCONNECT)
+                .withParameter(new PiActionParam(
+                        FabricConstants.PORT_NUM, outPort.toLong()))
+                .build();
+    }
+
+    static PiAction mapAclTreatment(TrafficTreatment treatment, PiTableId tableId)
+            throws PiInterpreterException {
+        if (isNoAction(treatment)) {
+            return nop(tableId);
+        }
+        treatmentException(
+                tableId, treatment,
+                "unsupported treatment");
+
+        // This function will never return null
+        return null;
+    }
+
+
+    static PiAction mapEgressNextTreatment(
+            TrafficTreatment treatment, PiTableId tableId)
+            throws PiInterpreterException {
+        l2InstructionOrFail(treatment, VLAN_POP, tableId);
+        return PiAction.builder()
+                .withId(FabricConstants.FABRIC_EGRESS_EGRESS_NEXT_POP_VLAN)
+                .build();
+
+    }
+
+    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 boolean isFilteringPopAction(TrafficTreatment treatment) {
+        return l2Instruction(treatment, VLAN_POP) != null;
+    }
+
+    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 List<L2ModificationInstruction> l2InstructionsOrFail(
+            TrafficTreatment treatment,
+            L2ModificationInstruction.L2SubType subType, PiTableId tableId)
+            throws PiInterpreterException {
+        final List<L2ModificationInstruction> inst = l2Instructions(treatment, subType);
+        if (inst == null || inst.isEmpty()) {
+            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/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricUtils.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricUtils.java
new file mode 100644
index 0000000..655f88d
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/FabricUtils.java
@@ -0,0 +1,107 @@
+/*
+ * 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.impl.behaviour;
+
+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.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.lang.String.format;
+
+/**
+ * Utility class with methods common to fabric pipeconf operations.
+ */
+public final class FabricUtils {
+
+    private FabricUtils() {
+        // Hides constructor.
+    }
+
+    public static Criterion criterion(Collection<Criterion> criteria, Criterion.Type type) {
+        return criteria.stream()
+                .filter(c -> c.type().equals(type))
+                .findFirst().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 List<L2ModificationInstruction> l2Instructions(
+            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))
+                .collect(Collectors.toList());
+    }
+
+    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/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/package-info.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/package-info.java
new file mode 100644
index 0000000..75586a1
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Fabric pipeconf behaviour implementations.
+ */
+package org.onosproject.pipelines.fabric.impl.behaviour;
\ No newline at end of file
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/AbstractObjectiveTranslator.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/AbstractObjectiveTranslator.java
new file mode 100644
index 0000000..5b9671f
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/AbstractObjectiveTranslator.java
@@ -0,0 +1,107 @@
+/*
+ * 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.impl.behaviour.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.impl.behaviour.FabricCapabilities;
+import org.onosproject.pipelines.fabric.impl.behaviour.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;
+
+    AbstractObjectiveTranslator(DeviceId deviceId, FabricCapabilities capabilities) {
+        this.deviceId = checkNotNull(deviceId);
+        this.capabilities = checkNotNull(capabilities);
+        this.interpreter = new FabricInterpreter(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/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/Commons.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/Commons.java
new file mode 100644
index 0000000..b288eb4
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/Commons.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.impl.behaviour.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 Commons {
+
+    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 Commons() {
+        // hides constructor.
+    }
+}
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/FabricPipeliner.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/FabricPipeliner.java
new file mode 100644
index 0000000..eac2116
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/FabricPipeliner.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.pipelines.fabric.impl.behaviour.pipeliner;
+
+import com.google.common.collect.ImmutableList;
+import org.onlab.util.KryoNamespace;
+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.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleOperations;
+import org.onosproject.net.flow.FlowRuleService;
+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.GroupService;
+import org.onosproject.pipelines.fabric.impl.behaviour.AbstractFabricHandlerBehavior;
+import org.onosproject.pipelines.fabric.impl.behaviour.FabricCapabilities;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.slf4j.Logger;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static java.lang.String.format;
+import static org.onosproject.pipelines.fabric.impl.behaviour.FabricUtils.outputPort;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * 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 AbstractFabricHandlerBehavior
+        implements Pipeliner {
+
+    private static final Logger log = getLogger(FabricPipeliner.class);
+
+    protected static final KryoNamespace KRYO = new KryoNamespace.Builder()
+            .register(KryoNamespaces.API)
+            .register(FabricNextGroup.class)
+            .build("FabricPipeliner");
+
+    protected DeviceId deviceId;
+    protected FlowRuleService flowRuleService;
+    protected GroupService groupService;
+    protected FlowObjectiveStore flowObjectiveStore;
+
+    private FilteringObjectiveTranslator filteringTranslator;
+    private ForwardingObjectiveTranslator forwardingTranslator;
+    private NextObjectiveTranslator nextTranslator;
+
+    private final ExecutorService callbackExecutor = SharedExecutors.getPoolThreadExecutor();
+
+    /**
+     * Creates a new instance of this behavior with the given capabilities.
+     *
+     * @param capabilities capabilities
+     */
+    public FabricPipeliner(FabricCapabilities capabilities) {
+        super(capabilities);
+    }
+
+    /**
+     * Create a new instance of this behaviour. Used by the abstract projectable
+     * model (i.e., {@link org.onosproject.net.Device#as(Class)}.
+     */
+    public FabricPipeliner() {
+        super();
+    }
+
+    @Override
+    public void init(DeviceId deviceId, PipelinerContext context) {
+        this.deviceId = deviceId;
+        this.flowRuleService = context.directory().get(FlowRuleService.class);
+        this.groupService = context.directory().get(GroupService.class);
+        this.flowObjectiveStore = context.directory().get(FlowObjectiveStore.class);
+        this.filteringTranslator = new FilteringObjectiveTranslator(deviceId, capabilities);
+        this.forwardingTranslator = new ForwardingObjectiveTranslator(deviceId, capabilities);
+        this.nextTranslator = new NextObjectiveTranslator(deviceId, capabilities);
+    }
+
+    @Override
+    public void filter(FilteringObjective obj) {
+        final ObjectiveTranslation result = filteringTranslator.translate(obj);
+        handleResult(obj, result);
+    }
+
+    @Override
+    public void forward(ForwardingObjective obj) {
+        final ObjectiveTranslation result = forwardingTranslator.translate(obj);
+        handleResult(obj, result);
+    }
+
+    @Override
+    public void next(NextObjective obj) {
+        if (obj.op() == Objective.Operation.VERIFY) {
+            // TODO: support VERIFY operation
+            log.debug("VERIFY operation not yet supported for NextObjective, will return success");
+            success(obj);
+            return;
+        }
+
+        if (obj.op() == Objective.Operation.MODIFY) {
+            // TODO: support MODIFY operation
+            log.warn("MODIFY operation not yet supported for NextObjective, will return failure :(");
+            fail(obj, ObjectiveError.UNSUPPORTED);
+            return;
+        }
+
+        final ObjectiveTranslation result = nextTranslator.translate(obj);
+        handleResult(obj, result);
+    }
+
+    @Override
+    public List<String> getNextMappings(NextGroup nextGroup) {
+        final FabricNextGroup fabricNextGroup = KRYO.deserialize(nextGroup.data());
+        return fabricNextGroup.nextMappings().stream()
+                .map(m -> format("%s -> %s", fabricNextGroup.type(), m))
+                .collect(Collectors.toList());
+    }
+
+    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 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;
+        }
+        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.build());
+    }
+
+    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()));
+                break;
+            case ADD_TO_EXISTING:
+                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())
+                );
+                break;
+            default:
+                log.warn("Unsupported Objective operation {}", objective.op());
+        }
+    }
+
+    private void fail(Objective objective, ObjectiveError error) {
+        CompletableFuture.runAsync(
+                () -> objective.context().ifPresent(
+                        ctx -> ctx.onError(objective, error)), callbackExecutor);
+
+    }
+
+
+    private void success(Objective objective) {
+        CompletableFuture.runAsync(
+                () -> objective.context().ifPresent(
+                        ctx -> ctx.onSuccess(objective)), callbackExecutor);
+    }
+
+    private void removeNextGroup(NextObjective obj) {
+        final NextGroup removed = flowObjectiveStore.removeNextGroup(obj.id());
+        if (removed == null) {
+            log.debug("NextGroup {} was not found in FlowObjectiveStore");
+        }
+    }
+
+    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("Unknown NextTreatment type '{}'", n.type());
+                return "???";
+        }
+    }
+
+    /**
+     * NextGroup implementation.
+     */
+    private static class FabricNextGroup implements NextGroup {
+
+        private final NextObjective.Type type;
+        private final List<String> nextMappings;
+
+        FabricNextGroup(NextObjective.Type type, List<String> nextMappings) {
+            this.type = type;
+            this.nextMappings = ImmutableList.copyOf(nextMappings);
+        }
+
+        NextObjective.Type type() {
+            return type;
+        }
+
+        Collection<String> nextMappings() {
+            return nextMappings;
+        }
+
+        @Override
+        public byte[] data() {
+            return KRYO.serialize(this);
+        }
+    }
+}
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/FabricPipelinerException.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/FabricPipelinerException.java
new file mode 100644
index 0000000..6755e75
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/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.impl.behaviour.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/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/FilteringObjectiveTranslator.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/FilteringObjectiveTranslator.java
new file mode 100644
index 0000000..d682d8c
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/FilteringObjectiveTranslator.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.pipelines.fabric.impl.behaviour.pipeliner;
+
+import com.google.common.collect.Lists;
+import org.onlab.packet.EthType;
+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.impl.behaviour.FabricCapabilities;
+import org.onosproject.pipelines.fabric.impl.behaviour.FabricConstants;
+
+import java.util.Collection;
+import java.util.List;
+
+import static java.lang.String.format;
+import static org.onosproject.pipelines.fabric.impl.behaviour.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();
+
+
+    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 outerVlan = (VlanIdCriterion) criterion(
+                obj.conditions(), Criterion.Type.VLAN_VID);
+        final VlanIdCriterion innerVlan = (VlanIdCriterion) criterion(
+                obj.conditions(), Criterion.Type.INNER_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, outerVlan, innerVlan, resultBuilder);
+        fwdClassifierRules(obj, inPort, ethDst, ethDstMasked, resultBuilder);
+
+        return resultBuilder.build();
+    }
+
+    private void ingressPortVlanRule(
+            FilteringObjective obj,
+            Criterion inPortCriterion,
+            VlanIdCriterion outerVlanCriterion,
+            VlanIdCriterion innerVlanCriterion,
+            ObjectiveTranslation.Builder resultBuilder)
+            throws FabricPipelinerException {
+
+        final boolean outerVlanValid = outerVlanCriterion != null
+                && !outerVlanCriterion.vlanId().equals(VlanId.NONE);
+        final boolean innerVlanValid = innerVlanCriterion != null
+                && !innerVlanCriterion.vlanId().equals(VlanId.NONE);
+
+        final PiCriterion piCriterion = PiCriterion.builder()
+                .matchExact(FabricConstants.HDR_VLAN_IS_VALID, outerVlanValid ? ONE : ZERO)
+                .build();
+
+        final TrafficSelector.Builder selector = DefaultTrafficSelector.builder()
+                .add(inPortCriterion)
+                .add(piCriterion);
+        if (outerVlanValid) {
+            selector.add(outerVlanCriterion);
+        }
+        if (innerVlanValid) {
+            selector.add(innerVlanCriterion);
+        }
+
+        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)
+                .matchPi(mapEthTypeFwdClassifier(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();
+
+    }
+
+    static PiCriterion mapEthTypeFwdClassifier(short ethType) {
+        // Map the Ethernet type to the validity bits of the fabric pipeline
+        switch (EthType.EtherType.lookup(ethType)) {
+            case IPV4: {
+                return PiCriterion.builder()
+                        .matchExact(FabricConstants.HDR_IS_IPV4, ONE)
+                        .matchExact(FabricConstants.HDR_IS_IPV6, ZERO)
+                        .matchExact(FabricConstants.HDR_IS_MPLS, ZERO)
+                        .build();
+            }
+            case IPV6: {
+                return PiCriterion.builder()
+                        .matchExact(FabricConstants.HDR_IS_IPV4, ZERO)
+                        .matchExact(FabricConstants.HDR_IS_IPV6, ONE)
+                        .matchExact(FabricConstants.HDR_IS_MPLS, ZERO)
+                        .build();
+            }
+            case MPLS_UNICAST: {
+                return PiCriterion.builder()
+                        .matchExact(FabricConstants.HDR_IS_IPV4, ZERO)
+                        .matchExact(FabricConstants.HDR_IS_IPV6, ZERO)
+                        .matchExact(FabricConstants.HDR_IS_MPLS, ONE)
+                        .build();
+            }
+            default: {
+                return PiCriterion.builder()
+                        .matchExact(FabricConstants.HDR_IS_IPV4, ZERO)
+                        .matchExact(FabricConstants.HDR_IS_IPV6, ZERO)
+                        .matchExact(FabricConstants.HDR_IS_MPLS, ZERO)
+                        .build();
+            }
+        }
+    }
+}
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/ForwardingFunctionType.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/ForwardingFunctionType.java
new file mode 100644
index 0000000..1e33fa5
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/ForwardingFunctionType.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.pipelines.fabric.impl.behaviour.pipeliner;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.onosproject.net.flow.criteria.Criterion;
+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;
+import static org.onosproject.net.flow.criteria.Criterion.Type.IPV4_DST;
+import static org.onosproject.net.flow.criteria.Criterion.Type.IPV6_DST;
+import static org.onosproject.net.flow.criteria.Criterion.Type.MPLS_BOS;
+import static org.onosproject.net.flow.criteria.Criterion.Type.MPLS_LABEL;
+import static org.onosproject.net.flow.criteria.Criterion.Type.VLAN_VID;
+import static org.onosproject.pipelines.fabric.impl.behaviour.pipeliner.Commons.MATCH_ETH_DST_NONE;
+import static org.onosproject.pipelines.fabric.impl.behaviour.pipeliner.Commons.MATCH_ETH_TYPE_IPV4;
+import static org.onosproject.pipelines.fabric.impl.behaviour.pipeliner.Commons.MATCH_ETH_TYPE_IPV6;
+import static org.onosproject.pipelines.fabric.impl.behaviour.pipeliner.Commons.MATCH_ETH_TYPE_MPLS;
+import static org.onosproject.pipelines.fabric.impl.behaviour.pipeliner.Commons.MATCH_MPLS_BOS_FALSE;
+import static org.onosproject.pipelines.fabric.impl.behaviour.pipeliner.Commons.MATCH_MPLS_BOS_TRUE;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * 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.
+     */
+    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.
+     */
+    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.
+     */
+    IPV4_ROUTING(
+            Sets.newHashSet(ETH_TYPE, IPV4_DST),
+            Lists.newArrayList(MATCH_ETH_TYPE_IPV4),
+            Collections.emptyList()),
+
+    /**
+     * IPv4 multicast.
+     */
+    IPV4_ROUTING_MULTICAST(
+            Sets.newHashSet(ETH_TYPE, VLAN_VID, IPV4_DST),
+            Lists.newArrayList(MATCH_ETH_TYPE_IPV4),
+            Collections.emptyList()),
+
+    /**
+     * IPv6 unicast.
+     */
+    IPV6_ROUTING(
+            Sets.newHashSet(ETH_TYPE, IPV6_DST),
+            Lists.newArrayList(MATCH_ETH_TYPE_IPV6),
+            Collections.emptyList()),
+
+    /**
+     * IPv6 multicast.
+     */
+    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.
+     */
+    UNKNOWN(
+            Collections.emptySet(),
+            Collections.emptyList(),
+            Collections.emptyList());
+
+    private static final Logger log = getLogger(ForwardingFunctionType.class);
+
+    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;
+
+    /**
+     * Creates a new FFT.
+     *
+     * @param expectedCriterionTypes expected criterion types
+     * @param matchCriteria          criterion instances to match
+     * @param mismatchCriteria       criterion instance not to be matched
+     */
+    ForwardingFunctionType(Set<Criterion.Type> expectedCriterionTypes,
+                           Collection<Criterion> matchCriteria,
+                           Collection<Criterion> mismatchCriteria) {
+        this(expectedCriterionTypes, matchCriteria, mismatchCriteria, null);
+    }
+
+    /**
+     * Creates a new alias FFT that if matched, should return the given original
+     * FFT.
+     *
+     * @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
+     */
+    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/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/ForwardingObjectiveTranslator.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/ForwardingObjectiveTranslator.java
new file mode 100644
index 0000000..1d7d5c0
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/ForwardingObjectiveTranslator.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.pipelines.fabric.impl.behaviour.pipeliner;
+
+import com.google.common.collect.ImmutableList;
+import 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.core.ApplicationId;
+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.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.Objective;
+import org.onosproject.net.flowobjective.ObjectiveError;
+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.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.impl.behaviour.FabricCapabilities;
+import org.onosproject.pipelines.fabric.impl.behaviour.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.net.group.DefaultGroupBucket.createCloneGroupBucket;
+import static org.onosproject.pipelines.fabric.impl.behaviour.FabricUtils.criterionNotNull;
+import static org.onosproject.pipelines.fabric.impl.behaviour.FabricUtils.outputPort;
+
+
+/**
+ * ObjectiveTranslator implementation ForwardingObjective.
+ */
+class ForwardingObjectiveTranslator
+        extends AbstractObjectiveTranslator<ForwardingObjective> {
+
+    //FIXME: Max number supported by PI
+    static final int CLONE_TO_CPU_ID = 511;
+    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 {
+        if (obj.nextId() == null && obj.treatment() != null) {
+            final TrafficTreatment treatment = obj.treatment();
+            final PortNumber outPort = outputPort(treatment);
+            if (outPort != null
+                    && outPort.equals(PortNumber.CONTROLLER)
+                    && treatment.allInstructions().size() == 1) {
+
+                final PiAction aclAction;
+                if (treatment.clearedDeferred()) {
+                    aclAction = PiAction.builder()
+                            .withId(FabricConstants.FABRIC_INGRESS_ACL_PUNT_TO_CPU)
+                            .build();
+                } else {
+                    // Action is SET_CLONE_SESSION_ID
+                    if (obj.op() == Objective.Operation.ADD) {
+                        // Action is ADD, create clone group
+                        final DefaultGroupDescription cloneGroup =
+                                createCloneGroup(obj.appId(),
+                                                 CLONE_TO_CPU_ID,
+                                                 outPort);
+                        resultBuilder.addGroup(cloneGroup);
+                    }
+                    aclAction = PiAction.builder()
+                            .withId(FabricConstants.FABRIC_INGRESS_ACL_SET_CLONE_SESSION_ID)
+                            .withParameter(new PiActionParam(
+                                    FabricConstants.CLONE_ID, CLONE_TO_CPU_ID))
+                            .build();
+                }
+                final TrafficTreatment piTreatment = DefaultTrafficTreatment.builder()
+                        .piTableAction(aclAction)
+                        .build();
+                resultBuilder.addFlowRule(flowRule(
+                        obj, FabricConstants.FABRIC_INGRESS_ACL_ACL, obj.selector(), piTreatment));
+                return;
+            }
+        }
+        resultBuilder.addFlowRule(flowRule(
+                obj, FabricConstants.FABRIC_INGRESS_ACL_ACL, obj.selector()));
+    }
+
+    private DefaultGroupDescription createCloneGroup(
+            ApplicationId appId,
+            int cloneSessionId,
+            PortNumber outPort) {
+        final GroupKey groupKey = new DefaultGroupKey(
+                FabricPipeliner.KRYO.serialize(cloneSessionId));
+
+        final List<GroupBucket> bucketList = ImmutableList.of(
+                createCloneGroupBucket(DefaultTrafficTreatment.builder()
+                                               .setOutput(outPort)
+                                               .build()));
+        final DefaultGroupDescription cloneGroup = new DefaultGroupDescription(
+                deviceId, GroupDescription.Type.CLONE,
+                new GroupBuckets(bucketList),
+                groupKey, cloneSessionId, appId);
+        return cloneGroup;
+    }
+
+    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/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/NextObjectiveTranslator.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/NextObjectiveTranslator.java
new file mode 100644
index 0000000..4e3304d
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/NextObjectiveTranslator.java
@@ -0,0 +1,506 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.pipelines.fabric.impl.behaviour.pipeliner;
+
+import com.google.common.collect.Lists;
+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.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.ModVlanIdInstruction;
+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.PiActionParam;
+import org.onosproject.net.pi.runtime.PiActionProfileGroupId;
+import org.onosproject.net.pi.runtime.PiGroupKey;
+import org.onosproject.pipelines.fabric.impl.behaviour.FabricCapabilities;
+import org.onosproject.pipelines.fabric.impl.behaviour.FabricConstants;
+import org.onosproject.pipelines.fabric.impl.behaviour.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_ID;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.VLAN_POP;
+import static org.onosproject.pipelines.fabric.impl.behaviour.FabricUtils.criterion;
+import static org.onosproject.pipelines.fabric.impl.behaviour.FabricUtils.l2Instruction;
+import static org.onosproject.pipelines.fabric.impl.behaviour.FabricUtils.l2Instructions;
+import static org.onosproject.pipelines.fabric.impl.behaviour.FabricUtils.outputPort;
+
+/**
+ * ObjectiveTranslator implementation for NextObjective.
+ */
+class NextObjectiveTranslator
+        extends AbstractObjectiveTranslator<NextObjective> {
+
+    private static final String XCONNECT = "xconnect";
+
+    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:
+                if (isXconnect(obj)) {
+                    xconnectNext(obj, resultBuilder);
+                } else {
+                    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 {
+        // We expect NextObjective treatments to contain one or two VLAN instructions.
+        // If two, this treatment should be mapped to an action for double-vlan push.
+        // In fabric.p4, mapping next IDs to VLAN IDs is done by a direct table (next_vlan),
+        // for this reason, we also make sure that all treatments in the NextObjective
+        // have exactly the same VLAN instructions, as they will be mapped to a single action
+
+        // Try to extract VLAN instructions in the treatment,
+        //  later we check if we support multiple VLAN termination.
+        final List<List<ModVlanIdInstruction>> vlanInstructions = defaultNextTreatments(
+                obj.nextTreatments(), false).stream()
+                .map(defaultNextTreatment ->
+                             l2Instructions(defaultNextTreatment.treatment(), VLAN_ID)
+                                     .stream().map(v -> (ModVlanIdInstruction) v)
+                                     .collect(Collectors.toList()))
+                .filter(l -> !l.isEmpty())
+                .collect(Collectors.toList());
+
+        final VlanIdCriterion vlanIdCriterion = obj.meta() == null ? null
+                : (VlanIdCriterion) criterion(obj.meta().criteria(), Criterion.Type.VLAN_VID);
+
+        final List<VlanId> vlanIdList;
+        if (vlanInstructions.isEmpty() && vlanIdCriterion == null) {
+            // No VLAN_ID to apply.
+            return;
+        }
+        if (!vlanInstructions.isEmpty()) {
+            // Give priority to what found in the instructions.
+            // Expect the same VLAN ID (or two VLAN IDs in the same order) for all instructions.
+            final Set<List<VlanId>> vlanIds = vlanInstructions.stream()
+                    .map(l -> l.stream().map(ModVlanIdInstruction::vlanId).collect(Collectors.toList()))
+                    .collect(Collectors.toSet());
+            if (obj.nextTreatments().size() != vlanInstructions.size() ||
+                    vlanIds.size() != 1) {
+                throw new FabricPipelinerException(
+                        "Inconsistent VLAN_ID instructions, cannot process " +
+                                "next_vlan rule. It is required that all " +
+                                "treatments have the same VLAN_ID instructions.");
+            }
+            vlanIdList = vlanIds.iterator().next();
+        } else {
+            // Use the value in meta.
+            // FIXME: there should be no need to generate a next_vlan rule for
+            //  the value found in meta. Meta describes the fields that were
+            //  expected to be matched in previous pipeline stages, i.e.
+            //  existing packet fields. But, for some reason, if we remove this
+            //  rule, traffic is not forwarded at spines. We might need to look
+            //  at the way default VLANs are handled in fabric.p4.
+            vlanIdList = List.of(vlanIdCriterion.vlanId());
+        }
+
+        final TrafficSelector selector = nextIdSelector(obj.id());
+        final TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
+        vlanIdList.stream().forEach(vlanId -> treatmentBuilder.setVlanId(vlanId));
+        final TrafficTreatment treatment = treatmentBuilder.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 = defaultNextTreatments(
+                obj.nextTreatments(), true);
+
+        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 = defaultNextTreatments(
+                obj.nextTreatments(), true).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(PiActionProfileGroupId.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) {
+        return nextIdSelectorBuilder(nextId).build();
+    }
+
+    private TrafficSelector.Builder nextIdSelectorBuilder(int nextId) {
+        final PiCriterion nextIdCriterion = PiCriterion.builder()
+                .matchExact(FabricConstants.HDR_NEXT_ID, nextId)
+                .build();
+        return DefaultTrafficSelector.builder()
+                .matchPi(nextIdCriterion);
+    }
+
+    private void xconnectNext(NextObjective obj, ObjectiveTranslation.Builder resultBuilder)
+            throws FabricPipelinerException {
+
+        final Collection<DefaultNextTreatment> defaultNextTreatments =
+                defaultNextTreatments(obj.nextTreatments(), true);
+
+        final List<PortNumber> outPorts = defaultNextTreatments.stream()
+                .map(DefaultNextTreatment::treatment)
+                .map(FabricUtils::outputPort)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+
+        if (outPorts.size() != 2) {
+            throw new FabricPipelinerException(format(
+                    "Handling XCONNECT with %d treatments (ports), but expected is 2",
+                    defaultNextTreatments.size()), ObjectiveError.UNSUPPORTED);
+        }
+
+        final PortNumber port1 = outPorts.get(0);
+        final PortNumber port2 = outPorts.get(1);
+        final TrafficSelector selector1 = nextIdSelectorBuilder(obj.id())
+                .matchInPort(port1)
+                .build();
+        final TrafficTreatment treatment1 = DefaultTrafficTreatment.builder()
+                .setOutput(port2)
+                .build();
+        final TrafficSelector selector2 = nextIdSelectorBuilder(obj.id())
+                .matchInPort(port2)
+                .build();
+        final TrafficTreatment treatment2 = DefaultTrafficTreatment.builder()
+                .setOutput(port1)
+                .build();
+
+        resultBuilder.addFlowRule(flowRule(
+                obj, FabricConstants.FABRIC_INGRESS_NEXT_XCONNECT,
+                selector1, treatment1));
+        resultBuilder.addFlowRule(flowRule(
+                obj, FabricConstants.FABRIC_INGRESS_NEXT_XCONNECT,
+                selector2, treatment2));
+
+    }
+
+    private void multicastNext(NextObjective obj,
+                               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 =
+                defaultNextTreatments(obj.nextTreatments(), true);
+        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 =
+                defaultNextTreatments(obj.nextTreatments(), true);
+        // 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());
+
+        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> defaultNextTreatments(
+            Collection<NextTreatment> nextTreatments, boolean strict)
+            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 (strict && !unsupportedNextTreatments.isEmpty()) {
+            throw new FabricPipelinerException(format(
+                    "Unsupported NextTreatments: %s",
+                    unsupportedNextTreatments));
+        }
+        return defaultNextTreatments;
+    }
+
+    private TrafficTreatment getFirstDefaultNextTreatmentIfAny(
+            Collection<NextTreatment> nextTreatments)
+            throws FabricPipelinerException {
+        final Collection<DefaultNextTreatment> nexts = defaultNextTreatments(nextTreatments, false);
+        return nexts.isEmpty() ? null : nexts.iterator().next().treatment();
+    }
+
+    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;
+    }
+
+    private boolean isXconnect(NextObjective obj) {
+        return obj.appId().name().contains(XCONNECT);
+    }
+}
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/ObjectiveTranslation.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/ObjectiveTranslation.java
new file mode 100644
index 0000000..31ff617
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/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.impl.behaviour.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/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/package-info.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/package-info.java
new file mode 100644
index 0000000..905c942
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Pipeliner implementation classes for fabric.p4.
+ */
+package org.onosproject.pipelines.fabric.impl.behaviour.pipeliner;
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/package-info.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/package-info.java
new file mode 100644
index 0000000..257d1a3
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Implementation classes for the fabric pipeconf.
+ */
+package org.onosproject.pipelines.fabric.impl;
\ No newline at end of file
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/service/package-info.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/service/package-info.java
new file mode 100644
index 0000000..c57b047
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/service/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Fabric pipeconf service implementations.
+ */
+package org.onosproject.pipelines.fabric.impl.service;