[CORD-1108] Refactoring OFDPA Group Handler

Create package "ofdpa" under pipeline package
Move helper functions and classes to OfdpaGroupHandlerUtility

Change-Id: I47e42f2c8afc9088ed684cd6a087233a82c452f6
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/CpqdOfdpa2GroupHandler.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/CpqdOfdpa2GroupHandler.java
similarity index 91%
rename from drivers/default/src/main/java/org/onosproject/driver/pipeline/CpqdOfdpa2GroupHandler.java
rename to drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/CpqdOfdpa2GroupHandler.java
index 8c07dea..5c5fd1c 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/CpqdOfdpa2GroupHandler.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/CpqdOfdpa2GroupHandler.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.onosproject.driver.pipeline;
+package org.onosproject.driver.pipeline.ofdpa;
 
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
@@ -43,8 +43,7 @@
 import java.util.Deque;
 import java.util.List;
 
-import static org.onosproject.driver.pipeline.Ofdpa2GroupHandler.OfdpaMplsGroupSubType.MPLS_ECMP;
-import static org.onosproject.driver.pipeline.Ofdpa2Pipeline.isNotMplsBos;
+import static org.onosproject.driver.pipeline.ofdpa.OfdpaGroupHandlerUtility.*;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -55,8 +54,8 @@
 
     @Override
     protected GroupInfo createL2L3Chain(TrafficTreatment treatment, int nextId,
-                                      ApplicationId appId, boolean mpls,
-                                      TrafficSelector meta) {
+                                        ApplicationId appId, boolean mpls,
+                                        TrafficSelector meta) {
         // for the l2interface group, get vlan and port info
         // for the outer group, get the src/dst mac, and vlan info
         TrafficTreatment.Builder outerTtb = DefaultTrafficTreatment.builder();
@@ -189,7 +188,10 @@
         }
 
         // store l2groupkey with the groupChainElem for the outer-group that depends on it
-        GroupChainElem gce = new GroupChainElem(outerGrpDesc, 1, false);
+        GroupChainElem gce = new GroupChainElem(outerGrpDesc,
+                                                1,
+                                                false,
+                                                deviceId);
         updatePendingGroups(l2groupkey, gce);
 
         // create group description for the inner l2interfacegroup
@@ -210,11 +212,6 @@
         return new GroupInfo(l2groupDescription, outerGrpDesc);
     }
 
-    @Override
-    public boolean verifyHashedNextObjective(NextObjective nextObjective) {
-        return true;
-    }
-
     /**
      * In OFDPA2 we do not support the MPLS-ECMP, while we do in
      * CPQD implementation.
@@ -227,7 +224,7 @@
         // the transport of a VPWS. The necessary info are contained in the
         // meta selector. In particular we are looking for the case of BoS==False;
         TrafficSelector metaSelector = nextObjective.meta();
-        if (metaSelector != null && isNotMplsBos(metaSelector)) {
+        if (metaSelector != null && Ofdpa2Pipeline.isNotMplsBos(metaSelector)) {
             // storage for all group keys in the chain of groups created
             List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
             List<GroupInfo> unsentGroups = new ArrayList<>();
@@ -237,13 +234,13 @@
             for (GroupInfo gi : unsentGroups) {
                 // create ECMP bucket to point to the outer group
                 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
-                ttb.group(new GroupId(gi.getNextGroupDesc().givenGroupId()));
+                ttb.group(new GroupId(gi.nextGroupDesc().givenGroupId()));
                 GroupBucket sbucket = DefaultGroupBucket
                         .createSelectGroupBucket(ttb.build());
                 mplsEcmpGroupBuckets.add(sbucket);
             }
             int mplsEcmpIndex = getNextAvailableIndex();
-            int mplsEcmpGroupId = makeMplsForwardingGroupId(MPLS_ECMP, mplsEcmpIndex);
+            int mplsEcmpGroupId = makeMplsForwardingGroupId(OfdpaMplsGroupSubType.MPLS_ECMP, mplsEcmpIndex);
             GroupKey mplsEmpGroupKey = new DefaultGroupKey(
                     Ofdpa2Pipeline.appKryo.serialize(mplsEcmpIndex)
             );
@@ -257,7 +254,8 @@
             );
             GroupChainElem mplsEcmpGce = new GroupChainElem(mplsEcmpGroupDesc,
                                                             mplsEcmpGroupBuckets.size(),
-                                                            false);
+                                                            false,
+                                                            deviceId);
 
             // create objects for local and distributed storage
             allGroupKeys.forEach(gkeyChain -> gkeyChain.addFirst(mplsEmpGroupKey));
@@ -274,9 +272,9 @@
             // finally we are ready to send the innermost groups
             for (GroupInfo gi : unsentGroups) {
                 log.debug("Sending innermost group {} in group chain on device {} ",
-                          Integer.toHexString(gi.getInnerMostGroupDesc().givenGroupId()), deviceId);
-                updatePendingGroups(gi.getNextGroupDesc().appCookie(), mplsEcmpGce);
-                groupService.addGroup(gi.getInnerMostGroupDesc());
+                          Integer.toHexString(gi.innerMostGroupDesc().givenGroupId()), deviceId);
+                updatePendingGroups(gi.nextGroupDesc().appCookie(), mplsEcmpGce);
+                groupService.addGroup(gi.innerMostGroupDesc());
             }
             return;
         }
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/CpqdOfdpa2Pipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/CpqdOfdpa2Pipeline.java
similarity index 97%
rename from drivers/default/src/main/java/org/onosproject/driver/pipeline/CpqdOfdpa2Pipeline.java
rename to drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/CpqdOfdpa2Pipeline.java
index 7add4d0..377a0c7 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/CpqdOfdpa2Pipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/CpqdOfdpa2Pipeline.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.onosproject.driver.pipeline;
+package org.onosproject.driver.pipeline.ofdpa;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -71,7 +71,7 @@
 import static org.onlab.packet.IPv6.PROTOCOL_ICMP6;
 import static org.onlab.packet.MacAddress.BROADCAST;
 import static org.onlab.packet.MacAddress.NONE;
-import static org.onosproject.driver.pipeline.Ofdpa2GroupHandler.FOUR_BIT_MASK;
+import static org.onosproject.driver.pipeline.ofdpa.OfdpaGroupHandlerUtility.*;
 import static org.slf4j.LoggerFactory.getLogger;
 
 
@@ -193,8 +193,7 @@
             // NOTE: Emulating OFDPA behavior by popping off internal assigned
             //       VLAN before sending to controller
             if (supportPuntGroup() && vidCriterion.vlanId() == VlanId.NONE) {
-                GroupKey groupKey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(
-                        POP_VLAN_PUNT_GROUP_ID | (Objects.hash(deviceId) & FOUR_BIT_MASK)));
+                GroupKey groupKey = popVlanPuntGroupKey();
                 Group group = groupService.getGroup(deviceId, groupKey);
                 if (group != null) {
                     rules.add(buildPuntTableRule(pnum, assignedVlan));
@@ -416,7 +415,7 @@
 
     @Override
     protected List<FlowRule> processEthDstOnlyFilter(EthCriterion ethCriterion,
-            ApplicationId applicationId) {
+                                                     ApplicationId applicationId) {
         TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
         TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
         selector.matchEthType(Ethernet.TYPE_IPV4);
@@ -812,8 +811,7 @@
      * the copy of packet on the data plane is not affected by the pop vlan action.
      */
     private void initPopVlanPuntGroup() {
-        GroupKey groupKey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(
-                POP_VLAN_PUNT_GROUP_ID | (Objects.hash(deviceId) & FOUR_BIT_MASK)));
+        GroupKey groupKey = popVlanPuntGroupKey();
         TrafficTreatment bucketTreatment = DefaultTrafficTreatment.builder()
                 .popVlan().punt().build();
         GroupBucket bucket =
@@ -830,4 +828,15 @@
 
         log.info("Initialized pop vlan punt group on {}", deviceId);
     }
+
+    /**
+     * Generates group key for a static indirect group that pop vlan and punt to
+     * controller.
+     *
+     * @return the group key of the indirect table
+     */
+    private GroupKey popVlanPuntGroupKey() {
+        int hash = POP_VLAN_PUNT_GROUP_ID | (Objects.hash(deviceId) & FOUR_BIT_MASK);
+        return new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(hash));
+    }
 }
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/CpqdOfdpa2VlanPipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/CpqdOfdpa2VlanPipeline.java
similarity index 99%
rename from drivers/default/src/main/java/org/onosproject/driver/pipeline/CpqdOfdpa2VlanPipeline.java
rename to drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/CpqdOfdpa2VlanPipeline.java
index 30bdfe6..7219fc6 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/CpqdOfdpa2VlanPipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/CpqdOfdpa2VlanPipeline.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.onosproject.driver.pipeline;
+package org.onosproject.driver.pipeline.ofdpa;
 
 import static org.slf4j.LoggerFactory.getLogger;
 
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa2GroupHandler.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2GroupHandler.java
similarity index 67%
rename from drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa2GroupHandler.java
rename to drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2GroupHandler.java
index c73316c..aaca495 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa2GroupHandler.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2GroupHandler.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.onosproject.driver.pipeline;
+package org.onosproject.driver.pipeline.ofdpa;
 
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
@@ -76,16 +76,13 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
 
 import static org.onlab.util.Tools.groupedThreads;
-import static org.onosproject.driver.pipeline.Ofdpa2GroupHandler.OfdpaMplsGroupSubType.OFDPA_GROUP_TYPE_SHIFT;
-import static org.onosproject.driver.pipeline.Ofdpa2GroupHandler.OfdpaMplsGroupSubType.OFDPA_MPLS_SUBTYPE_SHIFT;
-import static org.onosproject.driver.pipeline.Ofdpa2Pipeline.isNotMplsBos;
+import static org.onosproject.driver.pipeline.ofdpa.Ofdpa2Pipeline.*;
+import static org.onosproject.driver.pipeline.ofdpa.OfdpaGroupHandlerUtility.*;
 import static org.onosproject.net.flow.criteria.Criterion.Type.TUNNEL_ID;
 import static org.onosproject.net.flow.criteria.Criterion.Type.VLAN_VID;
-import static org.onosproject.net.flowobjective.NextObjective.Type.HASHED;
 import static org.onosproject.net.group.GroupDescription.Type.ALL;
 import static org.onosproject.net.group.GroupDescription.Type.SELECT;
 import static org.slf4j.LoggerFactory.getLogger;
@@ -94,59 +91,38 @@
  * Group handler that emulates Broadcom OF-DPA TTP.
  */
 public class Ofdpa2GroupHandler {
-    /*
-     * OFDPA requires group-id's to have a certain form.
-     * L2 Interface Groups have <4bits-0><12bits-vlanid><16bits-portid>
-     * L3 Unicast Groups have <4bits-2><28bits-index>
-     * MPLS Interface Groups have <4bits-9><4bits:0><24bits-index>
-     * L3 ECMP Groups have <4bits-7><28bits-index>
-     * L2 Flood Groups have <4bits-4><12bits-vlanid><16bits-index>
-     * L3 VPN Groups have <4bits-9><4bits-2><24bits-index>
-     */
-    protected static final int L2_INTERFACE_TYPE = 0x00000000;
-    protected static final int L3_INTERFACE_TYPE = 0x50000000;
-    protected static final int L3_UNICAST_TYPE = 0x20000000;
-    protected static final int L3_MULTICAST_TYPE = 0x60000000;
-    protected static final int MPLS_INTERFACE_TYPE = 0x90000000;
-    protected static final int MPLS_L3VPN_SUBTYPE = 0x92000000;
-    protected static final int L3_ECMP_TYPE = 0x70000000;
-    protected static final int L2_FLOOD_TYPE = 0x40000000;
+    protected final Logger log = getLogger(getClass());
 
-    protected static final int TYPE_MASK = 0x0fffffff;
-    protected static final int SUBTYPE_MASK = 0x00ffffff;
-    protected static final int TYPE_VLAN_MASK = 0x0000ffff;
-
-    protected static final int THREE_BIT_MASK = 0x0fff;
-    protected static final int FOUR_BIT_MASK = 0xffff;
-    protected static final int PORT_LEN = 16;
-
-    protected static final int PORT_LOWER_BITS_MASK = 0x3f;
-    protected static final long PORT_HIGHER_BITS_MASK = ~PORT_LOWER_BITS_MASK;
-
-    protected static final String HEX_PREFIX = "0x";
-
-    private final Logger log = getLogger(getClass());
-    private ServiceDirectory serviceDirectory;
+    // Services, Stores
     protected GroupService groupService;
     protected StorageService storageService;
-
-    protected DeviceId deviceId;
-    private FlowObjectiveStore flowObjectiveStore;
-    private Cache<GroupKey, List<OfdpaNextGroup>> pendingAddNextObjectives;
-    private Cache<NextObjective, List<GroupKey>> pendingRemoveNextObjectives;
-    private Cache<GroupKey, Set<GroupChainElem>> pendingGroups;
-    private ConcurrentHashMap<GroupKey, Set<NextObjective>> pendingUpdateNextObjectives;
-    private ScheduledExecutorService groupChecker =
-            Executors.newScheduledThreadPool(2, groupedThreads("onos/pipeliner", "ofdpa2-%d", log));
+    protected FlowObjectiveStore flowObjectiveStore;
 
     // index number for group creation
     private AtomicCounter nextIndex;
 
+    protected DeviceId deviceId;
+    private Cache<GroupKey, List<OfdpaGroupHandlerUtility.OfdpaNextGroup>> pendingAddNextObjectives;
+    private Cache<NextObjective, List<GroupKey>> pendingRemoveNextObjectives;
+    private Cache<GroupKey, Set<OfdpaGroupHandlerUtility.GroupChainElem>> pendingGroups;
+    private ConcurrentHashMap<GroupKey, Set<NextObjective>> pendingUpdateNextObjectives;
+
     // local store for pending bucketAdds - by design there can be multiple
     // pending bucket for a group
     protected ConcurrentHashMap<Integer, Set<NextObjective>> pendingBuckets =
             new ConcurrentHashMap<>();
 
+    private ScheduledExecutorService groupCheckerExecutor =
+            Executors.newScheduledThreadPool(2, groupedThreads("onos/pipeliner", "ofdpa-%d", log));
+
+    public Cache<GroupKey, List<OfdpaNextGroup>> pendingAddNextObjectives() {
+        return pendingAddNextObjectives;
+    }
+
+    public Cache<GroupKey, Set<GroupChainElem>> pendingGroups() {
+        return pendingGroups;
+    }
+
     /**
      * Determines whether this pipeline support copy ttl instructions or not.
      *
@@ -178,76 +154,56 @@
     }
 
     protected void init(DeviceId deviceId, PipelinerContext context) {
+        ServiceDirectory serviceDirectory = context.directory();
         this.deviceId = deviceId;
         this.flowObjectiveStore = context.store();
-        this.serviceDirectory = context.directory();
         this.groupService = serviceDirectory.get(GroupService.class);
         this.storageService = serviceDirectory.get(StorageService.class);
         this.nextIndex = storageService.getAtomicCounter("group-id-index-counter");
 
         pendingAddNextObjectives = CacheBuilder.newBuilder()
                 .expireAfterWrite(20, TimeUnit.SECONDS)
-                .removalListener((
-                        RemovalNotification<GroupKey, List<OfdpaNextGroup>> notification) -> {
-                    if (notification.getCause() == RemovalCause.EXPIRED) {
-                        notification.getValue().forEach(ofdpaNextGrp ->
-                                Ofdpa2Pipeline.fail(ofdpaNextGrp.nextObj,
-                                        ObjectiveError.GROUPINSTALLATIONFAILED));
+                .removalListener((RemovalNotification<GroupKey, List<OfdpaNextGroup>> notification) -> {
+                    if (notification.getCause() == RemovalCause.EXPIRED &&
+                            Objects.nonNull(notification.getValue())) {
+                        notification.getValue()
+                                .forEach(ofdpaNextGrp ->
+                                                 fail(ofdpaNextGrp.nextObjective(),
+                                                      ObjectiveError.GROUPINSTALLATIONFAILED));
                     }
                 }).build();
 
         pendingRemoveNextObjectives = CacheBuilder.newBuilder()
                 .expireAfterWrite(20, TimeUnit.SECONDS)
-                .removalListener((
-                        RemovalNotification<NextObjective, List<GroupKey>> notification) -> {
+                .removalListener((RemovalNotification<NextObjective, List<GroupKey>> notification) -> {
                     if (notification.getCause() == RemovalCause.EXPIRED) {
-                            Ofdpa2Pipeline.fail(notification.getKey(),
-                                    ObjectiveError.GROUPREMOVALFAILED);
+                        fail(notification.getKey(),
+                                            ObjectiveError.GROUPREMOVALFAILED);
                     }
                 }).build();
         pendingGroups = CacheBuilder.newBuilder()
                 .expireAfterWrite(20, TimeUnit.SECONDS)
-                .removalListener((
-                        RemovalNotification<GroupKey, Set<GroupChainElem>> notification) -> {
+                .removalListener((RemovalNotification<GroupKey, Set<GroupChainElem>> notification) -> {
                     if (notification.getCause() == RemovalCause.EXPIRED) {
                         log.error("Unable to install group with key {} and pending GCEs: {}",
                                   notification.getKey(), notification.getValue());
                     }
                 }).build();
         pendingUpdateNextObjectives = new ConcurrentHashMap<>();
-        groupChecker.scheduleAtFixedRate(new GroupChecker(), 0, 500, TimeUnit.MILLISECONDS);
-
+        GroupChecker groupChecker = new GroupChecker(this);
+        groupCheckerExecutor.scheduleAtFixedRate(groupChecker, 0, 500, TimeUnit.MILLISECONDS);
         groupService.addListener(new InnerGroupListener());
     }
 
-    /**
-     * The purpose of this function is to verify if the hashed next
-     * objective is supported by the current pipeline.
-     *
-     * @param nextObjective the hashed objective to verify
-     * @return true if the hashed objective is supported. Otherwise false.
-     */
-    public boolean verifyHashedNextObjective(NextObjective nextObjective) {
-        // if it is not hashed, there is something wrong;
-        if (nextObjective.type() != HASHED) {
-            return false;
-        }
-        // The case non supported is the MPLS-ECMP. For now, we try
-        // to create a MPLS-ECMP for the transport of a VPWS. The
-        // necessary info are contained in the meta selector. In particular
-        // we are looking for the case of BoS==False;
-        TrafficSelector metaSelector = nextObjective.meta();
-        if (metaSelector != null && isNotMplsBos(metaSelector)) {
-            return false;
-        }
-
-        return true;
-    }
-
     //////////////////////////////////////
     //  Group Creation
     //////////////////////////////////////
 
+    /**
+     * Adds a list of group chain by given NextObjective.
+     *
+     * @param nextObjective the NextObjective
+     */
     protected void addGroup(NextObjective nextObjective) {
         switch (nextObjective.type()) {
             case SIMPLE:
@@ -256,7 +212,7 @@
                     log.error("Next Objectives of type Simple should only have a "
                                     + "single Traffic Treatment. Next Objective Id:{}",
                             nextObjective.id());
-                    Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.BADPARAMS);
+                    fail(nextObjective, ObjectiveError.BADPARAMS);
                     return;
                 }
                 processSimpleNextObjective(nextObjective);
@@ -268,17 +224,17 @@
                 if (!verifyHashedNextObjective(nextObjective)) {
                     log.error("Next Objectives of type hashed not supported. Next Objective Id:{}",
                               nextObjective.id());
-                    Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.BADPARAMS);
+                    fail(nextObjective, ObjectiveError.BADPARAMS);
                     return;
                 }
                 processHashedNextObjective(nextObjective);
                 break;
             case FAILOVER:
