ONOS-7251 ONOS-7264 Support for clone to CPU action in fabric.p4

Clone to CPU is available only for packets processed via multicast
groups. Can be changed in the future when implementation for clone
session APIs is available in PI and P4 targets.

Also:
- compile "fabric-full" profile and generate constants from it
- use interpreter to map logical ports to data plane port IDs

Change-Id: I7db30c08dcf69ed9c870748cce8a797bbd5d6f78
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricConstants.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricConstants.java
index 86a4a11..fed205c 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricConstants.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricConstants.java
@@ -33,8 +33,12 @@
     }
 
     // Header field IDs
+    public static final PiMatchFieldId STANDARD_METADATA_EGRESS_PORT =
+            PiMatchFieldId.of("standard_metadata.egress_port");
     public static final PiMatchFieldId FABRIC_METADATA_L4_SRC_PORT =
             PiMatchFieldId.of("fabric_metadata.l4_src_port");
+    public static final PiMatchFieldId SPGW_META_S1U_SGW_ADDR =
+            PiMatchFieldId.of("spgw_meta.s1u_sgw_addr");
     public static final PiMatchFieldId HDR_IPV4_SRC_ADDR =
             PiMatchFieldId.of("hdr.ipv4.src_addr");
     public static final PiMatchFieldId HDR_VLAN_TAG_VLAN_ID =
@@ -47,18 +51,18 @@
             PiMatchFieldId.of("hdr.ethernet.src_addr");
     public static final PiMatchFieldId HDR_ICMP_ICMP_TYPE =
             PiMatchFieldId.of("hdr.icmp.icmp_type");
-    public static final PiMatchFieldId STANDARD_METADATA_EGRESS_PORT =
-            PiMatchFieldId.of("standard_metadata.egress_port");
     public static final PiMatchFieldId FABRIC_METADATA_NEXT_ID =
             PiMatchFieldId.of("fabric_metadata.next_id");
+    public static final PiMatchFieldId HDR_IPV4_DST_ADDR =
+            PiMatchFieldId.of("hdr.ipv4.dst_addr");
     public static final PiMatchFieldId FABRIC_METADATA_L4_DST_PORT =
             PiMatchFieldId.of("fabric_metadata.l4_dst_port");
     public static final PiMatchFieldId STANDARD_METADATA_INGRESS_PORT =
             PiMatchFieldId.of("standard_metadata.ingress_port");
     public static final PiMatchFieldId FABRIC_METADATA_ORIGINAL_ETHER_TYPE =
             PiMatchFieldId.of("fabric_metadata.original_ether_type");
-    public static final PiMatchFieldId HDR_IPV4_DST_ADDR =
-            PiMatchFieldId.of("hdr.ipv4.dst_addr");
+    public static final PiMatchFieldId IPV4_DST_ADDR =
+            PiMatchFieldId.of("ipv4.dst_addr");
     public static final PiMatchFieldId HDR_VLAN_TAG_IS_VALID =
             PiMatchFieldId.of("hdr.vlan_tag.is_valid");
     public static final PiMatchFieldId FABRIC_METADATA_IP_PROTO =
@@ -78,20 +82,28 @@
             PiTableId.of("FabricIngress.forwarding.mpls");
     public static final PiTableId FABRIC_INGRESS_NEXT_MULTICAST =
             PiTableId.of("FabricIngress.next.multicast");
-    public static final PiTableId FABRIC_INGRESS_FORWARDING_MULTICAST_V4 =
-            PiTableId.of("FabricIngress.forwarding.multicast_v4");
+    public static final PiTableId FABRIC_INGRESS_SPGW_INGRESS_UE_CDR_TABLE =
+            PiTableId.of("FabricIngress.spgw_ingress.ue_cdr_table");
     public static final PiTableId FABRIC_INGRESS_FORWARDING_MULTICAST_V6 =
             PiTableId.of("FabricIngress.forwarding.multicast_v6");
     public static final PiTableId FABRIC_INGRESS_FORWARDING_UNICAST_V4 =
             PiTableId.of("FabricIngress.forwarding.unicast_v4");
     public static final PiTableId FABRIC_INGRESS_FILTERING_FWD_CLASSIFIER =
             PiTableId.of("FabricIngress.filtering.fwd_classifier");