-                Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.UNSUPPORTED);
+                fail(nextObjective, ObjectiveError.UNSUPPORTED);
                 log.warn("Unsupported next objective type {}", nextObjective.type());
                 break;
             default:
-                Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.UNKNOWN);
+                fail(nextObjective, ObjectiveError.UNKNOWN);
                 log.warn("Unknown next objective type {}", nextObjective.type());
         }
     }
@@ -340,18 +296,18 @@
             }
             // create object for local and distributed storage
             Deque<GroupKey> gkeyChain = new ArrayDeque<>();
-            gkeyChain.addFirst(groupInfo.innerMostGroupDesc.appCookie());
-            gkeyChain.addFirst(groupInfo.nextGroupDesc.appCookie());
+            gkeyChain.addFirst(groupInfo.innerMostGroupDesc().appCookie());
+            gkeyChain.addFirst(groupInfo.nextGroupDesc().appCookie());
             OfdpaNextGroup ofdpaGrp =
                     new OfdpaNextGroup(Collections.singletonList(gkeyChain), nextObj);
 
             // store l3groupkey with the ofdpaNextGroup for the nextObjective that depends on it
-            updatePendingNextObjective(groupInfo.nextGroupDesc.appCookie(), ofdpaGrp);
+            updatePendingNextObjective(groupInfo.nextGroupDesc().appCookie(), ofdpaGrp);
 
             // now we are ready to send the l2 groupDescription (inner), as all the stores
             // that will get async replies have been updated. By waiting to update
             // the stores, we prevent nasty race conditions.
-            groupService.addGroup(groupInfo.innerMostGroupDesc);
+            groupService.addGroup(groupInfo.innerMostGroupDesc());
         } else {
             // We handle the pseudo wire with a different a procedure.
             // This procedure is meant to handle both initiation and
@@ -366,17 +322,17 @@
      * @param nextObj the next Objective
      */
     private void createL2InterfaceGroup(NextObjective nextObj) {
-        VlanId assignedVlan = Ofdpa2Pipeline.readVlanFromSelector(nextObj.meta());
+        VlanId assignedVlan = readVlanFromSelector(nextObj.meta());
         if (assignedVlan == null) {
             log.warn("VLAN ID required by simple next obj is missing. Abort.");
-            Ofdpa2Pipeline.fail(nextObj, ObjectiveError.BADPARAMS);
+            fail(nextObj, ObjectiveError.BADPARAMS);
             return;
         }
 
         List<GroupInfo> groupInfos = prepareL2InterfaceGroup(nextObj, assignedVlan);
 
         // There is only one L2 interface group in this case
-        GroupDescription l2InterfaceGroupDesc = groupInfos.get(0).innerMostGroupDesc;
+        GroupDescription l2InterfaceGroupDesc = groupInfos.get(0).innerMostGroupDesc();
 
         // Put all dependency information into allGroupKeys
         List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
@@ -518,26 +474,26 @@
             // untagged outgoing port
             TrafficTreatment.Builder temp = DefaultTrafficTreatment.builder();
             temp.popVlan();
-            innerTtb.build().allInstructions().forEach(i -> temp.add(i));
+            innerTtb.build().allInstructions().forEach(temp::add);
             innerTtb = temp;
         }
 
         // assemble information for ofdpa l2interface group
-        int l2groupId = L2_INTERFACE_TYPE | (vlanid.toShort() << 16) | (int) portNum;
+        int l2groupId = l2GroupId(vlanid, portNum);
         // a globally unique groupkey that is different for ports in the same device,
         // but different for the same portnumber on different devices. Also different
         // for the various group-types created out of the same next objective.
         int l2gk = l2InterfaceGroupKey(deviceId, vlanid, portNum);
-        final GroupKey l2groupkey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l2gk));
+        final GroupKey l2groupkey = new DefaultGroupKey(appKryo.serialize(l2gk));
 
         // assemble information for outer group
-        GroupDescription outerGrpDesc = null;
+        GroupDescription outerGrpDesc;
         if (mpls) {
-            // outer group is MPLSInteface
+            // outer group is MPLS Interface
             int mplsInterfaceIndex = getNextAvailableIndex();
-            int mplsgroupId = MPLS_INTERFACE_TYPE | (SUBTYPE_MASK & mplsInterfaceIndex);
-            final GroupKey mplsgroupkey = new DefaultGroupKey(
-                               Ofdpa2Pipeline.appKryo.serialize(mplsInterfaceIndex));
+            int mplsGroupId = MPLS_INTERFACE_TYPE | (SUBTYPE_MASK & mplsInterfaceIndex);
+            final GroupKey mplsGroupKey = new DefaultGroupKey(
+                               appKryo.serialize(mplsInterfaceIndex));
             outerTtb.group(new GroupId(l2groupId));
             // create the mpls-interface group description to wait for the
             // l2 interface group to be processed
@@ -548,18 +504,18 @@
                     GroupDescription.Type.INDIRECT,
                     new GroupBuckets(Collections.singletonList(
                             mplsinterfaceGroupBucket)),
-                    mplsgroupkey,
-                    mplsgroupId,
+                    mplsGroupKey,
+                    mplsGroupId,
                     appId);
             log.debug("Trying MPLS-Interface: device:{} gid:{} gkey:{} nextid:{}",
-                    deviceId, Integer.toHexString(mplsgroupId),
-                    mplsgroupkey, nextId);
+                    deviceId, Integer.toHexString(mplsGroupId),
+                    mplsGroupKey, nextId);
         } else {
             // outer group is L3Unicast
             int l3unicastIndex = getNextAvailableIndex();
             int l3groupId = L3_UNICAST_TYPE | (TYPE_MASK & l3unicastIndex);
             final GroupKey l3groupkey = new DefaultGroupKey(
-                               Ofdpa2Pipeline.appKryo.serialize(l3unicastIndex));
+                               appKryo.serialize(l3unicastIndex));
             outerTtb.group(new GroupId(l2groupId));
             // create the l3unicast group description to wait for the
             // l2 interface group to be processed
@@ -568,8 +524,7 @@
             outerGrpDesc = new DefaultGroupDescription(
                     deviceId,
                     GroupDescription.Type.INDIRECT,
-                    new GroupBuckets(Collections.singletonList(
-                            l3unicastGroupBucket)),
+                    new GroupBuckets(Collections.singletonList(l3unicastGroupBucket)),
                     l3groupkey,
                     l3groupId,
                     appId);
@@ -579,21 +534,19 @@
         }
 
         // store l2groupkey with the groupChainElem for the outer-group that depends on it
-        GroupChainElem gce = new GroupChainElem(outerGrpDesc, 1, false);
+        GroupChainElem gce = new GroupChainElem(outerGrpDesc, 1, false, deviceId);
         updatePendingGroups(l2groupkey, gce);
 
-        // create group description for the inner l2interfacegroup
+        // create group description for the inner l2 interface group
         GroupBucket l2InterfaceGroupBucket =
                 DefaultGroupBucket.createIndirectGroupBucket(innerTtb.build());
         GroupDescription l2groupDescription =
-                new DefaultGroupDescription(
-                        deviceId,
-                        GroupDescription.Type.INDIRECT,
-                        new GroupBuckets(Collections.singletonList(
-                                l2InterfaceGroupBucket)),
-                        l2groupkey,
-                        l2groupId,
-                        appId);
+                new DefaultGroupDescription(deviceId,
+                                            GroupDescription.Type.INDIRECT,
+                                            new GroupBuckets(Collections.singletonList(l2InterfaceGroupBucket)),
+                                            l2groupkey,
+                                            l2groupId,
+                                            appId);
         log.debug("Trying L2Interface: device:{} gid:{} gkey:{} nextId:{}",
                 deviceId, Integer.toHexString(l2groupId),
                 l2groupkey, nextId);
@@ -610,23 +563,22 @@
      * @param nextObj  the nextObjective of type BROADCAST
      */
     private void processBroadcastNextObjective(NextObjective nextObj) {
-        VlanId assignedVlan = Ofdpa2Pipeline.readVlanFromSelector(nextObj.meta());
+        VlanId assignedVlan = readVlanFromSelector(nextObj.meta());
         if (assignedVlan == null) {
             log.warn("VLAN ID required by broadcast next obj is missing. Abort.");
-            Ofdpa2Pipeline.fail(nextObj, ObjectiveError.BADPARAMS);
+            fail(nextObj, ObjectiveError.BADPARAMS);
             return;
         }
 
         List<GroupInfo> groupInfos = prepareL2InterfaceGroup(nextObj, assignedVlan);
 
-        IpPrefix ipDst = Ofdpa2Pipeline.readIpDstFromSelector(nextObj.meta());
+        IpPrefix ipDst = readIpDstFromSelector(nextObj.meta());
         if (ipDst != null) {
             if (ipDst.isMulticast()) {
                 createL3MulticastGroup(nextObj, assignedVlan, groupInfos);
             } else {
                 log.warn("Broadcast NextObj with non-multicast IP address {}", nextObj);
-                Ofdpa2Pipeline.fail(nextObj, ObjectiveError.BADPARAMS);
-                return;
+                fail(nextObj, ObjectiveError.BADPARAMS);
             }
         } else {
             createL2FloodGroup(nextObj, assignedVlan, groupInfos);
@@ -670,27 +622,31 @@
                 }
             }
 
+            if (portNum == null) {
+                log.warn("Can't find output port for the bucket {}.", treatment);
+                continue;
+            }
+
             // assemble info for l2 interface group
             VlanId l2InterfaceGroupVlan =
                     (egressVlan != null && !assignedVlan.equals(egressVlan)) ?
                             egressVlan : assignedVlan;
             int l2gk = l2InterfaceGroupKey(deviceId, l2InterfaceGroupVlan, portNum.toLong());
             final GroupKey l2InterfaceGroupKey =
-                    new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l2gk));
+                    new DefaultGroupKey(appKryo.serialize(l2gk));
             int l2InterfaceGroupId = L2_INTERFACE_TYPE |
                     ((l2InterfaceGroupVlan.toShort() & THREE_BIT_MASK) << PORT_LEN) |
                     ((int) portNum.toLong() & FOUR_BIT_MASK);
             GroupBucket l2InterfaceGroupBucket =
                     DefaultGroupBucket.createIndirectGroupBucket(newTreatment.build());
             GroupDescription l2InterfaceGroupDescription =
-                    new DefaultGroupDescription(
-                            deviceId,
-                            GroupDescription.Type.INDIRECT,
-                            new GroupBuckets(Collections.singletonList(
-                                    l2InterfaceGroupBucket)),
-                            l2InterfaceGroupKey,
-                            l2InterfaceGroupId,
-                            nextObj.appId());
+                    new DefaultGroupDescription(deviceId,
+                                                GroupDescription.Type.INDIRECT,
+                                                new GroupBuckets(Collections.singletonList(
+                                                        l2InterfaceGroupBucket)),
+                                                l2InterfaceGroupKey,
+                                                l2InterfaceGroupId,
+                                                nextObj.appId());
             log.debug("Trying L2-Interface: device:{} gid:{} gkey:{} nextid:{}",
                     deviceId, Integer.toHexString(l2InterfaceGroupId),
                     l2InterfaceGroupKey, nextObj.id());
@@ -705,14 +661,11 @@
                                     List<GroupInfo> groupInfos) {
         // assemble info for l2 flood group. Since there can be only one flood
         // group for a vlan, its index is always the same - 0
-        Integer l2floodgroupId = L2_FLOOD_TYPE | (vlanId.toShort() << 16);
-        int l2floodgk = l2FloodGroupKey(vlanId);
-        final GroupKey l2floodgroupkey =
-                new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l2floodgk));
+        Integer l2FloodGroupId = L2_FLOOD_TYPE | (vlanId.toShort() << 16);
+        final GroupKey l2FloodGroupKey = l2FloodGroupKey(vlanId, deviceId);
 
         // collection of group buckets pointing to all the l2 interface groups
-        List<GroupBucket> l2floodBuckets =
-                generateNextGroupBuckets(groupInfos, ALL);
+        List<GroupBucket> l2floodBuckets = generateNextGroupBuckets(groupInfos, ALL);
         // create the l2flood group-description to wait for all the
         // l2interface groups to be processed
         GroupDescription l2floodGroupDescription =
@@ -720,50 +673,45 @@
                         deviceId,
                         ALL,
                         new GroupBuckets(l2floodBuckets),
-                        l2floodgroupkey,
-                        l2floodgroupId,
+                        l2FloodGroupKey,
+                        l2FloodGroupId,
                         nextObj.appId());
         log.debug("Trying L2-Flood: device:{} gid:{} gkey:{} nextid:{}",
-                deviceId, Integer.toHexString(l2floodgroupId),
-                l2floodgroupkey, nextObj.id());
+                deviceId, Integer.toHexString(l2FloodGroupId),
+                l2FloodGroupKey, nextObj.id());
 
         // Put all dependency information into allGroupKeys
         List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
         groupInfos.forEach(groupInfo -> {
-            Deque<GroupKey> gkeyChain = new ArrayDeque<>();
+            Deque<GroupKey> groupKeyChain = new ArrayDeque<>();
             // In this case we should have L2 interface group only
-            gkeyChain.addFirst(groupInfo.nextGroupDesc.appCookie());
-            gkeyChain.addFirst(l2floodgroupkey);
-            allGroupKeys.add(gkeyChain);
+            groupKeyChain.addFirst(groupInfo.nextGroupDesc().appCookie());
+            groupKeyChain.addFirst(l2FloodGroupKey);
+            allGroupKeys.add(groupKeyChain);
         });
 
         // Point the next objective to this group
         OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
-        updatePendingNextObjective(l2floodgroupkey, ofdpaGrp);
+        updatePendingNextObjective(l2FloodGroupKey, ofdpaGrp);
 
         GroupChainElem gce = new GroupChainElem(l2floodGroupDescription,
-                groupInfos.size(), false);
+                groupInfos.size(), false, deviceId);
         groupInfos.forEach(groupInfo -> {
             // Point this group to the next group
-            updatePendingGroups(groupInfo.nextGroupDesc.appCookie(), gce);
+            updatePendingGroups(groupInfo.nextGroupDesc().appCookie(), gce);
             // Start installing the inner-most group
-            groupService.addGroup(groupInfo.innerMostGroupDesc);
+            groupService.addGroup(groupInfo.innerMostGroupDesc());
         });
     }
 
-    private int l2FloodGroupKey(VlanId vlanId) {
-        int hash = Objects.hash(deviceId, vlanId);
-        return L2_FLOOD_TYPE | TYPE_MASK & hash;
-    }
-
     private void createL3MulticastGroup(NextObjective nextObj, VlanId vlanId,
                                         List<GroupInfo> groupInfos) {
         List<GroupBucket> l3McastBuckets = new ArrayList<>();
         groupInfos.forEach(groupInfo -> {
             // Points to L3 interface group if there is one.
             // Otherwise points to L2 interface group directly.
-            GroupDescription nextGroupDesc = (groupInfo.nextGroupDesc != null) ?
-                    groupInfo.nextGroupDesc : groupInfo.innerMostGroupDesc;
+            GroupDescription nextGroupDesc = (groupInfo.nextGroupDesc() != null) ?
+                    groupInfo.nextGroupDesc() : groupInfo.innerMostGroupDesc();
             TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
             ttb.group(new GroupId(nextGroupDesc.givenGroupId()));
             GroupBucket abucket = DefaultGroupBucket.createAllGroupBucket(ttb.build());
@@ -774,7 +722,7 @@
         int l3MulticastGroupId = L3_MULTICAST_TYPE |
                 vlanId.toShort() << 16 | (TYPE_VLAN_MASK & l3MulticastIndex);
         final GroupKey l3MulticastGroupKey =
-                new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l3MulticastIndex));
+                new DefaultGroupKey(appKryo.serialize(l3MulticastIndex));
 
         GroupDescription l3MulticastGroupDesc = new DefaultGroupDescription(deviceId,
                 ALL,
@@ -787,10 +735,10 @@
         List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
         groupInfos.forEach(groupInfo -> {
             Deque<GroupKey> gkeyChain = new ArrayDeque<>();
-            gkeyChain.addFirst(groupInfo.innerMostGroupDesc.appCookie());
+            gkeyChain.addFirst(groupInfo.innerMostGroupDesc().appCookie());
             // Add L3 interface group to the chain if there is one.
-            if (!groupInfo.nextGroupDesc.equals(groupInfo.innerMostGroupDesc)) {
-                gkeyChain.addFirst(groupInfo.nextGroupDesc.appCookie());
+            if (!groupInfo.nextGroupDesc().equals(groupInfo.innerMostGroupDesc())) {
+                gkeyChain.addFirst(groupInfo.nextGroupDesc().appCookie());
             }
             gkeyChain.addFirst(l3MulticastGroupKey);
             allGroupKeys.add(gkeyChain);
@@ -801,20 +749,20 @@
         updatePendingNextObjective(l3MulticastGroupKey, ofdpaGrp);
 
         GroupChainElem outerGce = new GroupChainElem(l3MulticastGroupDesc,
-                groupInfos.size(), false);
+                groupInfos.size(), false, deviceId);
         groupInfos.forEach(groupInfo -> {
             // Point this group (L3 multicast) to the next group
-            updatePendingGroups(groupInfo.nextGroupDesc.appCookie(), outerGce);
+            updatePendingGroups(groupInfo.nextGroupDesc().appCookie(), outerGce);
 
             // Point next group to inner-most group, if any
-            if (!groupInfo.nextGroupDesc.equals(groupInfo.innerMostGroupDesc)) {
-                GroupChainElem innerGce = new GroupChainElem(groupInfo.nextGroupDesc,
-                        1, false);
-                updatePendingGroups(groupInfo.innerMostGroupDesc.appCookie(), innerGce);
+            if (!groupInfo.nextGroupDesc().equals(groupInfo.innerMostGroupDesc())) {
+                GroupChainElem innerGce = new GroupChainElem(groupInfo.nextGroupDesc(),
+                        1, false, deviceId);
+                updatePendingGroups(groupInfo.innerMostGroupDesc().appCookie(), innerGce);
             }
 
             // Start installing the inner-most group
-            groupService.addGroup(groupInfo.innerMostGroupDesc);
+            groupService.addGroup(groupInfo.innerMostGroupDesc());
         });
     }
 
@@ -844,7 +792,7 @@
         for (GroupInfo gi : unsentGroups) {
             // create ECMP bucket to point to the outer group
             TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
-            ttb.group(new GroupId(gi.nextGroupDesc.givenGroupId()));
+            ttb.group(new GroupId(gi.nextGroupDesc().givenGroupId()));
             GroupBucket sbucket = DefaultGroupBucket
                     .createSelectGroupBucket(ttb.build());
             l3ecmpGroupBuckets.add(sbucket);
@@ -852,7 +800,7 @@
         int l3ecmpIndex = getNextAvailableIndex();
         int l3ecmpGroupId = L3_ECMP_TYPE | (TYPE_MASK & l3ecmpIndex);
         GroupKey l3ecmpGroupKey = new DefaultGroupKey(
-                                          Ofdpa2Pipeline.appKryo.serialize(l3ecmpIndex));
+                                          appKryo.serialize(l3ecmpIndex));
         GroupDescription l3ecmpGroupDesc =
                 new DefaultGroupDescription(
                         deviceId,
@@ -863,10 +811,10 @@
                         nextObj.appId());
         GroupChainElem l3ecmpGce = new GroupChainElem(l3ecmpGroupDesc,
                 l3ecmpGroupBuckets.size(),
-                false);
+                false, deviceId);
 
         // create objects for local and distributed storage
-        allGroupKeys.forEach(gkeyChain -> gkeyChain.addFirst(l3ecmpGroupKey));
+        allGroupKeys.forEach(gKeyChain -> gKeyChain.addFirst(l3ecmpGroupKey));
         OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
 
         // store l3ecmpGroupKey with the ofdpaGroupChain for the nextObjective
@@ -879,9 +827,9 @@
         // finally we are ready to send the innermost groups
         for (GroupInfo gi : unsentGroups) {
             log.debug("Sending innermost group {} in group chain on device {} ",
-                    Integer.toHexString(gi.innerMostGroupDesc.givenGroupId()), deviceId);
-            updatePendingGroups(gi.nextGroupDesc.appCookie(), l3ecmpGce);
-            groupService.addGroup(gi.innerMostGroupDesc);
+                    Integer.toHexString(gi.innerMostGroupDesc().givenGroupId()), deviceId);
+            updatePendingGroups(gi.nextGroupDesc().appCookie(), l3ecmpGce);
+            groupService.addGroup(gi.innerMostGroupDesc());
         }
     }
 
@@ -899,8 +847,8 @@
      * @param unsentGroups  a list to store GroupInfo for each bucket-group-chain
      */
     protected void createHashBucketChains(NextObjective nextObj,
-                                        List<Deque<GroupKey>> allGroupKeys,
-                                        List<GroupInfo> unsentGroups) {
+                                          List<Deque<GroupKey>> allGroupKeys,
+                                          List<GroupInfo> unsentGroups) {
         // break up hashed next objective to multiple groups
         Collection<TrafficTreatment> buckets = nextObj.next();
 
@@ -916,43 +864,44 @@
                     }
                     if (l2ins.subtype() == L2ModificationInstruction.L2SubType.MPLS_LABEL) {
                         if (innermostLabel == null) {
-                            innermostLabel = ((L2ModificationInstruction.ModMplsLabelInstruction) l2ins).label();
+                            innermostLabel =
+                                    ((L2ModificationInstruction.ModMplsLabelInstruction) l2ins).label();
                         }
                     }
                 }
             }
 
-            Deque<GroupKey> gkeyChain = new ArrayDeque<>();
+            Deque<GroupKey> gKeyChain = new ArrayDeque<>();
             // XXX we only deal with 0 and 1 label push right now
             if (labelsPushed == 0) {
-                GroupInfo nolabelGroupInfo;
+                GroupInfo noLabelGroupInfo;
                 TrafficSelector metaSelector = nextObj.meta();
                 if (metaSelector != null) {
                     if (isNotMplsBos(metaSelector)) {
-                        nolabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
-                                        nextObj.appId(), true,
-                                        nextObj.meta());
+                        noLabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
+                                                           nextObj.appId(), true,
+                                                           nextObj.meta());
                     } else {
-                        nolabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
-                                        nextObj.appId(), false,
-                                        nextObj.meta());
+                        noLabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
+                                                           nextObj.appId(), false,
+                                                           nextObj.meta());
                     }
                 } else {
-                    nolabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
-                                    nextObj.appId(), false,
-                                    nextObj.meta());
+                    noLabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
+                                                       nextObj.appId(), false,
+                                                       nextObj.meta());
                 }
-                if (nolabelGroupInfo == null) {
+                if (noLabelGroupInfo == null) {
                     log.error("Could not process nextObj={} in dev:{}",
                             nextObj.id(), deviceId);
                     return;
                 }
-                gkeyChain.addFirst(nolabelGroupInfo.innerMostGroupDesc.appCookie());
-                gkeyChain.addFirst(nolabelGroupInfo.nextGroupDesc.appCookie());
+                gKeyChain.addFirst(noLabelGroupInfo.innerMostGroupDesc().appCookie());
+                gKeyChain.addFirst(noLabelGroupInfo.nextGroupDesc().appCookie());
 
                 // we can't send the inner group description yet, as we have to
                 // create the dependent ECMP group first. So we store..
-                unsentGroups.add(nolabelGroupInfo);
+                unsentGroups.add(noLabelGroupInfo);
 
             } else if (labelsPushed == 1) {
                 GroupInfo onelabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
@@ -970,7 +919,7 @@
                 }
                 l3vpnTtb.pushMpls()
                         .setMpls(innermostLabel)
-                        .group(new GroupId(onelabelGroupInfo.nextGroupDesc.givenGroupId()));
+                        .group(new GroupId(onelabelGroupInfo.nextGroupDesc().givenGroupId()));
                 if (supportCopyTtl()) {
                     l3vpnTtb.copyTtlOut();
                 }
@@ -984,35 +933,37 @@
                 GroupBucket l3vpnGrpBkt  =
                         DefaultGroupBucket.createIndirectGroupBucket(l3vpnTtb.build());
                 int l3vpnIndex = getNextAvailableIndex();
-                int l3vpngroupId = MPLS_L3VPN_SUBTYPE | (SUBTYPE_MASK & l3vpnIndex);
-                GroupKey l3vpngroupkey = new DefaultGroupKey(
-                             Ofdpa2Pipeline.appKryo.serialize(l3vpnIndex));
+                int l3vpnGroupId = MPLS_L3VPN_SUBTYPE | (SUBTYPE_MASK & l3vpnIndex);
+                GroupKey l3vpnGroupKey = new DefaultGroupKey(
+                             appKryo.serialize(l3vpnIndex));
                 GroupDescription l3vpnGroupDesc =
                         new DefaultGroupDescription(
                                 deviceId,
                                 GroupDescription.Type.INDIRECT,
-                                new GroupBuckets(Collections.singletonList(
-                                        l3vpnGrpBkt)),
-                                l3vpngroupkey,
-                                l3vpngroupId,
+                                new GroupBuckets(Collections.singletonList(l3vpnGrpBkt)),
+                                l3vpnGroupKey,
+                                l3vpnGroupId,
                                 nextObj.appId());
-                GroupChainElem l3vpnGce = new GroupChainElem(l3vpnGroupDesc, 1, false);
-                updatePendingGroups(onelabelGroupInfo.nextGroupDesc.appCookie(), l3vpnGce);
+                GroupChainElem l3vpnGce = new GroupChainElem(l3vpnGroupDesc,
+                                                             1,
+                                                             false,
+                                                             deviceId);
+                updatePendingGroups(onelabelGroupInfo.nextGroupDesc().appCookie(), l3vpnGce);
 
-                gkeyChain.addFirst(onelabelGroupInfo.innerMostGroupDesc.appCookie());
-                gkeyChain.addFirst(onelabelGroupInfo.nextGroupDesc.appCookie());
-                gkeyChain.addFirst(l3vpngroupkey);
+                gKeyChain.addFirst(onelabelGroupInfo.innerMostGroupDesc().appCookie());
+                gKeyChain.addFirst(onelabelGroupInfo.nextGroupDesc().appCookie());
+                gKeyChain.addFirst(l3vpnGroupKey);
 
                 //now we can replace the outerGrpDesc with the one we just created
-                onelabelGroupInfo.nextGroupDesc = l3vpnGroupDesc;
+                onelabelGroupInfo.nextGroupDesc(l3vpnGroupDesc);
 
                 // we can't send the innermost group yet, as we have to create
                 // the dependent ECMP group first. So we store ...
                 unsentGroups.add(onelabelGroupInfo);
 