-    public static final PiTableId FABRIC_INGRESS_FORWARDING_BRIDGING =
-            PiTableId.of("FabricIngress.forwarding.bridging");
     public static final PiTableId FABRIC_INGRESS_NEXT_SIMPLE =
             PiTableId.of("FabricIngress.next.simple");
+    public static final PiTableId FABRIC_INGRESS_SPGW_INGRESS_UE_FILTER_TABLE =
+            PiTableId.of("FabricIngress.spgw_ingress.ue_filter_table");
+    public static final PiTableId FABRIC_INGRESS_FORWARDING_BRIDGING =
+            PiTableId.of("FabricIngress.forwarding.bridging");
+    public static final PiTableId FABRIC_INGRESS_SPGW_INGRESS_S1U_FILTER_TABLE =
+            PiTableId.of("FabricIngress.spgw_ingress.s1u_filter_table");
+    public static final PiTableId FABRIC_INGRESS_FORWARDING_MULTICAST_V4 =
+            PiTableId.of("FabricIngress.forwarding.multicast_v4");
     public static final PiTableId FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN =
             PiTableId.of("FabricIngress.filtering.ingress_port_vlan");
+    public static final PiTableId FABRIC_INGRESS_SPGW_INGRESS_DL_SESS_LOOKUP =
+            PiTableId.of("FabricIngress.spgw_ingress.dl_sess_lookup");
     public static final PiTableId FABRIC_INGRESS_FORWARDING_UNICAST_V6 =
             PiTableId.of("FabricIngress.forwarding.unicast_v6");
     public static final PiTableId FABRIC_INGRESS_NEXT_VLAN_META =
@@ -106,8 +118,8 @@
             PiCounterId.of("FabricIngress.forwarding.acl_counter");
     public static final PiCounterId FABRIC_INGRESS_NEXT_MULTICAST_COUNTER =
             PiCounterId.of("FabricIngress.next.multicast_counter");