-                log.debug("Trying L3VPN: device:{} gid:{} gkey:{} nextId:{}",
-                        deviceId, Integer.toHexString(l3vpngroupId),
-                        l3vpngroupkey, nextObj.id());
+                log.debug("Trying L3VPN: device:{} gid:{} group key:{} nextId:{}",
+                        deviceId, Integer.toHexString(l3vpnGroupId),
+                        l3vpnGroupKey, nextObj.id());
 
             } else {
                 log.warn("Driver currently does not handle more than 1 MPLS "
@@ -1021,7 +972,7 @@
             }
 
             // all groups in this chain
-            allGroupKeys.add(gkeyChain);
+            allGroupKeys.add(gKeyChain);
         }
     }
 
@@ -1034,17 +985,15 @@
      */
     protected void processPwNextObjective(NextObjective nextObjective) {
         log.warn("Pseudo wire extensions are not support for the OFDPA 2.0 {}", nextObjective.id());
-        return;
     }
 
     //////////////////////////////////////
     //  Group Editing
     //////////////////////////////////////
-
     /**
      *  Adds a bucket to the top level group of a group-chain, and creates the chain.
      *  Ensures that bucket being added is not a duplicate, by checking existing
-     *  buckets for the same outport.
+     *  buckets for the same output port.
      *
      * @param nextObjective the bucket information for a next group
      * @param next the representation of the existing group-chain for this next objective
@@ -1054,15 +1003,18 @@
                 nextObjective.type() != NextObjective.Type.BROADCAST) {
             log.warn("AddBuckets not applied to nextType:{} in dev:{} for next:{}",
                      nextObjective.type(), deviceId, nextObjective.id());
-            Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.UNSUPPORTED);
+            fail(nextObjective, ObjectiveError.UNSUPPORTED);
             return;
         }
 
         // first check to see if bucket being added is not a duplicate of an
-        // existing bucket. If it is for an existing outport, then its a duplicate.
+        // existing bucket. If it is for an existing output port, then its a
+        // duplicate.
         Set<TrafficTreatment> duplicateBuckets = Sets.newHashSet();
-        List<Deque<GroupKey>> allActiveKeys = Ofdpa2Pipeline.appKryo.deserialize(next.data());
-        Set<PortNumber> existingPorts = getExistingOutputPorts(allActiveKeys);
+        List<Deque<GroupKey>> allActiveKeys = appKryo.deserialize(next.data());
+        Set<PortNumber> existingPorts = getExistingOutputPorts(allActiveKeys,
+                                                               groupService,
+                                                               deviceId);
         Set<TrafficTreatment> nonDuplicateBuckets = Sets.newHashSet();
         NextObjective objectiveToAdd;
 
@@ -1107,31 +1059,12 @@
         }
     }
 
-    private Set<PortNumber> getExistingOutputPorts(List<Deque<GroupKey>> allActiveKeys) {
-        Set<PortNumber> existingPorts = Sets.newHashSet();
-
-        allActiveKeys.forEach(keyChain -> {
-            GroupKey ifaceGroupKey = keyChain.peekLast();
-            Group ifaceGroup = groupService.getGroup(deviceId, ifaceGroupKey);
-            if (ifaceGroup != null && !ifaceGroup.buckets().buckets().isEmpty()) {
-                 ifaceGroup.buckets().buckets().forEach(bucket -> {
-                     PortNumber portNumber = readOutPortFromTreatment(bucket.treatment());
-
-                     if (portNumber != null) {
-                         existingPorts.add(portNumber);
-                     }
-                 });
-            }
-        });
-        return existingPorts;
-    }
-
     private void addBucketToHashGroup(NextObjective nextObjective,
                                       List<Deque<GroupKey>> allActiveKeys) {
         // storage for all group keys in the chain of groups created
         List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
         List<GroupInfo> unsentGroups = new ArrayList<>();
-        List<GroupBucket> newBuckets = Lists.newArrayList();
+        List<GroupBucket> newBuckets;
         createHashBucketChains(nextObjective, allGroupKeys, unsentGroups);
 
         // now we can create the buckets to add to the outermost L3 ECMP group
@@ -1140,7 +1073,7 @@
         // retrieve the original L3 ECMP group
         Group l3ecmpGroup = retrieveTopLevelGroup(allActiveKeys, nextObjective.id());
         if (l3ecmpGroup == null) {
-            Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.GROUPMISSING);
+            fail(nextObjective, ObjectiveError.GROUPMISSING);
             return;
         }
         GroupKey l3ecmpGroupKey = l3ecmpGroup.appCookie();
@@ -1150,16 +1083,16 @@
         // existing groups, we still use one in the GroupChainElem. When the latter is
         // processed, the info will be extracted for the bucketAdd call to groupService
         GroupDescription l3ecmpGroupDesc =
-                new DefaultGroupDescription(
-                        deviceId,
-                        SELECT,
-                        new GroupBuckets(newBuckets),
-                        l3ecmpGroupKey,
-                        l3ecmpGroupId,
-                        nextObjective.appId());
+                new DefaultGroupDescription(deviceId,
+                                            SELECT,
+                                            new GroupBuckets(newBuckets),
+                                            l3ecmpGroupKey,
+                                            l3ecmpGroupId,
+                                            nextObjective.appId());
         GroupChainElem l3ecmpGce = new GroupChainElem(l3ecmpGroupDesc,
                                                       unsentGroups.size(),
-                                                      true);
+                                                      true,
+                                                      deviceId);
 
         // update original NextGroup with new bucket-chain
         // If active keys shows only the top-level group without a chain of groups,
@@ -1170,48 +1103,42 @@
             allActiveKeys.clear();
         }
         allActiveKeys.add(newBucketChain);
-        updatePendingNextObjective(l3ecmpGroupKey,
-                                   new OfdpaNextGroup(allActiveKeys, nextObjective));
+        updatePendingNextObjective(l3ecmpGroupKey, new OfdpaNextGroup(allActiveKeys, nextObjective));
 
-        log.debug("Adding to L3ECMP: device:{} gid:{} gkey:{} nextId:{}",
+        log.debug("Adding to L3ECMP: device:{} gid:{} group key:{} nextId:{}",
                 deviceId, Integer.toHexString(l3ecmpGroupId),
                 l3ecmpGroupKey, nextObjective.id());
 
         unsentGroups.forEach(groupInfo -> {
             // send the innermost group
             log.debug("Sending innermost group {} in group chain on device {} ",
-                      Integer.toHexString(groupInfo.innerMostGroupDesc.givenGroupId()), deviceId);
-            updatePendingGroups(groupInfo.nextGroupDesc.appCookie(), l3ecmpGce);
-            groupService.addGroup(groupInfo.innerMostGroupDesc);
+                      Integer.toHexString(groupInfo.innerMostGroupDesc().givenGroupId()), deviceId);
+            updatePendingGroups(groupInfo.nextGroupDesc().appCookie(), l3ecmpGce);
+            groupService.addGroup(groupInfo.innerMostGroupDesc());
         });
     }
 
     private void addBucketToBroadcastGroup(NextObjective nextObj,
-                                        List<Deque<GroupKey>> allActiveKeys) {
-        VlanId assignedVlan = Ofdpa2Pipeline.readVlanFromSelector(nextObj.meta());
+                                           List<Deque<GroupKey>> allActiveKeys) {
+        VlanId assignedVlan = readVlanFromSelector(nextObj.meta());
         if (assignedVlan == null) {
             log.warn("VLAN ID required by broadcast next obj is missing. "
                     + "Aborting add bucket to broadcast group for next:{} in dev:{}",
                     nextObj.id(), deviceId);
-            Ofdpa2Pipeline.fail(nextObj, ObjectiveError.BADPARAMS);
+            fail(nextObj, ObjectiveError.BADPARAMS);
             return;
         }
-
         List<GroupInfo> groupInfos = prepareL2InterfaceGroup(nextObj, assignedVlan);
-
-        IpPrefix ipDst = Ofdpa2Pipeline.readIpDstFromSelector(nextObj.meta());
+        IpPrefix ipDst = readIpDstFromSelector(nextObj.meta());
         if (ipDst != null) {
             if (ipDst.isMulticast()) {
-                addBucketToL3MulticastGroup(nextObj, allActiveKeys,
-                                            groupInfos, assignedVlan);
+                addBucketToL3MulticastGroup(nextObj, allActiveKeys, groupInfos, assignedVlan);
             } else {
                 log.warn("Broadcast NextObj with non-multicast IP address {}", nextObj);
-                Ofdpa2Pipeline.fail(nextObj, ObjectiveError.BADPARAMS);
-                return;
+                fail(nextObj, ObjectiveError.BADPARAMS);
             }
         } else {
-            addBucketToL2FloodGroup(nextObj, allActiveKeys,
-                                    groupInfos, assignedVlan);
+            addBucketToL2FloodGroup(nextObj, allActiveKeys, groupInfos, assignedVlan);
         }
     }
 
@@ -1224,7 +1151,7 @@
         if (l2FloodGroup == null) {
             log.warn("Can't find L2 flood group while adding bucket to it. NextObj = {}",
                      nextObj);
-            Ofdpa2Pipeline.fail(nextObj, ObjectiveError.GROUPMISSING);
+            fail(nextObj, ObjectiveError.GROUPMISSING);
             return;
         }
 
@@ -1233,18 +1160,18 @@
         List<GroupBucket> newBuckets = generateNextGroupBuckets(groupInfos, ALL);
 
         GroupDescription l2FloodGroupDescription =
-                new DefaultGroupDescription(
-                        deviceId,
-                        ALL,
-                        new GroupBuckets(newBuckets),
-                        l2floodGroupKey,
-                        l2floodGroupId,
-                        nextObj.appId());
+                new DefaultGroupDescription(deviceId,
+                                            ALL,
+                                            new GroupBuckets(newBuckets),
+                                            l2floodGroupKey,
+                                            l2floodGroupId,
+                                            nextObj.appId());
 
         GroupChainElem l2FloodGroupChainElement =
                 new GroupChainElem(l2FloodGroupDescription,
                                    groupInfos.size(),
-                                   true);
+                                   true,
+                                   deviceId);
 
         updatePendingNextObjective(l2floodGroupKey,
                                    new OfdpaNextGroup(allActiveKeys, nextObj));
@@ -1256,7 +1183,7 @@
             log.warn("VLAN ID {} does not match Flood group {} to which bucket is "
                              + "being added, for next:{} in dev:{}. Abort.", assignedVlan,
                      Integer.toHexString(l2floodGroupId), nextObj.id(), deviceId);
-            Ofdpa2Pipeline.fail(nextObj, ObjectiveError.BADPARAMS);
+            fail(nextObj, ObjectiveError.BADPARAMS);
             return;
         }
 
@@ -1265,81 +1192,36 @@
             // If active keys shows only the top-level group without a chain of groups,
             // then it represents an empty group. Update by replacing empty chain.
             Deque<GroupKey> newBucketChain = new ArrayDeque<>();
-            newBucketChain.addFirst(groupInfo.nextGroupDesc.appCookie());
+            newBucketChain.addFirst(groupInfo.nextGroupDesc().appCookie());
             newBucketChain.addFirst(l2floodGroupKey);
             if (allActiveKeys.size() == 1 && allActiveKeys.get(0).size() == 1) {
                 allActiveKeys.clear();
             }
             allActiveKeys.add(newBucketChain);
 
-            log.debug("Adding to L2FLOOD: device:{} gid:{} gkey:{} nextId:{}",
+            log.debug("Adding to L2FLOOD: device:{} gid:{} group key:{} nextId:{}",
                       deviceId, Integer.toHexString(l2floodGroupId),
                       l2floodGroupKey, nextObj.id());
             // send the innermost group
             log.debug("Sending innermost group {} in group chain on device {} ",
-                      Integer.toHexString(groupInfo.innerMostGroupDesc.givenGroupId()),
+                      Integer.toHexString(groupInfo.innerMostGroupDesc().givenGroupId()),
                       deviceId);
 
-            updatePendingGroups(groupInfo.nextGroupDesc.appCookie(), l2FloodGroupChainElement);
+            updatePendingGroups(groupInfo.nextGroupDesc().appCookie(), l2FloodGroupChainElement);
 
-            DeviceId innerMostGroupDevice = groupInfo.innerMostGroupDesc.deviceId();
-            GroupKey innerMostGroupKey = groupInfo.innerMostGroupDesc.appCookie();
+            DeviceId innerMostGroupDevice = groupInfo.innerMostGroupDesc().deviceId();
+            GroupKey innerMostGroupKey = groupInfo.innerMostGroupDesc().appCookie();
             Group existsL2IGroup = groupService.getGroup(innerMostGroupDevice, innerMostGroupKey);
 
             if (existsL2IGroup != null) {
                 // group already exist
                 processPendingAddGroupsOrNextObjs(innerMostGroupKey, true);
             } else {
-                groupService.addGroup(groupInfo.innerMostGroupDesc);
+                groupService.addGroup(groupInfo.innerMostGroupDesc());
             }
-
         });
     }
 
-    private VlanId extractVlanIdFromGroupId(int groupId) {
-        // Extract the 9th to 20th bit from group id as vlan id.
-        short vlanId = (short) ((groupId & 0x0fff0000) >> 16);
-        return VlanId.vlanId(vlanId);
-    }
-
-    private List<GroupBucket> generateNextGroupBuckets(List<GroupInfo> groupInfos,
-                                                       GroupDescription.Type bucketType) {
-        List<GroupBucket> newBuckets = Lists.newArrayList();
-
-        groupInfos.forEach(groupInfo -> {
-            GroupDescription groupDesc = groupInfo.nextGroupDesc;
-            TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
-            treatmentBuilder.group(new GroupId(groupDesc.givenGroupId()));
-            GroupBucket newBucket = null;
-            switch (bucketType) {
-                case ALL:
-                    newBucket =
-                            DefaultGroupBucket.createAllGroupBucket(treatmentBuilder.build());
-                    break;
-                case INDIRECT:
-                    newBucket =
-                            DefaultGroupBucket.createIndirectGroupBucket(treatmentBuilder.build());
-                    break;
-                case SELECT:
-                    newBucket =
-                            DefaultGroupBucket.createSelectGroupBucket(treatmentBuilder.build());
-                    break;
-                case FAILOVER:
-                    // TODO support failover bucket type
-                default:
-                    log.warn("Unknown bucket type: {}", bucketType);
-                    break;
-            }
-
-            if (newBucket != null) {
-                newBuckets.add(newBucket);
-            }
-
-        });
-
-        return ImmutableList.copyOf(newBuckets);
-    }
-
     private void addBucketToL3MulticastGroup(NextObjective nextObj,
                                              List<Deque<GroupKey>> allActiveKeys,
                                              List<GroupInfo> groupInfos,
@@ -1349,18 +1231,18 @@
         groupInfos.forEach(groupInfo -> {
             // Points to L3 interface group if there is one.
             // Otherwise points to L2 interface group directly.
-            GroupDescription nextGroupDesc = (groupInfo.nextGroupDesc != null) ?
-                    groupInfo.nextGroupDesc : groupInfo.innerMostGroupDesc;
-            TrafficTreatment.Builder treatmentBuidler = DefaultTrafficTreatment.builder();
-            treatmentBuidler.group(new GroupId(nextGroupDesc.givenGroupId()));
-            GroupBucket newBucket = DefaultGroupBucket.createAllGroupBucket(treatmentBuidler.build());
+            GroupDescription nextGroupDesc = (groupInfo.nextGroupDesc() != null) ?
+                    groupInfo.nextGroupDesc() : groupInfo.innerMostGroupDesc();
+            TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
+            treatmentBuilder.group(new GroupId(nextGroupDesc.givenGroupId()));
+            GroupBucket newBucket = DefaultGroupBucket.createAllGroupBucket(treatmentBuilder.build());
             newBuckets.add(newBucket);
         });
 
         // get the group being edited
         Group l3mcastGroup = retrieveTopLevelGroup(allActiveKeys, nextObj.id());
         if (l3mcastGroup == null) {
-            Ofdpa2Pipeline.fail(nextObj, ObjectiveError.GROUPMISSING);
+            fail(nextObj, ObjectiveError.GROUPMISSING);
             return;
         }
         GroupKey l3mcastGroupKey = l3mcastGroup.appCookie();
@@ -1372,25 +1254,26 @@
             log.warn("VLAN ID {} does not match L3 Mcast group {} to which bucket is "
                     + "being added, for next:{} in dev:{}. Abort.", assignedVlan,
                     Integer.toHexString(l3mcastGroupId), nextObj.id(), deviceId);
-            Ofdpa2Pipeline.fail(nextObj, ObjectiveError.BADPARAMS);
+            fail(nextObj, ObjectiveError.BADPARAMS);
         }
         GroupDescription l3mcastGroupDescription =
-                new DefaultGroupDescription(
-                        deviceId,
-                        ALL,
-                        new GroupBuckets(newBuckets),
-                        l3mcastGroupKey,
-                        l3mcastGroupId,
-                        nextObj.appId());
+                new DefaultGroupDescription(deviceId,
+                                            ALL,
+                                            new GroupBuckets(newBuckets),
+                                            l3mcastGroupKey,
+                                            l3mcastGroupId,
+                                            nextObj.appId());
         GroupChainElem l3mcastGce = new GroupChainElem(l3mcastGroupDescription,
-                                                       groupInfos.size(), true);
+                                                       groupInfos.size(),
+                                                       true,
+                                                       deviceId);
         groupInfos.forEach(groupInfo -> {
             // update original NextGroup with new bucket-chain
             Deque<GroupKey> newBucketChain = new ArrayDeque<>();
-            newBucketChain.addFirst(groupInfo.innerMostGroupDesc.appCookie());
+            newBucketChain.addFirst(groupInfo.innerMostGroupDesc().appCookie());
             // Add L3 interface group to the chain if there is one.
-            if (!groupInfo.nextGroupDesc.equals(groupInfo.innerMostGroupDesc)) {
-                newBucketChain.addFirst(groupInfo.nextGroupDesc.appCookie());
+            if (!groupInfo.nextGroupDesc().equals(groupInfo.innerMostGroupDesc())) {
+                newBucketChain.addFirst(groupInfo.nextGroupDesc().appCookie());
             }
             newBucketChain.addFirst(l3mcastGroupKey);
             // If active keys shows only the top-level group without a chain of groups,
@@ -1400,21 +1283,23 @@
             }
             allActiveKeys.add(newBucketChain);
 
-            updatePendingGroups(groupInfo.nextGroupDesc.appCookie(), l3mcastGce);
+            updatePendingGroups(groupInfo.nextGroupDesc().appCookie(), l3mcastGce);
             // Point next group to inner-most group, if any
-            if (!groupInfo.nextGroupDesc.equals(groupInfo.innerMostGroupDesc)) {
-                GroupChainElem innerGce = new GroupChainElem(groupInfo.nextGroupDesc,
-                                                             1, false);
-                updatePendingGroups(groupInfo.innerMostGroupDesc.appCookie(), innerGce);
+            if (!groupInfo.nextGroupDesc().equals(groupInfo.innerMostGroupDesc())) {
+                GroupChainElem innerGce = new GroupChainElem(groupInfo.nextGroupDesc(),
+                                                             1,
+                                                             false,
+                                                             deviceId);
+                updatePendingGroups(groupInfo.innerMostGroupDesc().appCookie(), innerGce);
             }
-            log.debug("Adding to L3MCAST: device:{} gid:{} gkey:{} nextId:{}",
+            log.debug("Adding to L3MCAST: device:{} gid:{} group key:{} nextId:{}",
                       deviceId, Integer.toHexString(l3mcastGroupId),
                       l3mcastGroupKey, nextObj.id());
             // send the innermost group
             log.debug("Sending innermost group {} in group chain on device {} ",
-                      Integer.toHexString(groupInfo.innerMostGroupDesc.givenGroupId()),
+                      Integer.toHexString(groupInfo.innerMostGroupDesc().givenGroupId()),
                       deviceId);
-            groupService.addGroup(groupInfo.innerMostGroupDesc);
+            groupService.addGroup(groupInfo.innerMostGroupDesc());
 
         });
 
@@ -1435,7 +1320,7 @@
                 nextObjective.type() != NextObjective.Type.BROADCAST) {
             log.warn("RemoveBuckets not applied to nextType:{} in dev:{} for next:{}",
                     nextObjective.type(), deviceId, nextObjective.id());
-            Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.UNSUPPORTED);
+            fail(nextObjective, ObjectiveError.UNSUPPORTED);
             return;
         }
         Set<PortNumber> portsToRemove = Sets.newHashSet();
@@ -1455,10 +1340,10 @@
         if (portsToRemove.isEmpty()) {
             log.warn("next objective {} has no outport.. cannot remove bucket"
                              + "from group in dev: {}", nextObjective.id(), deviceId);
-            Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.BADPARAMS);
+            fail(nextObjective, ObjectiveError.BADPARAMS);
         }
 
-        List<Deque<GroupKey>> allActiveKeys = Ofdpa2Pipeline.appKryo.deserialize(next.data());
+        List<Deque<GroupKey>> allActiveKeys = appKryo.deserialize(next.data());
         List<Deque<GroupKey>> chainsToRemove = Lists.newArrayList();
         for (Deque<GroupKey> gkeys : allActiveKeys) {
             // last group in group chain should have a single bucket pointing to port
@@ -1485,7 +1370,7 @@
         if (chainsToRemove.isEmpty()) {
             log.warn("Could not find appropriate group-chain for removing bucket"
                     + " for next id {} in dev:{}", nextObjective.id(), deviceId);
-            Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.BADPARAMS);
+            fail(nextObjective, ObjectiveError.BADPARAMS);
             return;
         }
         List<GroupBucket> bucketsToRemove = Lists.newArrayList();
@@ -1555,8 +1440,7 @@
             allActiveKeys.add(top);
         }
         flowObjectiveStore.putNextGroup(nextObjective.id(),
-                                        new OfdpaNextGroup(allActiveKeys,
-                                                           nextObjective));
+                                        new OfdpaNextGroup(allActiveKeys, nextObjective));
     }
 
     /**
@@ -1567,33 +1451,29 @@
      *             this next objective
      */
     protected void removeGroup(NextObjective nextObjective, NextGroup next) {
-        List<Deque<GroupKey>> allActiveKeys = Ofdpa2Pipeline.appKryo.deserialize(next.data());
+        List<Deque<GroupKey>> allActiveKeys = appKryo.deserialize(next.data());
 
         List<GroupKey> groupKeys = allActiveKeys.stream()
                 .map(Deque::getFirst).collect(Collectors.toList());
-        pendingRemoveNextObjectives.put(nextObjective, groupKeys);
+        addPendingRemoveNextObjective(nextObjective, groupKeys);
 
         allActiveKeys.forEach(groupChain -> groupChain.forEach(groupKey ->
                 groupService.removeGroup(deviceId, groupKey, nextObjective.appId())));
         flowObjectiveStore.removeNextGroup(nextObjective.id());
     }
 
-    //////////////////////////////////////
-    //  Helper Methods and Classes
-    //////////////////////////////////////
-
-    protected void updatePendingNextObjective(GroupKey gkey, OfdpaNextGroup value) {
-        pendingAddNextObjectives.asMap().compute(gkey, (k, val) -> {
+    protected void updatePendingNextObjective(GroupKey groupKey, OfdpaNextGroup nextGrp) {
+        pendingAddNextObjectives.asMap().compute(groupKey, (k, val) -> {
             if (val == null) {
-                val = new CopyOnWriteArrayList<OfdpaNextGroup>();
+                val = new CopyOnWriteArrayList<>();
             }
-            val.add(value);
+            val.add(nextGrp);
             return val;
         });
     }
 
-    protected void updatePendingGroups(GroupKey gkey, GroupChainElem gce) {
-        pendingGroups.asMap().compute(gkey, (k, val) -> {
+    protected void updatePendingGroups(GroupKey groupKey, GroupChainElem gce) {
+        pendingGroups.asMap().compute(groupKey, (k, val) -> {
             if (val == null) {
                 val = Sets.newConcurrentHashSet();
             }
@@ -1602,7 +1482,8 @@
         });
     }
 
-    private void addPendingUpdateNextObjective(GroupKey groupKey, NextObjective nextObjective) {
+    protected void addPendingUpdateNextObjective(GroupKey groupKey,
+                                                 NextObjective nextObjective) {
         pendingUpdateNextObjectives.compute(groupKey, (gKey, nextObjs) -> {
             if (nextObjs != null) {
                 nextObjs.add(nextObjective);
@@ -1613,6 +1494,94 @@
         });
     }
 
+    private void processPendingUpdateNextObjs(GroupKey groupKey) {
+        pendingUpdateNextObjectives.compute(groupKey, (gKey, nextObjs) -> {
+            if (nextObjs != null) {
+
+                nextObjs.forEach(nextObj -> {
+                    log.debug("Group {} updated, update pending next objective {}.",
+                              groupKey, nextObj);
+
+                    pass(nextObj);
+                });
+            }
+            return Sets.newHashSet();
+        });
+    }
+
+    private void processPendingRemoveNextObjs(GroupKey key) {
+        pendingRemoveNextObjectives.asMap().forEach((nextObjective, groupKeys) -> {
+            if (groupKeys.isEmpty()) {
+                pendingRemoveNextObjectives.invalidate(nextObjective);
+                pass(nextObjective);
+            } else {
+                groupKeys.remove(key);
+            }
+        });
+    }
+
+    protected int getNextAvailableIndex() {
+        return (int) nextIndex.incrementAndGet();
+    }
+
+    protected Group retrieveTopLevelGroup(List<Deque<GroupKey>> allActiveKeys,
+                                          int nextid) {
+        GroupKey topLevelGroupKey;
+        if (!allActiveKeys.isEmpty()) {
+            topLevelGroupKey = allActiveKeys.get(0).peekFirst();
+        } else {
+            log.warn("Could not determine top level group while processing"
+                             + "next:{} in dev:{}", nextid, deviceId);
+            return null;
+        }
+        Group topGroup = groupService.getGroup(deviceId, topLevelGroupKey);
+        if (topGroup == null) {
+            log.warn("Could not find top level group while processing "
+                             + "next:{} in dev:{}", nextid, deviceId);
+        }
+        return topGroup;
+    }
+
+    protected void processPendingAddGroupsOrNextObjs(GroupKey key, boolean added) {
+        //first check for group chain
+        Set<OfdpaGroupHandlerUtility.GroupChainElem> gceSet = pendingGroups.asMap().remove(key);
+        if (gceSet != null) {
+            for (GroupChainElem gce : gceSet) {
+                log.debug("Group service {} group key {} in device {}. "
+                                  + "Processing next group in group chain with group id 0x{}",
+                          (added) ? "ADDED" : "processed",
+                          key, deviceId,
+                          Integer.toHexString(gce.groupDescription().givenGroupId()));
+                processGroupChain(gce);
+            }
+        } else {
+            // otherwise chain complete - check for waiting nextObjectives
+            List<OfdpaGroupHandlerUtility.OfdpaNextGroup> nextGrpList =
+                    pendingAddNextObjectives.getIfPresent(key);
+            if (nextGrpList != null) {
+                pendingAddNextObjectives.invalidate(key);
+                nextGrpList.forEach(nextGrp -> {
+                    log.debug("Group service {} group key {} in device:{}. "
+                                      + "Done implementing next objective: {} <<-->> gid:0x{}",
+                              (added) ? "ADDED" : "processed",
+                              key, deviceId, nextGrp.nextObjective().id(),
+                              Integer.toHexString(groupService.getGroup(deviceId, key)
+                                                          .givenGroupId()));
+                    pass(nextGrp.nextObjective());
+                    flowObjectiveStore.putNextGroup(nextGrp.nextObjective().id(), nextGrp);
+
+                    // check if addBuckets waiting for this completion
+                    pendingBuckets.compute(nextGrp.nextObjective().id(), (nextId, pendBkts) -> {
+                        if (pendBkts != null) {
+                            pendBkts.forEach(pendBkt -> addBucketToGroup(pendBkt, nextGrp));
+                        }
+                        return null;
+                    });
+                });
+            }
+        }
+    }
+
     /**
      * Processes next element of a group chain. Assumption is that if this
      * group points to another group, the latter has already been created
@@ -1634,38 +1603,20 @@
             return;
         }
         log.debug("GCE: {} ready to be processed", gce);
-        if (gce.addBucketToGroup) {
-            groupService.addBucketsToGroup(gce.groupDescription.deviceId(),
-                    gce.groupDescription.appCookie(),
-                    gce.groupDescription.buckets(),
-                    gce.groupDescription.appCookie(),
-                    gce.groupDescription.appId());
+        if (gce.addBucketToGroup()) {
+            groupService.addBucketsToGroup(gce.groupDescription().deviceId(),
+                                           gce.groupDescription().appCookie(),
+                                           gce.groupDescription().buckets(),
+                                           gce.groupDescription().appCookie(),
+                                           gce.groupDescription().appId());
         } else {
-            groupService.addGroup(gce.groupDescription);
+            groupService.addGroup(gce.groupDescription());
         }
     }
 
-    private class GroupChecker implements Runnable {
-        @Override
-        public void run() {
-            if (pendingGroups.size() != 0) {
-                log.debug("pending groups being checked: {}", pendingGroups.asMap().keySet());
-            }
-            if (pendingAddNextObjectives.size() != 0) {
-                log.debug("pending add-next-obj being checked: {}",
-                          pendingAddNextObjectives.asMap().keySet());
-            }
-            Set<GroupKey> keys = pendingGroups.asMap().keySet().stream()
-                    .filter(key -> groupService.getGroup(deviceId, key) != null)
-                    .collect(Collectors.toSet());
-            Set<GroupKey> otherkeys = pendingAddNextObjectives.asMap().keySet().stream()
-                    .filter(otherkey -> groupService.getGroup(deviceId, otherkey) != null)
-                    .collect(Collectors.toSet());
-            keys.addAll(otherkeys);
-
-            keys.forEach(key ->
-                    processPendingAddGroupsOrNextObjs(key, false));
-        }
+    protected void addPendingRemoveNextObjective(NextObjective nextObjective,
+                                                 List<GroupKey> groupKeys) {
+        pendingRemoveNextObjectives.put(nextObjective, groupKeys);
     }
 
     private class InnerGroupListener implements GroupListener {
@@ -1687,314 +1638,4 @@
             }
         }
     }
-
-    private void processPendingUpdateNextObjs(GroupKey groupKey) {
-        pendingUpdateNextObjectives.compute(groupKey, (gKey, nextObjs) -> {
-            if (nextObjs != null) {
-
-                nextObjs.forEach(nextObj -> {
-                    log.debug("Group {} updated, update pending next objective {}.",
-                              groupKey, nextObj);
-
-                    Ofdpa2Pipeline.pass(nextObj);
-                });
-            }
-            return Sets.newHashSet();
-        });
-    }
-
-    private void processPendingAddGroupsOrNextObjs(GroupKey key, boolean added) {
-        //first check for group chain
-        Set<GroupChainElem> gceSet = pendingGroups.asMap().remove(key);
-        if (gceSet != null) {
-            for (GroupChainElem gce : gceSet) {
-                log.debug("Group service {} group key {} in device {}. "
-                                + "Processing next group in group chain with group id 0x{}",
-                        (added) ? "ADDED" : "processed",
-                        key, deviceId,
-                        Integer.toHexString(gce.groupDescription.givenGroupId()));
-                processGroupChain(gce);
-            }
-        } else {
-            // otherwise chain complete - check for waiting nextObjectives
-            List<OfdpaNextGroup> nextGrpList = pendingAddNextObjectives.getIfPresent(key);
-            if (nextGrpList != null) {
-                pendingAddNextObjectives.invalidate(key);
-                nextGrpList.forEach(nextGrp -> {
-                    log.debug("Group service {} group key {} in device:{}. "
-                                    + "Done implementing next objective: {} <<-->> gid:0x{}",
-                            (added) ? "ADDED" : "processed",
-                            key, deviceId, nextGrp.nextObjective().id(),
-                            Integer.toHexString(groupService.getGroup(deviceId, key)
-                                    .givenGroupId()));
-                    Ofdpa2Pipeline.pass(nextGrp.nextObjective());
-                    flowObjectiveStore.putNextGroup(nextGrp.nextObjective().id(), nextGrp);
-
-                    // check if addBuckets waiting for this completion
-                    pendingBuckets.compute(nextGrp.nextObjective().id(), (nextId, pendBkts) -> {
-                        if (pendBkts != null) {
-                            pendBkts.forEach(pendBkt -> addBucketToGroup(pendBkt, nextGrp));
-                        }
-                        return null;
-                    });
-                });
-            }
-        }
-    }
-
-    private void processPendingRemoveNextObjs(GroupKey key) {
-        pendingRemoveNextObjectives.asMap().forEach((nextObjective, groupKeys) -> {
-            if (groupKeys.isEmpty()) {
-                pendingRemoveNextObjectives.invalidate(nextObjective);
-                Ofdpa2Pipeline.pass(nextObjective);
-            } else {
-                groupKeys.remove(key);
-            }
-        });
-    }
-
-    protected int getNextAvailableIndex() {
-        return (int) nextIndex.incrementAndGet();
-    }
-
-    /**
-     * Returns the outport in a traffic treatment.
-     *
-     * @param tt the treatment
-     * @return the PortNumber for the outport or null
-     */
-    protected static PortNumber readOutPortFromTreatment(TrafficTreatment tt) {
-        for (Instruction ins : tt.allInstructions()) {
-            if (ins.type() == Instruction.Type.OUTPUT) {
-                return ((Instructions.OutputInstruction) ins).port();
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Returns a hash as the L2 Interface Group Key.
-     *
-     * Keep the lower 6-bit for port since port number usually smaller than 64.
-     * Hash other information into remaining 28 bits.
-     *
-     * @param deviceId Device ID
-     * @param vlanId VLAN ID
-     * @param portNumber Port number
-     * @return L2 interface group key
-     */
-    protected int l2InterfaceGroupKey(DeviceId deviceId, VlanId vlanId, long portNumber) {
-        int portLowerBits = (int) portNumber & PORT_LOWER_BITS_MASK;
-        long portHigherBits = portNumber & PORT_HIGHER_BITS_MASK;
-        int hash = Objects.hash(deviceId, vlanId, portHigherBits);
-        return L2_INTERFACE_TYPE | (TYPE_MASK & hash << 6) | portLowerBits;
-    }
-
-    private Group retrieveTopLevelGroup(List<Deque<GroupKey>> allActiveKeys,
-                                        int nextid) {
-        GroupKey topLevelGroupKey;
-        if (!allActiveKeys.isEmpty()) {
-            topLevelGroupKey = allActiveKeys.get(0).peekFirst();
-        } else {
-            log.warn("Could not determine top level group while processing"
-                    + "next:{} in dev:{}", nextid, deviceId);
-            return null;
-        }
-        Group topGroup = groupService.getGroup(deviceId, topLevelGroupKey);
-        if (topGroup == null) {
-            log.warn("Could not find top level group while processing "
-                    + "next:{} in dev:{}", nextid, deviceId);
-        }
-        return topGroup;
-    }
-
-    /**
-     * Utility class for moving group information around.
-     *
-     * Example: Suppose we are trying to create a group-chain A-B-C-D, where
-     * A is the top level group, and D is the inner-most group, typically L2 Interface.
-     * The innerMostGroupDesc is always D. At various stages of the creation
-     * process the nextGroupDesc may be C or B. The nextGroupDesc exists to
-     * inform the referencing group about which group it needs to point to,
-     * and wait for. In some cases the group chain may simply be A-B. In this case,
-     * both innerMostGroupDesc and nextGroupDesc will be B.
-     */
-    protected class GroupInfo {
-        /**
-         * Description of the inner-most group of the group chain.
-         * It is always an L2 interface group.
-         */
-        private GroupDescription innerMostGroupDesc;
-
-        /**
-         * Description of the next group in the group chain.
-         * It can be L2 interface, L3 interface, L3 unicast, L3 VPN group.
-         * It is possible that nextGroupDesc is the same as the innerMostGroup.
-         */
-        private GroupDescription nextGroupDesc;
-
-        GroupInfo(GroupDescription innerMostGroupDesc, GroupDescription nextGroupDesc) {
-            this.innerMostGroupDesc = innerMostGroupDesc;
-            this.nextGroupDesc = nextGroupDesc;
-        }
-
-        /**
-         * Getter for innerMostGroupDesc.
-         *
-         * @return the inner most group description
-         */
-        public GroupDescription getInnerMostGroupDesc() {
-            return innerMostGroupDesc;
-        }
-
-        /**
-         * Getter for the next group description.
-         *
-         * @return the next group description
-         */
-        public GroupDescription getNextGroupDesc() {
-            return nextGroupDesc;
-        }
-    }
-
-    /**
-     * Represents an entire group-chain that implements a Next-Objective from
-     * the application. The objective is represented as a list of deques, where
-     * each deque is a separate chain of groups.
-     * <p>
-     * For example, an ECMP group with 3 buckets, where each bucket points to
-     * a group chain of L3 Unicast and L2 interface groups will look like this:
-     * <ul>
-     * <li>List[0] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
-     * <li>List[1] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
-     * <li>List[2] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
-     * </ul>
-     * where the first element of each deque is the same, representing the
-     * top level ECMP group, while every other element represents a unique groupKey.
-     * <p>
-     * Also includes information about the next objective that
-     * resulted in these group-chains.
-     *
-     */
-    protected class OfdpaNextGroup implements NextGroup {
-        private final NextObjective nextObj;
-        private final List<Deque<GroupKey>> gkeys;
-
-        public OfdpaNextGroup(List<Deque<GroupKey>> gkeys, NextObjective nextObj) {
-            this.nextObj = nextObj;
-            this.gkeys = gkeys;
-        }
-
-        public NextObjective nextObjective() {
-            return nextObj;
-        }
-
-        public List<Deque<GroupKey>> groupKeys() {
-            return gkeys;
-        }
-
-        @Override
-        public byte[] data() {
-            return Ofdpa2Pipeline.appKryo.serialize(gkeys);
-        }
-    }
-
-    /**
-     * Represents a group element that is part of a chain of groups.
-     * Stores enough information to create a Group Description to add the group
-     * to the switch by requesting the Group Service. Objects instantiating this
-     * class are meant to be temporary and live as long as it is needed to wait for
-     * referenced groups in the group chain to be created.
-     */
-    protected class GroupChainElem {
-        private GroupDescription groupDescription;
-        private AtomicInteger waitOnGroups;
-        private boolean addBucketToGroup;
-
-        GroupChainElem(GroupDescription groupDescription, int waitOnGroups,
-                boolean addBucketToGroup) {
-            this.groupDescription = groupDescription;
-            this.waitOnGroups = new AtomicInteger(waitOnGroups);
-            this.addBucketToGroup = addBucketToGroup;
-        }
-
-        /**
-         * This method atomically decrements the counter for the number of
-         * groups this GroupChainElement is waiting on, for notifications from
-         * the Group Service. When this method returns a value of 0, this
-         * GroupChainElement is ready to be processed.
-         *
-         * @return integer indication of the number of notifications being waited on
-         */
-        int decrementAndGetGroupsWaitedOn() {
-            return waitOnGroups.decrementAndGet();
-        }
-
-        @Override
-        public String toString() {
-            return (Integer.toHexString(groupDescription.givenGroupId()) +
-                    " groupKey: " + groupDescription.appCookie() +
-                    " waiting-on-groups: " + waitOnGroups.get() +
-                    " addBucketToGroup: " + addBucketToGroup +
-                    " device: " + deviceId);
-        }
-    }
-
-    /**
-     * Helper enum to handle the different MPLS group
-     * types.
-     */
-    protected enum OfdpaMplsGroupSubType {
-        MPLS_INTF((short) 0),
-        L2_VPN((short) 1),
-        L3_VPN((short) 2),
-        MPLS_TUNNEL_LABEL_1((short) 3),
-        MPLS_TUNNEL_LABEL_2((short) 4),
-        MPLS_SWAP_LABEL((short) 5),
-        MPLS_ECMP((short) 8);
-
-        private short value;
-        public static final int OFDPA_GROUP_TYPE_SHIFT = 28;
-        public static final int OFDPA_MPLS_SUBTYPE_SHIFT = 24;
-
-        OfdpaMplsGroupSubType(short value) {
-            this.value = value;
-        }
-
-        /**
-         * Gets the value as an short.
-         *
-         * @return the value as an short
-         */
-        public short getValue() {
-            return this.value;
-        }
-
-    }
-
-    /**
-     * Creates MPLS Label group id given a sub type and
-     * the index.
-     *
-     * @param subType the MPLS Label group sub type
-     * @param index the index of the group
-     * @return the OFDPA group id
-     */
-    public Integer makeMplsLabelGroupId(OfdpaMplsGroupSubType subType, int index) {
-        index = index & 0x00FFFFFF;
-        return index | (9 << OFDPA_GROUP_TYPE_SHIFT) | (subType.value << OFDPA_MPLS_SUBTYPE_SHIFT);
-    }
-
-    /**
-     * Creates MPLS Forwarding group id given a sub type and
-     * the index.
-     *
-     * @param subType the MPLS forwarding group sub type
-     * @param index the index of the group
-     * @return the OFDPA group id
-     */
-    public Integer makeMplsForwardingGroupId(OfdpaMplsGroupSubType subType, int index) {
-        index = index & 0x00FFFFFF;
-        return index | (10 << OFDPA_GROUP_TYPE_SHIFT) | (subType.value << OFDPA_MPLS_SUBTYPE_SHIFT);
-    }
 }
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa2Pipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2Pipeline.java
similarity index 98%
rename from drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa2Pipeline.java
rename to drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2Pipeline.java
index 77efbe6..1abe86c 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa2Pipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2Pipeline.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.onosproject.driver.pipeline;
+package org.onosproject.driver.pipeline.ofdpa;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
@@ -91,13 +91,13 @@
 import static org.onlab.packet.MacAddress.BROADCAST;
 import static org.onlab.packet.MacAddress.NONE;
 import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.driver.pipeline.ofdpa.OfdpaGroupHandlerUtility.*;
 import static org.slf4j.LoggerFactory.getLogger;
 import static org.onosproject.net.flow.criteria.Criterion.Type.MPLS_BOS;
 import static org.onosproject.net.flowobjective.NextObjective.Type.HASHED;
 
 /**
  * Driver for Broadcom's OF-DPA v2.0 TTP.
- *
  */
 public class Ofdpa2Pipeline extends AbstractHandlerBehaviour implements Pipeliner {
 
@@ -142,12 +142,12 @@
     protected ApplicationId driverId;
     protected DeviceService deviceService;
     protected static KryoNamespace appKryo = new KryoNamespace.Builder()
-        .register(KryoNamespaces.API)
-        .register(GroupKey.class)
-        .register(DefaultGroupKey.class)
-        .register(Ofdpa2GroupHandler.OfdpaNextGroup.class)
-        .register(ArrayDeque.class)
-        .build("Ofdpa2Pipeline");
+            .register(KryoNamespaces.API)
+            .register(GroupKey.class)
+            .register(DefaultGroupKey.class)
+            .register(OfdpaNextGroup.class)
+            .register(ArrayDeque.class)
+            .build("Ofdpa2Pipeline");
 
     protected Ofdpa2GroupHandler groupHandler;
 
@@ -493,9 +493,9 @@
      * @return list of FlowRule for port-vlan filters
      */
     protected List<FlowRule> processVlanIdFilter(PortCriterion portCriterion,
-                                                         VlanIdCriterion vidCriterion,
-                                                         VlanId assignedVlan,
-                                                         ApplicationId applicationId) {
+                                                 VlanIdCriterion vidCriterion,
+                                                 VlanId assignedVlan,
+                                                 ApplicationId applicationId) {
         List<FlowRule> rules = new ArrayList<>();
         TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
         TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
@@ -706,7 +706,7 @@
     }
 
     protected List<FlowRule> processEthDstOnlyFilter(EthCriterion ethCriterion,
-            ApplicationId applicationId) {
+                                                     ApplicationId applicationId) {
         ImmutableList.Builder<FlowRule> builder = ImmutableList.builder();
 
         TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
@@ -741,7 +741,7 @@
     }
 
     protected List<FlowRule> processMcastEthDstFilter(EthCriterion ethCriterion,
-            ApplicationId applicationId) {
+                                                      ApplicationId applicationId) {
         TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
         TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
         selector.matchEthType(Ethernet.TYPE_IPV4);
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa3GroupHandler.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3GroupHandler.java
similarity index 88%
rename from drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa3GroupHandler.java
rename to drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3GroupHandler.java
index ca7e085..14f069f 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa3GroupHandler.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3GroupHandler.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.onosproject.driver.pipeline;
+package org.onosproject.driver.pipeline.ofdpa;
 
 import com.google.common.collect.Lists;
 import org.onlab.packet.VlanId;
@@ -44,7 +44,7 @@
 import java.util.Deque;
 import java.util.List;
 
-import static org.onosproject.driver.pipeline.Ofdpa2GroupHandler.OfdpaMplsGroupSubType.*;
+import static org.onosproject.driver.pipeline.ofdpa.OfdpaGroupHandlerUtility.*;
 import static org.onosproject.net.flow.instructions.L3ModificationInstruction.L3SubType.TTL_OUT;
 import static org.onosproject.net.group.GroupDescription.Type.INDIRECT;
 import static org.slf4j.LoggerFactory.getLogger;
@@ -93,8 +93,8 @@
             return;
         }
         // We update the chain with the last two groups;
-        gkeyChain.addFirst(groupInfo.getInnerMostGroupDesc().appCookie());
-        gkeyChain.addFirst(groupInfo.getNextGroupDesc().appCookie());
+        gkeyChain.addFirst(groupInfo.innerMostGroupDesc().appCookie());
+        gkeyChain.addFirst(groupInfo.nextGroupDesc().appCookie());
         // We retrieve also all mpls instructions.
         List<List<Instruction>> mplsInstructionSets = Lists.newArrayList();
         List<Instruction> mplsInstructionSet = Lists.newArrayList();
@@ -119,7 +119,7 @@
             Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.BADPARAMS);
             return;
         }
-        int nextGid = groupInfo.getNextGroupDesc().givenGroupId();
+        int nextGid = groupInfo.nextGroupDesc().givenGroupId();
         int index;
         // We create the mpls tunnel label groups.
         // In this case we need to use also the
@@ -129,7 +129,7 @@
             index = getNextAvailableIndex();
             groupDescription = createMplsTunnelLabelGroup(
                     nextGid,
-                    MPLS_TUNNEL_LABEL_2,
+                    OfdpaMplsGroupSubType.MPLS_TUNNEL_LABEL_2,
                     index,
                     mplsInstructionSets.get(2),
                     nextObjective.appId()
@@ -138,9 +138,9 @@
                     Ofdpa2Pipeline.appKryo.serialize(index)
             );
             // We update the chain.
-            groupChainElem = new GroupChainElem(groupDescription, 1, false);
+            groupChainElem = new GroupChainElem(groupDescription, 1, false, deviceId);
             updatePendingGroups(
-                    groupInfo.getNextGroupDesc().appCookie(),
+                    groupInfo.nextGroupDesc().appCookie(),
                     groupChainElem
             );
             gkeyChain.addFirst(groupKey);
@@ -148,7 +148,7 @@
             // l2 vpn group before to send the inner most
             // group. We update the nextGid.
             nextGid = groupDescription.givenGroupId();
-            groupInfo = new GroupInfo(groupInfo.getInnerMostGroupDesc(), groupDescription);
+            groupInfo = new GroupInfo(groupInfo.innerMostGroupDesc(), groupDescription);
 
             log.debug("Trying Label 2 Group: device:{} gid:{} gkey:{} nextId:{}",
                       deviceId, Integer.toHexString(nextGid),
@@ -158,7 +158,7 @@
         index = getNextAvailableIndex();
         groupDescription = createMplsTunnelLabelGroup(
                 nextGid,
-                MPLS_TUNNEL_LABEL_1,
+                OfdpaMplsGroupSubType.MPLS_TUNNEL_LABEL_1,
                 index,
                 mplsInstructionSets.get(1),
                 nextObjective.appId()
@@ -166,16 +166,16 @@
         groupKey = new DefaultGroupKey(
                 Ofdpa2Pipeline.appKryo.serialize(index)
         );
-        groupChainElem = new GroupChainElem(groupDescription, 1, false);
+        groupChainElem = new GroupChainElem(groupDescription, 1, false, deviceId);
         updatePendingGroups(
-                groupInfo.getNextGroupDesc().appCookie(),
+                groupInfo.nextGroupDesc().appCookie(),
                 groupChainElem
         );
         gkeyChain.addFirst(groupKey);
         // We have to create the l2 vpn group before
         // to send the inner most group.
         nextGid = groupDescription.givenGroupId();
-        groupInfo = new GroupInfo(groupInfo.getInnerMostGroupDesc(), groupDescription);
+        groupInfo = new GroupInfo(groupInfo.innerMostGroupDesc(), groupDescription);
 
         log.debug("Trying Label 1 Group: device:{} gid:{} gkey:{} nextId:{}",
                   deviceId, Integer.toHexString(nextGid),
@@ -191,9 +191,9 @@
         groupKey = new DefaultGroupKey(
                 Ofdpa2Pipeline.appKryo.serialize(index)
         );
-        groupChainElem = new GroupChainElem(groupDescription, 1, false);
+        groupChainElem = new GroupChainElem(groupDescription, 1, false, deviceId);
         updatePendingGroups(
-                groupInfo.getNextGroupDesc().appCookie(),
+                groupInfo.nextGroupDesc().appCookie(),
                 groupChainElem
         );
         gkeyChain.addFirst(groupKey);
@@ -208,8 +208,8 @@
                   groupKey, nextObjective.id());
         // Finally we send the innermost group.
         log.debug("Sending innermost group {} in group chain on device {} ",
-                  Integer.toHexString(groupInfo.getInnerMostGroupDesc().givenGroupId()), deviceId);
-        groupService.addGroup(groupInfo.getInnerMostGroupDesc());
+                  Integer.toHexString(groupInfo.innerMostGroupDesc().givenGroupId()), deviceId);
+        groupService.addGroup(groupInfo.innerMostGroupDesc());
     }
 
     /**
@@ -223,10 +223,10 @@
      * @return the group description
      */
     private GroupDescription createMplsTunnelLabelGroup(int nextGroupId,
-                                                          OfdpaMplsGroupSubType subtype,
-                                                          int index,
-                                                          List<Instruction> instructions,
-                                                          ApplicationId applicationId) {
+                                                        OfdpaMplsGroupSubType subtype,
+                                                        int index,
+                                                        List<Instruction> instructions,
+                                                        ApplicationId applicationId) {
         TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
         // We add all the instructions.
         instructions.forEach(treatment::add);
@@ -259,9 +259,9 @@
      * @return the group description
      */
     private GroupDescription createMplsL2VpnGroup(int nextGroupId,
-                                                    int index,
-                                                    List<Instruction> instructions,
-                                                    ApplicationId applicationId) {
+                                                  int index,
+                                                  List<Instruction> instructions,
+                                                  ApplicationId applicationId) {
         TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
         // We add the extensions and the instructions.
         treatment.extension(new Ofdpa3PushL2Header(), deviceId);
@@ -273,7 +273,7 @@
         GroupBucket groupBucket = DefaultGroupBucket
                 .createIndirectGroupBucket(treatment.build());
         // Finally we build the group description.
-        int groupId = makeMplsLabelGroupId(L2_VPN, index);
+        int groupId = makeMplsLabelGroupId(OfdpaMplsGroupSubType.L2_VPN, index);
         GroupKey groupKey = new DefaultGroupKey(
                 Ofdpa2Pipeline.appKryo.serialize(index)
         );
@@ -296,8 +296,8 @@
      * @param mplsTreatment the mpls treatment builder
      */
     private void createL2L3AndMplsTreatments(TrafficTreatment treatment,
-                                               TrafficTreatment.Builder l2L3Treatment,
-                                               TrafficTreatment.Builder mplsTreatment) {
+                                             TrafficTreatment.Builder l2L3Treatment,
+                                             TrafficTreatment.Builder mplsTreatment) {
 
         for (Instruction ins : treatment.allInstructions()) {
 
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa3Pipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3Pipeline.java
similarity index 98%
rename from drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa3Pipeline.java
rename to drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3Pipeline.java
index 5588228..4ce533c 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa3Pipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3Pipeline.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.onosproject.driver.pipeline;
+package org.onosproject.driver.pipeline.ofdpa;
 
 import com.google.common.collect.ImmutableList;
 import org.onosproject.core.ApplicationId;
@@ -207,10 +207,10 @@
      * @return a list of flow rules to install
      */
     private List<FlowRule> processPwFilter(PortCriterion portCriterion,
-                                             VlanIdCriterion innerVlanIdCriterion,
-                                             VlanIdCriterion outerVlanIdCriterion,
-                                             long tunnelId,
-                                             ApplicationId applicationId) {
+                                           VlanIdCriterion innerVlanIdCriterion,
+                                           VlanIdCriterion outerVlanIdCriterion,
+                                           long tunnelId,
+                                           ApplicationId applicationId) {
         // As first we create the flow rule for the vlan 1 table.
         FlowRule vlan1FlowRule;
         int mplsLogicalPort = ((int) portCriterion.port().toLong());
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa3QmxPipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3QmxPipeline.java
similarity index 97%
rename from drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa3QmxPipeline.java
rename to drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3QmxPipeline.java
index 5790321..8230581 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa3QmxPipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3QmxPipeline.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016-present Open Networking Laboratory
+ * Copyright 2017-present Open Networking Laboratory
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.onosproject.driver.pipeline;
+package org.onosproject.driver.pipeline.ofdpa;
 
 import static org.slf4j.LoggerFactory.getLogger;
 
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/OfdpaGroupHandlerUtility.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/OfdpaGroupHandlerUtility.java
new file mode 100644
index 0000000..a058923
--- /dev/null
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/OfdpaGroupHandlerUtility.java
@@ -0,0 +1,479 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * 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.driver.pipeline.ofdpa;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.behaviour.NextGroup;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.flowobjective.NextObjective;
+import org.onosproject.net.group.DefaultGroupBucket;
+import org.onosproject.net.group.DefaultGroupKey;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.group.GroupService;
+import org.slf4j.Logger;
+
+import java.util.Deque;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+
+import static org.onosproject.driver.pipeline.ofdpa.Ofdpa2Pipeline.isNotMplsBos;
+import static org.onosproject.driver.pipeline.ofdpa.OfdpaGroupHandlerUtility.OfdpaMplsGroupSubType.OFDPA_GROUP_TYPE_SHIFT;
+import static org.onosproject.driver.pipeline.ofdpa.OfdpaGroupHandlerUtility.OfdpaMplsGroupSubType.OFDPA_MPLS_SUBTYPE_SHIFT;
+import static org.onosproject.net.flowobjective.NextObjective.Type.HASHED;
+import static org.slf4j.LoggerFactory.getLogger;
+
+public final class OfdpaGroupHandlerUtility {
+    /*
+     * OFDPA requires group-id's to have a certain form.
+     * L2 Interface Groups have <4bits-0><12bits-vlanId><16bits-portId>
+     * L3 Unicast Groups have <4bits-2><28bits-index>
+     * MPLS Interface Groups have <4bits-9><4bits:0><24bits-index>
+     * L3 ECMP Groups have <4bits-7><28bits-index>
+     * L2 Flood Groups have <4bits-4><12bits-vlanId><16bits-index>
+     * L3 VPN Groups have <4bits-9><4bits-2><24bits-index>
+     */
+    protected static final int L2_INTERFACE_TYPE = 0x00000000;
+    protected static final int L3_INTERFACE_TYPE = 0x50000000;
+    protected static final int L3_UNICAST_TYPE = 0x20000000;
+    protected static final int L3_MULTICAST_TYPE = 0x60000000;
+    protected static final int MPLS_INTERFACE_TYPE = 0x90000000;
+    protected static final int MPLS_L3VPN_SUBTYPE = 0x92000000;
+    protected static final int L3_ECMP_TYPE = 0x70000000;
+    protected static final int L2_FLOOD_TYPE = 0x40000000;
+
+    protected static final int TYPE_MASK = 0x0fffffff;
+    protected static final int SUBTYPE_MASK = 0x00ffffff;
+    protected static final int TYPE_VLAN_MASK = 0x0000ffff;
+
+    protected static final int THREE_BIT_MASK = 0x0fff;
+    protected static final int FOUR_BIT_MASK = 0xffff;
+    protected static final int PORT_LEN = 16;
+
+    protected static final int PORT_LOWER_BITS_MASK = 0x3f;
+    protected static final long PORT_HIGHER_BITS_MASK = ~PORT_LOWER_BITS_MASK;
+
+    protected static final String HEX_PREFIX = "0x";
+    protected static final Logger log = getLogger(OfdpaGroupHandlerUtility.class);
+
+    private OfdpaGroupHandlerUtility() {
+        // Utility classes should not have a public or default constructor.
+    }
+
+    /**
+     * Returns the outport in a traffic treatment.
+     *
+     * @param tt the treatment
+     * @return the PortNumber for the outport or null
+     */
+    protected static PortNumber readOutPortFromTreatment(TrafficTreatment tt) {
+        for (Instruction ins : tt.allInstructions()) {
+            if (ins.type() == Instruction.Type.OUTPUT) {
+                return ((Instructions.OutputInstruction) ins).port();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Helper enum to handle the different MPLS group
+     * types.
+     */
+    public enum OfdpaMplsGroupSubType {
+        MPLS_INTF((short) 0),
+        L2_VPN((short) 1),
+        L3_VPN((short) 2),
+        MPLS_TUNNEL_LABEL_1((short) 3),
+        MPLS_TUNNEL_LABEL_2((short) 4),
+        MPLS_SWAP_LABEL((short) 5),
+        MPLS_ECMP((short) 8);
+
+        private short value;
+        public static final int OFDPA_GROUP_TYPE_SHIFT = 28;
+        public static final int OFDPA_MPLS_SUBTYPE_SHIFT = 24;
+
+        OfdpaMplsGroupSubType(short value) {
+            this.value = value;
+        }
+
+        /**
+         * Gets the value as an short.
+         *
+         * @return the value as an short
+         */
+        public short getValue() {
+            return this.value;
+        }
+
+    }
+
+    /**
+     * Creates MPLS Label group id given a sub type and
+     * the index.
+     *
+     * @param subType the MPLS Label group sub type
+     * @param index the index of the group
+     * @return the OFDPA group id
+     */
+    public static Integer makeMplsLabelGroupId(OfdpaMplsGroupSubType subType, int index) {
+        index = index & 0x00FFFFFF;
+        return index | (9 << OFDPA_GROUP_TYPE_SHIFT) | (subType.value << OFDPA_MPLS_SUBTYPE_SHIFT);
+    }
+
+    /**
+     * Creates MPLS Forwarding group id given a sub type and
+     * the index.
+     *
+     * @param subType the MPLS forwarding group sub type
+     * @param index the index of the group
+     * @return the OFDPA group id
+     */
+    public static Integer makeMplsForwardingGroupId(OfdpaMplsGroupSubType subType, int index) {
+        index = index & 0x00FFFFFF;
+        return index | (10 << OFDPA_GROUP_TYPE_SHIFT) | (subType.value << OFDPA_MPLS_SUBTYPE_SHIFT);
+    }
+
+    /**
+     * Gets duplicated output ports between group key chains and existing groups
+     * in the device.
+     *
+     * @param allActiveKeys list of group key chain
+     * @param groupService the group service to get group information
+     * @param deviceId the device id to get group
+     * @return a set of output port from the list of group key chain
+     */
+    public static Set<PortNumber> getExistingOutputPorts(List<Deque<GroupKey>> allActiveKeys,
+                                                     GroupService groupService,
+                                                     DeviceId deviceId) {
+        Set<PortNumber> existingPorts = Sets.newHashSet();
+
+        allActiveKeys.forEach(keyChain -> {
+            GroupKey ifaceGroupKey = keyChain.peekLast();
+            Group ifaceGroup = groupService.getGroup(deviceId, ifaceGroupKey);
+            if (ifaceGroup != null && !ifaceGroup.buckets().buckets().isEmpty()) {
+                ifaceGroup.buckets().buckets().forEach(bucket -> {
+                    PortNumber portNumber = readOutPortFromTreatment(bucket.treatment());
+                    if (portNumber != null) {
+                        existingPorts.add(portNumber);
+                    }
+                });
+            }
+        });
+        return existingPorts;
+    }
+
+    /**
+     * The purpose of this function is to verify if the hashed next
+     * objective is supported by the current pipeline.
+     *
+     * @param nextObjective the hashed objective to verify
+     * @return true if the hashed objective is supported. Otherwise false.
+     */
+    public static boolean verifyHashedNextObjective(NextObjective nextObjective) {
+        // if it is not hashed, there is something wrong;
+        if (nextObjective.type() != HASHED) {
+            return false;
+        }
+        // The case non supported is the MPLS-ECMP. For now, we try
+        // to create a MPLS-ECMP for the transport of a VPWS. The
+        // necessary info are contained in the meta selector. In particular
+        // we are looking for the case of BoS==False;
+        TrafficSelector metaSelector = nextObjective.meta();
+        if (metaSelector != null && isNotMplsBos(metaSelector)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Generates a list of group buckets from given list of group information
+     * and group bucket type.
+     *
+     * @param groupInfos a list of group information
+     * @param bucketType group bucket type
+     * @return list of group bucket generate from group information
+     */
+    protected static List<GroupBucket> generateNextGroupBuckets(List<GroupInfo> groupInfos,
+                                                       GroupDescription.Type bucketType) {
+        List<GroupBucket> newBuckets = Lists.newArrayList();
+
+        groupInfos.forEach(groupInfo -> {
+            GroupDescription groupDesc = groupInfo.nextGroupDesc();
+            TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
+            treatmentBuilder.group(new GroupId(groupDesc.givenGroupId()));
+            GroupBucket newBucket = null;
+            switch (bucketType) {
+                case ALL:
+                    newBucket =
+                            DefaultGroupBucket.createAllGroupBucket(treatmentBuilder.build());
+                    break;
+                case INDIRECT:
+                    newBucket =
+                            DefaultGroupBucket.createIndirectGroupBucket(treatmentBuilder.build());
+                    break;
+                case SELECT:
+                    newBucket =
+                            DefaultGroupBucket.createSelectGroupBucket(treatmentBuilder.build());
+                    break;
+                case FAILOVER:
+                    // TODO: support failover bucket type
+                default:
+                    log.warn("Unknown bucket type: {}", bucketType);
+                    break;
+            }
+
+            if (newBucket != null) {
+                newBuckets.add(newBucket);
+            }
+
+        });
+
+        return ImmutableList.copyOf(newBuckets);
+    }
+
+    /**
+     * Extracts VlanId from given group ID.
+     *
+     * @param groupId the group ID
+     * @return vlan id of the group
+     */
+    public static VlanId extractVlanIdFromGroupId(int groupId) {
+        // Extract the 9th to 20th bit from group id as vlan id.
+        short vlanId = (short) ((groupId & 0x0fff0000) >> 16);
+        return VlanId.vlanId(vlanId);
+    }
+
+    public static GroupKey l2FloodGroupKey(VlanId vlanId, DeviceId deviceId) {
+        int hash = Objects.hash(deviceId, vlanId);
+        hash = L2_FLOOD_TYPE | TYPE_MASK & hash;
+        return new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(hash));
+    }
+
+    public static int l2GroupId(VlanId vlanId, long portNum) {
+        return L2_INTERFACE_TYPE | (vlanId.toShort() << 16) | (int) portNum;
+    }
+
+    /**
+     * Returns a hash as the L2 Interface Group Key.
+     *
+     * Keep the lower 6-bit for port since port number usually smaller than 64.
+     * Hash other information into remaining 28 bits.
+     *
+     * @param deviceId Device ID
+     * @param vlanId VLAN ID
+     * @param portNumber Port number
+     * @return L2 interface group key
+     */
+    public static int l2InterfaceGroupKey(DeviceId deviceId, VlanId vlanId, long portNumber) {
+        int portLowerBits = (int) portNumber & PORT_LOWER_BITS_MASK;
+        long portHigherBits = portNumber & PORT_HIGHER_BITS_MASK;
+        int hash = Objects.hash(deviceId, vlanId, portHigherBits);
+        return L2_INTERFACE_TYPE | (TYPE_MASK & hash << 6) | portLowerBits;
+    }
+
+    /**
+     * Utility class for moving group information around.
+     *
+     * Example: Suppose we are trying to create a group-chain A-B-C-D, where
+     * A is the top level group, and D is the inner-most group, typically L2 Interface.
+     * The innerMostGroupDesc is always D. At various stages of the creation
+     * process the nextGroupDesc may be C or B. The nextGroupDesc exists to
+     * inform the referencing group about which group it needs to point to,
+     * and wait for. In some cases the group chain may simply be A-B. In this case,
+     * both innerMostGroupDesc and nextGroupDesc will be B.
+     */
+    public static class GroupInfo {
+        /**
+         * Description of the inner-most group of the group chain.
+         * It is always an L2 interface group.
+         */
+        private GroupDescription innerMostGroupDesc;
+
+        /**
+         * Description of the next group in the group chain.
+         * It can be L2 interface, L3 interface, L3 unicast, L3 VPN group.
+         * It is possible that nextGroupDesc is the same as the innerMostGroup.
+         */
+        private GroupDescription nextGroupDesc;
+
+        GroupInfo(GroupDescription innerMostGroupDesc, GroupDescription nextGroupDesc) {
+            this.innerMostGroupDesc = innerMostGroupDesc;
+            this.nextGroupDesc = nextGroupDesc;
+        }
+
+        /**
+         * Getter for innerMostGroupDesc.
+         *
+         * @return the inner most group description
+         */
+        public GroupDescription innerMostGroupDesc() {
+            return innerMostGroupDesc;
+        }
+
+        /**
+         * Getter for the next group description.
+         *
+         * @return the next group description
+         */
+        public GroupDescription nextGroupDesc() {
+            return nextGroupDesc;
+        }
+
+        /**
+         * Setter of nextGroupDesc.
+         *
+         * @param nextGroupDesc the given value to set
+         */
+        public void nextGroupDesc(GroupDescription nextGroupDesc) {
+            this.nextGroupDesc = nextGroupDesc;
+        }
+    }
+
+    /**
+     * Represents an entire group-chain that implements a Next-Objective from
+     * the application. The objective is represented as a list of deques, where
+     * each deque is a separate chain of groups.
+     * <p>
+     * For example, an ECMP group with 3 buckets, where each bucket points to
+     * a group chain of L3 Unicast and L2 interface groups will look like this:
+     * <ul>
+     * <li>List[0] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
+     * <li>List[1] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
+     * <li>List[2] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
+     * </ul>
+     * where the first element of each deque is the same, representing the
+     * top level ECMP group, while every other element represents a unique groupKey.
+     * <p>
+     * Also includes information about the next objective that
+     * resulted in these group-chains.
+     *
+     */
+    public static class OfdpaNextGroup implements NextGroup {
+        private final NextObjective nextObj;
+        private final List<Deque<GroupKey>> gkeys;
+
+        public OfdpaNextGroup(List<Deque<GroupKey>> gkeys, NextObjective nextObj) {
+            this.nextObj = nextObj;
+            this.gkeys = gkeys;
+        }
+
+        public NextObjective nextObjective() {
+            return nextObj;
+        }
+
+        @Override
+        public byte[] data() {
+            return Ofdpa2Pipeline.appKryo.serialize(gkeys);
+        }
+    }
+
+    /**
+     * Represents a group element that is part of a chain of groups.
+     * Stores enough information to create a Group Description to add the group
+     * to the switch by requesting the Group Service. Objects instantiating this
+     * class are meant to be temporary and live as long as it is needed to wait for
+     * referenced groups in the group chain to be created.
+     */
+    public static class GroupChainElem {
+        private GroupDescription groupDescription;
+        private AtomicInteger waitOnGroups;
+        private boolean addBucketToGroup;
+        private DeviceId deviceId;
+
+        public GroupChainElem(GroupDescription groupDescription, int waitOnGroups,
+                       boolean addBucketToGroup, DeviceId deviceId) {
+            this.groupDescription = groupDescription;
+            this.waitOnGroups = new AtomicInteger(waitOnGroups);
+            this.addBucketToGroup = addBucketToGroup;
+            this.deviceId = deviceId;
+        }
+
+        /**
+         * This method atomically decrements the counter for the number of
+         * groups this GroupChainElement is waiting on, for notifications from
+         * the Group Service. When this method returns a value of 0, this
+         * GroupChainElement is ready to be processed.
+         *
+         * @return integer indication of the number of notifications being waited on
+         */
+        int decrementAndGetGroupsWaitedOn() {
+            return waitOnGroups.decrementAndGet();
+        }
+
+        public GroupDescription groupDescription() {
+            return groupDescription;
+        }
+
+        public boolean addBucketToGroup() {
+            return addBucketToGroup;
+        }
+
+        @Override
+        public String toString() {
+            return (Integer.toHexString(groupDescription.givenGroupId()) +
+                    " groupKey: " + groupDescription.appCookie() +
+                    " waiting-on-groups: " + waitOnGroups.get() +
+                    " addBucketToGroup: " + addBucketToGroup +
+                    " device: " + deviceId);
+        }
+    }
+
+    public static class GroupChecker implements Runnable {
+        protected final Logger log = getLogger(getClass());
+        private Ofdpa2GroupHandler groupHandler;
+
+        public GroupChecker(Ofdpa2GroupHandler groupHandler) {
+            this.groupHandler = groupHandler;
+        }
+
+        @Override
+        public void run() {
+            if (groupHandler.pendingGroups().size() != 0) {
+                log.debug("pending groups being checked: {}", groupHandler.pendingGroups().asMap().keySet());
+            }
+            if (groupHandler.pendingAddNextObjectives().size() != 0) {
+                log.debug("pending add-next-obj being checked: {}",
+                          groupHandler.pendingAddNextObjectives().asMap().keySet());
+            }
+            Set<GroupKey> keys = groupHandler.pendingGroups().asMap().keySet().stream()
+                    .filter(key -> groupHandler.groupService.getGroup(groupHandler.deviceId, key) != null)
+                    .collect(Collectors.toSet());
+            Set<GroupKey> otherkeys = groupHandler.pendingAddNextObjectives().asMap().keySet().stream()
+                    .filter(otherkey -> groupHandler.groupService.getGroup(groupHandler.deviceId, otherkey) != null)
+                    .collect(Collectors.toSet());
+            keys.addAll(otherkeys);
+
+            keys.forEach(key -> groupHandler.processPendingAddGroupsOrNextObjs(key, false));
+        }
+    }
+}
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/OvsOfdpa2GroupHandler.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/OvsOfdpa2GroupHandler.java
similarity index 95%
rename from drivers/default/src/main/java/org/onosproject/driver/pipeline/OvsOfdpa2GroupHandler.java
rename to drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/OvsOfdpa2GroupHandler.java
index b5b0a98..9a013e0 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/OvsOfdpa2GroupHandler.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/OvsOfdpa2GroupHandler.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.onosproject.driver.pipeline;
+package org.onosproject.driver.pipeline.ofdpa;
 
 /**
  * Group handler that emulates Broadcom OF-DPA TTP on OVS.
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/OvsOfdpa2Pipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/OvsOfdpa2Pipeline.java
similarity index 97%
rename from drivers/default/src/main/java/org/onosproject/driver/pipeline/OvsOfdpa2Pipeline.java
rename to drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/OvsOfdpa2Pipeline.java
index ecf2400..b0e3d9f 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/OvsOfdpa2Pipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/OvsOfdpa2Pipeline.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.onosproject.driver.pipeline;
+package org.onosproject.driver.pipeline.ofdpa;
 
 import org.onosproject.net.behaviour.PipelinerContext;
 
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/package-info.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/package-info.java
new file mode 100644
index 0000000..17ca56a
--- /dev/null
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/**
+ * Pipelines for OF-DPA.
+ */
+package org.onosproject.driver.pipeline.ofdpa;
\ No newline at end of file
diff --git a/drivers/default/src/main/resources/onos-drivers.xml b/drivers/default/src/main/resources/onos-drivers.xml
index 5b00408..59144a5 100644
--- a/drivers/default/src/main/resources/onos-drivers.xml
+++ b/drivers/default/src/main/resources/onos-drivers.xml
@@ -77,7 +77,7 @@
     <driver name="ofdpa" extends="default"
             manufacturer="Broadcom Corp." hwVersion="OF-DPA i12_1.7" swVersion="OF-DPA i12_1.7">
         <behaviour api="org.onosproject.net.behaviour.Pipeliner"
-                   impl="org.onosproject.driver.pipeline.Ofdpa2Pipeline"/>
+                   impl="org.onosproject.driver.pipeline.ofdpa.Ofdpa2Pipeline"/>
         <behaviour api="org.onosproject.openflow.controller.ExtensionTreatmentInterpreter"
                    impl="org.onosproject.driver.extensions.OfdpaExtensionTreatmentInterpreter" />
         <behaviour api="org.onosproject.net.behaviour.ExtensionTreatmentResolver"
@@ -94,7 +94,7 @@
     <driver name="ofdpa3" extends="default"
             manufacturer="Broadcom Corp." hwVersion="OF-DPA 2.0" swVersion="OF-DPA 2.0">
         <behaviour api="org.onosproject.net.behaviour.Pipeliner"
-                   impl="org.onosproject.driver.pipeline.Ofdpa3Pipeline"/>
+                   impl="org.onosproject.driver.pipeline.ofdpa.Ofdpa3Pipeline"/>
         <behaviour api="org.onosproject.openflow.controller.ExtensionTreatmentInterpreter"
                    impl="org.onosproject.driver.extensions.Ofdpa3ExtensionTreatmentInterpreter" />
         <behaviour api="org.onosproject.net.behaviour.ExtensionTreatmentResolver"
@@ -111,13 +111,13 @@
     <driver name="qmx-ofdpa3" extends="ofdpa3"
             manufacturer="Broadcom Corp." hwVersion="Qmx" swVersion="Qmx">
             <behaviour api="org.onosproject.net.behaviour.Pipeliner"
-                   impl="org.onosproject.driver.pipeline.Ofdpa3QmxPipeline"/>
+                   impl="org.onosproject.driver.pipeline.ofdpa.Ofdpa3QmxPipeline"/>
     </driver>
 
     <driver name="znyx-ofdpa" extends="default"
             manufacturer="ZNYX Networks" hwVersion=".*" swVersion=".*OF-DPA.*">
         <behaviour api="org.onosproject.net.behaviour.Pipeliner"
-                   impl="org.onosproject.driver.pipeline.Ofdpa3Pipeline"/>
+                   impl="org.onosproject.driver.pipeline.ofdpa.Ofdpa3Pipeline"/>
         <behaviour api="org.onosproject.openflow.controller.ExtensionTreatmentInterpreter"
                    impl="org.onosproject.driver.extensions.Ofdpa3ExtensionTreatmentInterpreter" />
         <behaviour api="org.onosproject.net.behaviour.ExtensionTreatmentResolver"
@@ -136,7 +136,7 @@
             manufacturer="ONF"
             hwVersion="OF1.3 Software Switch from CPqD" swVersion="for Group Chaining">
         <behaviour api="org.onosproject.net.behaviour.Pipeliner"
-                   impl="org.onosproject.driver.pipeline.CpqdOfdpa2Pipeline"/>
+                   impl="org.onosproject.driver.pipeline.ofdpa.CpqdOfdpa2Pipeline"/>
     </driver>
 
     <!--  Emulation of the OFDPA pipeline using a CPqD OF 1.3 software switch.
@@ -147,7 +147,7 @@
             manufacturer="ONF"
             hwVersion="OF1.3 Software Switch from CPqD" swVersion="for Group Chaining">
         <behaviour api="org.onosproject.net.behaviour.Pipeliner"
-                   impl="org.onosproject.driver.pipeline.CpqdOfdpa2VlanPipeline"/>
+                   impl="org.onosproject.driver.pipeline.ofdpa.CpqdOfdpa2VlanPipeline"/>
     </driver>
 
     <!--  Emulation of the OFDPA pipeline using a OVS OF 1.3 software switch.
@@ -158,7 +158,7 @@
             manufacturer="ONF"
             hwVersion="OFDPA OVS" swVersion="OFDPA OVS">
         <behaviour api="org.onosproject.net.behaviour.Pipeliner"
-                   impl="org.onosproject.driver.pipeline.OvsOfdpa2Pipeline"/>
+                   impl="org.onosproject.driver.pipeline.ofdpa.OvsOfdpa2Pipeline"/>
     </driver>
 
     <driver name="celestica" extends="default"