-    public static final PiCounterId FABRIC_INGRESS_NEXT_VLAN_META_COUNTER =
-            PiCounterId.of("FabricIngress.next.vlan_meta_counter");
+    public static final PiCounterId FABRIC_INGRESS_FORWARDING_UNICAST_V6_COUNTER =
+            PiCounterId.of("FabricIngress.forwarding.unicast_v6_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 =
@@ -116,8 +128,10 @@
             PiCounterId.of("FabricIngress.forwarding.multicast_v6_counter");
     public static final PiCounterId FABRIC_INGRESS_FORWARDING_MULTICAST_V4_COUNTER =
             PiCounterId.of("FabricIngress.forwarding.multicast_v4_counter");
-    public static final PiCounterId FABRIC_INGRESS_FORWARDING_UNICAST_V6_COUNTER =
-            PiCounterId.of("FabricIngress.forwarding.unicast_v6_counter");
+    public static final PiCounterId FABRIC_INGRESS_SPGW_INGRESS_UE_COUNTER =
+            PiCounterId.of("FabricIngress.spgw_ingress.ue_counter");
+    public static final PiCounterId FABRIC_INGRESS_NEXT_VLAN_META_COUNTER =
+            PiCounterId.of("FabricIngress.next.vlan_meta_counter");
     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_FORWARDING_UNICAST_V4_COUNTER =
@@ -131,14 +145,20 @@
     public static final PiCounterId FABRIC_INGRESS_NEXT_HASHED_COUNTER =
             PiCounterId.of("FabricIngress.next.hashed_counter");
     // Action IDs
-    public static final PiActionId FABRIC_INGRESS_FORWARDING_SEND_TO_CONTROLLER =
-            PiActionId.of("FabricIngress.forwarding.send_to_controller");
+    public static final PiActionId FABRIC_INGRESS_SPGW_INGRESS_UPDATE_UE_CDR =
+            PiActionId.of("FabricIngress.spgw_ingress.update_ue_cdr");
     public static final PiActionId FABRIC_INGRESS_NEXT_MPLS_ROUTING_V6_SIMPLE =
             PiActionId.of("FabricIngress.next.mpls_routing_v6_simple");
+    public static final PiActionId FABRIC_INGRESS_NEXT_MPLS_ROUTING_V6_HASHED =
+            PiActionId.of("FabricIngress.next.mpls_routing_v6_hashed");
     public static final PiActionId FABRIC_INGRESS_FORWARDING_SET_NEXT_ID_BRIDGING =
             PiActionId.of("FabricIngress.forwarding.set_next_id_bridging");
+    public static final PiActionId FABRIC_INGRESS_FORWARDING_PUNT_TO_CPU =
+            PiActionId.of("FabricIngress.forwarding.punt_to_cpu");
     public static final PiActionId FABRIC_INGRESS_NEXT_SET_VLAN =
             PiActionId.of("FabricIngress.next.set_vlan");
+    public static final PiActionId FABRIC_EGRESS_SPGW_EGRESS_GTPU_ENCAP =
+            PiActionId.of("FabricEgress.spgw_egress.gtpu_encap");
     public static final PiActionId FABRIC_EGRESS_PKT_IO_EGRESS_POP_VLAN =
             PiActionId.of("FabricEgress.pkt_io_egress.pop_vlan");
     public static final PiActionId FABRIC_INGRESS_FILTERING_SET_VLAN =
@@ -147,12 +167,17 @@
             PiActionId.of("FabricIngress.next.l3_routing_simple");
     public static final PiActionId FABRIC_INGRESS_NEXT_SET_MCAST_GROUP =
             PiActionId.of("FabricIngress.next.set_mcast_group");
+    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_INGRESS_FILTERING_PUSH_INTERNAL_VLAN =
             PiActionId.of("FabricIngress.filtering.push_internal_vlan");
-    public static final PiActionId FABRIC_INGRESS_NEXT_MPLS_ROUTING_V6_HASHED =
-            PiActionId.of("FabricIngress.next.mpls_routing_v6_hashed");
+    public static final PiActionId FABRIC_INGRESS_FORWARDING_CLONE_TO_CPU =
+            PiActionId.of("FabricIngress.forwarding.clone_to_cpu");
+    public static final PiActionId FABRIC_INGRESS_SPGW_INGRESS_GTPU_DECAP =
+            PiActionId.of("FabricIngress.spgw_ingress.gtpu_decap");
     public static final PiActionId FABRIC_INGRESS_FORWARDING_POP_MPLS_AND_NEXT =
             PiActionId.of("FabricIngress.forwarding.pop_mpls_and_next");
+    public static final PiActionId DROP_NOW = PiActionId.of("drop_now");
     public static final PiActionId FABRIC_INGRESS_NEXT_L3_ROUTING_HASHED =
             PiActionId.of("FabricIngress.next.l3_routing_hashed");
     public static final PiActionId FABRIC_EGRESS_EGRESS_NEXT_POP_VLAN =
@@ -187,8 +212,13 @@
             PiActionId.of("FabricIngress.forwarding.set_next_id_acl");
     // Action Param IDs
     public static final PiActionParamId DMAC = PiActionParamId.of("dmac");
+    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 S1U_SGW_ADDR =
+            PiActionParamId.of("s1u_sgw_addr");
     public static final PiActionParamId LABEL = PiActionParamId.of("label");
     public static final PiActionParamId SMAC = PiActionParamId.of("smac");
     public static final PiActionParamId GID = PiActionParamId.of("gid");
@@ -205,5 +235,4 @@
             PiControlMetadataId.of("ingress_port");
     public static final PiControlMetadataId EGRESS_PORT =
             PiControlMetadataId.of("egress_port");
-    public static final int PORT_BITWIDTH = 9;
-}
\ No newline at end of file
+}
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricInterpreter.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricInterpreter.java
index 8fbff96..5381e36 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricInterpreter.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricInterpreter.java
@@ -60,6 +60,9 @@
  */
 public class FabricInterpreter extends AbstractHandlerBehaviour
         implements PiPipelineInterpreter {
+
+    public static final int PORT_BITWIDTH = 9;
+
     private static final ImmutableBiMap<Integer, PiTableId> TABLE_ID_MAP =
             ImmutableBiMap.<Integer, PiTableId>builder()
                     // Filtering
@@ -191,7 +194,7 @@
         try {
             return PiControlMetadata.builder()
                     .withId(FabricConstants.EGRESS_PORT)
-                    .withValue(copyFrom(portNumber).fit(FabricConstants.PORT_BITWIDTH))
+                    .withValue(copyFrom(portNumber).fit(PORT_BITWIDTH))
                     .build();
         } catch (ImmutableByteSequence.ByteSequenceTrimException e) {
             throw new PiInterpreterException(format(
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricTreatmentInterpreter.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricTreatmentInterpreter.java
index 921b5f4..79b3f48 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricTreatmentInterpreter.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricTreatmentInterpreter.java
@@ -42,15 +42,16 @@
 
 import static java.lang.String.format;
 import static org.onosproject.net.flow.instructions.Instruction.Type.L2MODIFICATION;
-import static org.onosproject.net.flow.instructions.Instruction.Type.OUTPUT;
 import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.VLAN_ID;
 import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.VLAN_PUSH;
+import static org.onosproject.pipelines.fabric.FabricUtils.getOutputPort;
 import static org.slf4j.LoggerFactory.getLogger;
 
 
 final class FabricTreatmentInterpreter {
     private static final Logger log = getLogger(FabricTreatmentInterpreter.class);
-    private static final String INVALID_TREATMENT = "Invalid treatment for %s block: %s";
+    private static final String INVALID_TREATMENT = "Invalid treatment for %s block [%s]";
+    private static final String INVALID_TREATMENT_WITH_EXP = "Invalid treatment for %s block: %s [%s]";
     private static final PiAction NOP = PiAction.builder()
             .withId(FabricConstants.NOP)
             .build();
@@ -142,26 +143,22 @@
         if (treatment.equals(DefaultTrafficTreatment.emptyTreatment())) {
             return null;
         }
-        List<Instruction> insts = treatment.allInstructions();
-        OutputInstruction outInst = insts.stream()
-                .filter(inst -> inst.type() == OUTPUT)
-                .map(inst -> (OutputInstruction) inst)
-                .findFirst()
-                .orElse(null);
-
-        if (outInst == null) {
-            throw new PiInterpreterException(format(INVALID_TREATMENT, "forwarding", treatment));
+        PortNumber outPort = getOutputPort(treatment);
+        if (outPort == null
+                || !outPort.equals(PortNumber.CONTROLLER)
+                || treatment.allInstructions().size() > 1) {
+            throw new PiInterpreterException(
+                    format(INVALID_TREATMENT_WITH_EXP,
+                           "forwarding", "supports only punt/clone to CPU actions",
+                           treatment));
         }
 
-        PortNumber portNumber = outInst.port();
-        if (!portNumber.equals(PortNumber.CONTROLLER)) {
-            throw new PiInterpreterException(format("Unsupported port number %s," +
-                                                            "supports punt action only",
-                                                    portNumber));
-        }
+        final PiActionId actionId = treatment.clearedDeferred()
+                ? FabricConstants.FABRIC_INGRESS_FORWARDING_PUNT_TO_CPU
+                : FabricConstants.FABRIC_INGRESS_FORWARDING_CLONE_TO_CPU;
 
         return PiAction.builder()
-                .withId(FabricConstants.FABRIC_INGRESS_FORWARDING_SEND_TO_CONTROLLER)
+                .withId(actionId)
                 .build();
     }
 
@@ -214,7 +211,8 @@
                             modMplsInst = (ModMplsLabelInstruction) l2Inst;
                             break;
                         default:
-                            log.warn("Unsupported l2 instruction sub type: {}", l2Inst.subtype());
+                            log.warn("Unsupported l2 instruction sub type {} [table={}, {}]",
+                                     l2Inst.subtype(), tableId, treatment);
                             break;
                     }
                     break;
@@ -222,7 +220,8 @@
                     outInst = (OutputInstruction) inst;
                     break;
                 default:
-                    log.warn("Unsupported instruction sub type: {}", inst.type());
+                    log.warn("Unsupported instruction sub type {} [table={}, {}]",
+                             inst.type(), tableId, treatment);
                     break;
             }
         }
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricUtils.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricUtils.java
new file mode 100644
index 0000000..b644613
--- /dev/null
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricUtils.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.pipelines.fabric;
+
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+
+import java.util.Optional;
+
+/**
+ * Utility class for fabric pipeliner.
+ */
+public final class FabricUtils {
+
+    private FabricUtils() {
+        // Hides constructor.
+    }
+
+    public static Optional<Instructions.OutputInstruction> getOutputInstruction(TrafficTreatment treatment) {
+        return treatment.allInstructions()
+                .stream()
+                .filter(inst -> inst.type() == Instruction.Type.OUTPUT)
+                .map(inst -> (Instructions.OutputInstruction) inst)
+                .findFirst();
+    }
+
+    public static PortNumber getOutputPort(TrafficTreatment treatment) {
+        return getOutputInstruction(treatment)
+                .map(Instructions.OutputInstruction::port)
+                .orElse(null);
+    }
+}
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/PipeconfLoader.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/PipeconfLoader.java
index 4ed38b5..72614c2 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/PipeconfLoader.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/PipeconfLoader.java
@@ -21,6 +21,7 @@
 import org.apache.felix.scr.annotations.Deactivate;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onosproject.net.PortNumber;
 import org.onosproject.net.behaviour.Pipeliner;
 import org.onosproject.net.device.PortStatisticsDiscovery;
 import org.onosproject.net.pi.model.DefaultPiPipeconf;
@@ -41,6 +42,7 @@
 import java.net.URL;
 import java.util.Collection;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.stream.Collectors;
 
 import static java.lang.String.format;
@@ -76,6 +78,8 @@
     private static final String TOFINO_BIN = "tofino.bin";
     private static final String TOFINO_CTX_JSON = "context.json";
 
+    private static final int BMV2_CPU_PORT = 255;
+
     private static final Collection<PiPipeconf> PIPECONFS = buildAllPipeconf();
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
@@ -144,7 +148,8 @@
         if (bmv2JsonUrl == null || p4InfoUrl == null) {
             throw new FileNotFoundException();
         }
-        return basePipeconfBuilder(profile, platform, p4InfoUrl)
+        return basePipeconfBuilder(
+                profile, platform, p4InfoUrl, Bmv2FabricInterpreter.class)
                 .addExtension(ExtensionType.BMV2_JSON, bmv2JsonUrl)
                 .build();
     }
@@ -160,14 +165,16 @@
         if (tofinoBinUrl == null || contextJsonUrl == null || p4InfoUrl == null) {
             throw new FileNotFoundException();
         }
-        return basePipeconfBuilder(profile, platform, p4InfoUrl)
+        return basePipeconfBuilder(
+                profile, platform, p4InfoUrl, FabricInterpreter.class)
                 .addExtension(ExtensionType.TOFINO_BIN, tofinoBinUrl)
                 .addExtension(ExtensionType.TOFINO_CONTEXT_JSON, contextJsonUrl)
                 .build();
     }
 
     private static DefaultPiPipeconf.Builder basePipeconfBuilder(
-            String profile, String platform, URL p4InfoUrl) {
+            String profile, String platform, URL p4InfoUrl,
+            Class<? extends FabricInterpreter> interpreterClass) {
         final String pipeconfId = platform.equals(DEFAULT_PLATFORM)
                 // Omit platform if default, e.g. with BMv2 pipeconf
                 ? format("%s.%s", BASE_PIPECONF_ID, profile)
@@ -177,7 +184,7 @@
                 .withId(new PiPipeconfId(pipeconfId))
                 .withPipelineModel(model)
                 .addBehaviour(PiPipelineInterpreter.class,
-                              FabricInterpreter.class)
+                              interpreterClass)
                 .addBehaviour(Pipeliner.class,
                               FabricPipeliner.class)
                 .addBehaviour(PortStatisticsDiscovery.class,
@@ -192,4 +199,16 @@
             throw new IllegalStateException(e);
         }
     }
+
+    // TODO: define interpreters with logical port mapping for Tofino platforms.
+    public static class Bmv2FabricInterpreter extends FabricInterpreter {
+        @Override
+        public Optional<Integer> mapLogicalPortNumber(PortNumber port) {
+            if (port.equals(PortNumber.CONTROLLER)) {
+                return Optional.of(BMV2_CPU_PORT);
+            } else {
+                return Optional.empty();
+            }
+        }
+    }
 }
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipeliner.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipeliner.java
index 2ee66aa..e5207eb 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipeliner.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipeliner.java
@@ -29,7 +29,6 @@
 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.Instructions.OutputInstruction;
 import org.onosproject.net.flow.instructions.L2ModificationInstruction;
 import org.onosproject.net.flowobjective.DefaultNextObjective;
 import org.onosproject.net.flowobjective.NextObjective;
@@ -47,12 +46,14 @@
 import org.onosproject.net.pi.runtime.PiActionParam;
 import org.onosproject.net.pi.runtime.PiGroupKey;
 import org.onosproject.pipelines.fabric.FabricConstants;
+import org.onosproject.pipelines.fabric.FabricUtils;
 import org.slf4j.Logger;
 
 import java.util.List;
 import java.util.Optional;
 import java.util.stream.Collectors;
 
+import static org.onosproject.pipelines.fabric.FabricUtils.getOutputPort;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -160,20 +161,6 @@
         }
     }
 
-    private Optional<OutputInstruction> getOutputInstruction(TrafficTreatment treatment) {
-        return treatment.allInstructions()
-                .stream()
-                .filter(inst -> inst.type() == Instruction.Type.OUTPUT)
-                .map(inst -> (OutputInstruction) inst)
-                .findFirst();
-    }
-
-    private PortNumber getOutputPort(TrafficTreatment treatment) {
-        return getOutputInstruction(treatment)
-                .map(OutputInstruction::port)
-                .orElse(null);
-    }
-
     private boolean includesPopVlanInst(TrafficTreatment treatment) {
         return treatment.allInstructions()
                 .stream()
@@ -355,7 +342,7 @@
 
     private GroupDescription getAllGroup(NextObjective next) {
         final List<GroupBucket> bucketList = next.next().stream()
-                .map(this::getOutputInstruction)
+                .map(FabricUtils::getOutputInstruction)
                 .filter(Optional::isPresent)
                 .map(Optional::get)
                 .map(i -> DefaultTrafficTreatment.builder().add(i).build())
@@ -364,12 +351,22 @@
 
         if (bucketList.size() != next.next().size()) {
             log.warn("Got BROADCAST NextObjective with {} treatments but " +
-                             "only {} have OUTPUT instructions, cannot " +
+                             "found only {} OUTPUT instructions, cannot " +
                              "translate to ALL groups",
                      next.next().size(), bucketList.size());
             return null;
         }
 
+        // FIXME: remove once support for clone sessions is available
+        // Right now we add a CPU port to all multicast groups. The egress
+        // pipeline is expected to drop replicated packets to the CPU if a clone
+        // was  not requested in the ingress pipeline.
+        bucketList.add(
+                DefaultGroupBucket.createAllGroupBucket(
+                        DefaultTrafficTreatment.builder()
+                                .setOutput(PortNumber.CONTROLLER)
+                                .build()));
+
         final int groupId = next.id();
         final GroupBuckets buckets = new GroupBuckets(bucketList);
         // Used DefaultGroupKey instead of PiGroupKey