CORD-367 L2 bridging and L3 routing support with internal VLANs in OF-DPA.
Also includes:
     All forwarding in app is now via nextObjectives (not treatments) - Spring Open driver converts
     non-ECMP forwarding to flow-actions, while OF-DPA driver continues to use groups.
     Convert 'setMeta' methods to 'withMeta' in Flow Objectives API.
     Bug fix in Flow Objective Manager - set of PendingNext is now threadsafe.
     Bug fix in ArpHandler - now recognizes routerIp in addition to gatewayIps
     Removed a bunch of testcode
     Added group count in CLI

Change-Id: Id3b879c5dda78151ca0ec359179f1604066d39fc
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ArpHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ArpHandler.java
index 2c6412c..7f4bcb1 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ArpHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ArpHandler.java
@@ -107,7 +107,7 @@
                                             vlanId);
 
         // ARP request for router. Send ARP reply.
-        if (isArpReqForRouter(deviceId, arpRequest)) {
+        if (isArpForRouter(deviceId, arpRequest)) {
             Ip4Address targetAddress = Ip4Address.valueOf(arpRequest.getTargetProtocolAddress());
             sendArpResponse(arpRequest, config.getRouterMacForAGatewayIp(targetAddress), vlanId);
         } else {
@@ -130,7 +130,7 @@
                                             vlanId);
 
         // ARP reply for router. Process all pending IP packets.
-        if (isArpReqForRouter(deviceId, arpReply)) {
+        if (isArpForRouter(deviceId, arpReply)) {
             Ip4Address hostIpAddress = Ip4Address.valueOf(arpReply.getSenderProtocolAddress());
             srManager.ipHandler.forwardPackets(deviceId, hostIpAddress);
         } else {
@@ -141,7 +141,8 @@
             // ARP reply for unknown host, Flood in the subnet.
             } else {
                 // Don't flood to non-edge ports
-                if (vlanId.equals(VlanId.vlanId(srManager.ASSIGNED_VLAN_NO_SUBNET))) {
+                if (vlanId.equals(
+                        VlanId.vlanId(SegmentRoutingManager.ASSIGNED_VLAN_NO_SUBNET))) {
                     return;
                 }
                 removeVlanAndFlood(payload, inPort);
@@ -150,14 +151,21 @@
     }
 
 
-    private boolean isArpReqForRouter(DeviceId deviceId, ARP arpRequest) {
-        Set<Ip4Address> gatewayIpAddresses = config.getPortIPs(deviceId);
-        if (gatewayIpAddresses != null) {
-            Ip4Address targetProtocolAddress = Ip4Address.valueOf(arpRequest
-                    .getTargetProtocolAddress());
-            if (gatewayIpAddresses.contains(targetProtocolAddress)) {
+    private boolean isArpForRouter(DeviceId deviceId, ARP arpMsg) {
+        Ip4Address targetProtocolAddress = Ip4Address.valueOf(
+                                               arpMsg.getTargetProtocolAddress());
+        Set<Ip4Address> gatewayIpAddresses = null;
+        try {
+            if (targetProtocolAddress.equals(config.getRouterIp(deviceId))) {
                 return true;
             }
+            gatewayIpAddresses = config.getPortIPs(deviceId);
+        } catch (DeviceConfigNotFoundException e) {
+            log.warn(e.getMessage() + " Aborting check for router IP in processing arp");
+        }
+        if (gatewayIpAddresses != null &&
+                gatewayIpAddresses.contains(targetProtocolAddress)) {
+            return true;
         }
         return false;
     }
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
index eb3b3fd..d1dc8dd 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java
@@ -88,10 +88,10 @@
                 (destinationAddress.equals(routerIpAddress) ||
                         gatewayIpAddresses.contains(destinationAddress))) {
             sendICMPResponse(ethernet, connectPoint);
-            // TODO: do we need to set the flow rule again ??
 
         // ICMP for any known host
         } else if (!srManager.hostService.getHostsByIp(destinationAddress).isEmpty()) {
+            // TODO: known host packet should not be coming to controller - resend flows?
             srManager.ipHandler.forwardPackets(deviceId, destinationAddress);
 
         // ICMP for an unknown host in the subnet of the router
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IpHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IpHandler.java
index b1682e7..d6a9dcf 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IpHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IpHandler.java
@@ -98,7 +98,7 @@
      */
     public void addToPacketBuffer(IPv4 ipPacket) {
 
-        // Better not buffer TPC packets due to out-of-order packet transfer
+        // Better not buffer TCP packets due to out-of-order packet transfer
         if (ipPacket.getProtocol() == IPv4.PROTOCOL_TCP) {
             return;
         }
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
index fc6a3f3..d4aa770 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
@@ -147,20 +147,34 @@
         TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
         TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
 
-        sbuilder.matchIPDst(IpPrefix.valueOf(hostIp, IpPrefix.MAX_INET_MASK_LENGTH));
         sbuilder.matchEthType(Ethernet.TYPE_IPV4);
+        sbuilder.matchIPDst(IpPrefix.valueOf(hostIp, IpPrefix.MAX_INET_MASK_LENGTH));
+        TrafficSelector selector = sbuilder.build();
 
         tbuilder.deferred()
                 .setEthDst(hostMac)
                 .setEthSrc(deviceMac)
                 .setOutput(outPort);
-
         TrafficTreatment treatment = tbuilder.build();
-        TrafficSelector selector = sbuilder.build();
+
+        // All forwarding is via Groups. Drivers can re-purpose to flow-actions if needed.
+        // for switch pipelines that need it, provide outgoing vlan as metadata
+        VlanId outvlan = null;
+        Ip4Prefix subnet = srManager.deviceConfiguration.getPortSubnet(deviceId, outPort);
+        if (subnet == null) {
+            outvlan = VlanId.vlanId(SegmentRoutingManager.ASSIGNED_VLAN_NO_SUBNET);
+        } else {
+            outvlan = srManager.getSubnetAssignedVlanId(deviceId, subnet);
+        }
+        TrafficSelector meta = DefaultTrafficSelector.builder()
+                                    .matchVlanId(outvlan).build();
+        int portNextObjId = srManager.getPortNextObjectiveId(deviceId, outPort,
+                                                             treatment, meta);
 
         return DefaultForwardingObjective.builder()
+                .withSelector(selector)
+                .nextStep(portNextObjId)
                 .fromApp(srManager.appId).makePermanent()
-                .withSelector(selector).withTreatment(treatment)
                 .withPriority(100).withFlag(ForwardingObjective.Flag.SPECIFIC);
     }
 
@@ -454,7 +468,7 @@
                 if (srManager.mastershipService.isLocalMaster(deviceId)) {
                     TrafficTreatment tt = DefaultTrafficTreatment.builder()
                             .pushVlan().setVlanId(assignedVlan).build();
-                    fob.setMeta(tt);
+                    fob.withMeta(tt);
                 }
                 fob.permit().fromApp(srManager.appId);
                 srManager.flowObjectiveService.
@@ -559,6 +573,12 @@
             int nextId = srManager.getSubnetNextObjectiveId(deviceId, subnet);
             VlanId vlanId = srManager.getSubnetAssignedVlanId(deviceId, subnet);
 
+            if (nextId < 0 || vlanId == null) {
+                log.error("Cannot install subnet broadcast rule in dev:{} due"
+                        + "to vlanId:{} or nextId:{}", vlanId, nextId);
+                return;
+            }
+
             /* Driver should treat objective with MacAddress.NONE as the
              * subnet broadcast rule
              */
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
index 518bce3..62722f0 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
@@ -57,6 +57,7 @@
 import org.onosproject.segmentrouting.grouphandler.DefaultGroupHandler;
 import org.onosproject.segmentrouting.grouphandler.NeighborSet;
 import org.onosproject.segmentrouting.grouphandler.NeighborSetNextObjectiveStoreKey;
+import org.onosproject.segmentrouting.grouphandler.PortNextObjectiveStoreKey;
 import org.onosproject.mastership.MastershipService;
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
@@ -97,7 +98,6 @@
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 
-@SuppressWarnings("ALL")
 @Service
 @Component(immediate = true)
 public class SegmentRoutingManager implements SegmentRoutingService {
@@ -150,21 +150,27 @@
     private ScheduledExecutorService executorService = Executors
             .newScheduledThreadPool(1);
 
+    @SuppressWarnings("unused")
     private static ScheduledFuture<?> eventHandlerFuture = null;
+    @SuppressWarnings("rawtypes")
     private ConcurrentLinkedQueue<Event> eventQueue = new ConcurrentLinkedQueue<Event>();
     private Map<DeviceId, DefaultGroupHandler> groupHandlerMap =
             new ConcurrentHashMap<DeviceId, DefaultGroupHandler>();
     // Per device next objective ID store with (device id + neighbor set) as key
     private EventuallyConsistentMap<NeighborSetNextObjectiveStoreKey, Integer>
             nsNextObjStore = null;
+    // Per device next objective ID store with (device id + subnet) as key
     private EventuallyConsistentMap<SubnetNextObjectiveStoreKey, Integer>
             subnetNextObjStore = null;
-    private EventuallyConsistentMap<String, Tunnel> tunnelStore = null;
-    private EventuallyConsistentMap<String, Policy> policyStore = null;
+    // Per device next objective ID store with (device id + port) as key
+    private EventuallyConsistentMap<PortNextObjectiveStoreKey, Integer>
+            portNextObjStore = null;
     // Per device, per-subnet assigned-vlans store, with (device id + subnet
     // IPv4 prefix) as key
     private EventuallyConsistentMap<SubnetAssignedVidStoreKey, VlanId>
         subnetVidStore = null;
+    private EventuallyConsistentMap<String, Tunnel> tunnelStore = null;
+    private EventuallyConsistentMap<String, Policy> policyStore = null;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected StorageService storageService;
@@ -175,6 +181,7 @@
     private final InternalConfigListener cfgListener =
             new InternalConfigListener(this);
 
+    @SuppressWarnings({ "unchecked", "rawtypes" })
     private final ConfigFactory cfgFactory =
             new ConfigFactory(SubjectFactories.DEVICE_SUBJECT_FACTORY,
                               SegmentRoutingConfig.class,
@@ -228,7 +235,6 @@
         log.debug("Creating EC map nsnextobjectivestore");
         EventuallyConsistentMapBuilder<NeighborSetNextObjectiveStoreKey, Integer>
                 nsNextObjMapBuilder = storageService.eventuallyConsistentMapBuilder();
-
         nsNextObjStore = nsNextObjMapBuilder
                 .withName("nsnextobjectivestore")
                 .withSerializer(kryoBuilder)
@@ -239,16 +245,23 @@
         log.debug("Creating EC map subnetnextobjectivestore");
         EventuallyConsistentMapBuilder<SubnetNextObjectiveStoreKey, Integer>
                 subnetNextObjMapBuilder = storageService.eventuallyConsistentMapBuilder();
-
         subnetNextObjStore = subnetNextObjMapBuilder
                 .withName("subnetnextobjectivestore")
                 .withSerializer(kryoBuilder)
                 .withTimestampProvider((k, v) -> new WallClockTimestamp())
                 .build();
 
+        log.debug("Creating EC map subnetnextobjectivestore");
+        EventuallyConsistentMapBuilder<PortNextObjectiveStoreKey, Integer>
+                portNextObjMapBuilder = storageService.eventuallyConsistentMapBuilder();
+        portNextObjStore = portNextObjMapBuilder
+                .withName("portnextobjectivestore")
+                .withSerializer(kryoBuilder)
+                .withTimestampProvider((k, v) -> new WallClockTimestamp())
+                .build();
+
         EventuallyConsistentMapBuilder<String, Tunnel> tunnelMapBuilder =
                 storageService.eventuallyConsistentMapBuilder();
-
         tunnelStore = tunnelMapBuilder
                 .withName("tunnelstore")
                 .withSerializer(kryoBuilder)
@@ -257,7 +270,6 @@
 
         EventuallyConsistentMapBuilder<String, Policy> policyMapBuilder =
                 storageService.eventuallyConsistentMapBuilder();
-
         policyStore = policyMapBuilder
                 .withName("policystore")
                 .withSerializer(kryoBuilder)
@@ -266,7 +278,6 @@
 
         EventuallyConsistentMapBuilder<SubnetAssignedVidStoreKey, VlanId>
             subnetVidStoreMapBuilder = storageService.eventuallyConsistentMapBuilder();
-
         subnetVidStore = subnetVidStoreMapBuilder
                 .withName("subnetvidstore")
                 .withSerializer(kryoBuilder)
@@ -425,8 +436,7 @@
     /**
      * Returns the next objective ID for the given NeighborSet.
      * If the nextObjective does not exist, a new one is created and
-     * it's id is returned.
-     * TODO move the side-effect creation of a Next Objective into a new method
+     * its id is returned.
      *
      * @param deviceId Device ID
      * @param ns NegighborSet
@@ -441,18 +451,19 @@
             return groupHandlerMap
                     .get(deviceId).getNextObjectiveId(ns, meta);
         } else {
-            log.warn("getNextObjectiveId query in device {} not found", deviceId);
+            log.warn("getNextObjectiveId query - groupHandler for device {} "
+                    + "not found", deviceId);
             return -1;
         }
     }
 
     /**
-     * Returns the next objective ID for the Subnet given. If the nextObjectiveID does not exist,
-     * a new one is created and returned.
+     * Returns the next objective ID for the given subnet prefix. It is expected
+     * that the next-objective has been pre-created from configuration.
      *
      * @param deviceId Device ID
      * @param prefix Subnet
-     * @return next objective ID
+     * @return next objective ID or -1 if it was not found
      */
     public int getSubnetNextObjectiveId(DeviceId deviceId, IpPrefix prefix) {
         if (groupHandlerMap.get(deviceId) != null) {
@@ -460,7 +471,33 @@
             return groupHandlerMap
                     .get(deviceId).getSubnetNextObjectiveId(prefix);
         } else {
-            log.warn("getSubnetNextObjectiveId query in device {} not found", deviceId);
+            log.warn("getSubnetNextObjectiveId query - groupHandler for "
+                    + "device {} not found", deviceId);
+            return -1;
+        }
+    }
+
+    /**
+     * Returns the next objective ID for the given portNumber, given the treatment.
+     * There could be multiple different treatments to the same outport, which
+     * would result in different objectives. If the next object
+     * does not exist, a new one is created and its id is returned.
+     *
+     * @param deviceId Device ID
+     * @param portNum port number on device for which NextObjective is queried
+     * @param treatment the actions to apply on the packets (should include outport)
+     * @param meta metadata passed into the creation of a Next Objective if necessary
+     * @return next objective ID or -1 if it was not found
+     */
+    public int getPortNextObjectiveId(DeviceId deviceId, PortNumber portNum,
+                                      TrafficTreatment treatment,
+                                      TrafficSelector meta) {
+        DefaultGroupHandler ghdlr = groupHandlerMap.get(deviceId);
+        if (ghdlr != null) {
+            return ghdlr.getPortNextObjectiveId(portNum, treatment, meta);
+        } else {
+            log.warn("getPortNextObjectiveId query -  groupHandler for device {}"
+                    + " not found", deviceId);
             return -1;
         }
     }
@@ -475,7 +512,7 @@
 
             InboundPacket pkt = context.inPacket();
             Ethernet ethernet = pkt.parsed();
-
+            log.trace("Rcvd pktin: {}", ethernet);
             if (ethernet.getEtherType() == Ethernet.TYPE_ARP) {
                 arpHandler.processPacketIn(pkt);
             } else if (ethernet.getEtherType() == Ethernet.TYPE_IPV4) {
@@ -517,6 +554,7 @@
         }
     }
 
+    @SuppressWarnings("rawtypes")
     private void scheduleEventHandlerIfNotScheduled(Event event) {
         synchronized (threadSchedulerLock) {
             eventQueue.add(event);
@@ -539,6 +577,7 @@
         public void run() {
             try {
                 while (true) {
+                    @SuppressWarnings("rawtypes")
                     Event event = null;
                     synchronized (threadSchedulerLock) {
                         if (!eventQueue.isEmpty()) {
@@ -647,7 +686,8 @@
                                            linkService,
                                            flowObjectiveService,
                                            nsNextObjStore,
-                                           subnetNextObjStore);
+                                           subnetNextObjStore,
+                                           portNextObjStore);
             } catch (DeviceConfigNotFoundException e) {
                 log.warn(e.getMessage() + " Aborting processDeviceAdded.");
                 return;
@@ -714,7 +754,8 @@
                                                    linkService,
                                                    flowObjectiveService,
                                                    nsNextObjStore,
-                                                   subnetNextObjStore);
+                                                   subnetNextObjStore,
+                                                   portNextObjStore);
                     } catch (DeviceConfigNotFoundException e) {
                         log.warn(e.getMessage() + " Aborting configureNetwork.");
                         return;
@@ -766,7 +807,7 @@
 
                 // Populate bridging table entry
                 ForwardingObjective.Builder fob =
-                        getForwardingObjectiveBuilder(mac, vlanId, port);
+                        getForwardingObjectiveBuilder(deviceId, mac, vlanId, port);
                 flowObjectiveService.forward(deviceId, fob.add(
                         new BridgingTableObjectiveContext(mac, vlanId)
                 ));
@@ -782,20 +823,37 @@
         }
 
         private ForwardingObjective.Builder getForwardingObjectiveBuilder(
-                MacAddress mac, VlanId vlanId, PortNumber port) {
+                     DeviceId deviceId, MacAddress mac, VlanId vlanId,
+                     PortNumber outport) {
+            // match rule
             TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
             sbuilder.matchEthDst(mac);
             sbuilder.matchVlanId(vlanId);
 
             TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
-            // TODO Move popVlan from flow action to group action
             tbuilder.immediate().popVlan();
-            tbuilder.immediate().setOutput(port);
+            tbuilder.immediate().setOutput(outport);
+
+            // for switch pipelines that need it, provide outgoing vlan as metadata
+            VlanId outvlan = null;
+            Ip4Prefix subnet = deviceConfiguration.getPortSubnet(deviceId, outport);
+            if (subnet == null) {
+                outvlan = VlanId.vlanId(ASSIGNED_VLAN_NO_SUBNET);
+            } else {
+                outvlan = getSubnetAssignedVlanId(deviceId, subnet);
+            }
+            TrafficSelector meta = DefaultTrafficSelector.builder()
+                                        .matchVlanId(outvlan).build();
+
+            // All forwarding is via Groups. Drivers can re-purpose to flow-actions if needed.
+            int portNextObjId = getPortNextObjectiveId(deviceId, outport,
+                                                       tbuilder.build(),
+                                                       meta);
 
             return DefaultForwardingObjective.builder()
                     .withFlag(ForwardingObjective.Flag.SPECIFIC)
                     .withSelector(sbuilder.build())
-                    .withTreatment(tbuilder.build())
+                    .nextStep(portNextObjId)
                     .withPriority(100)
                     .fromApp(appId)
                     .makePermanent();
@@ -807,11 +865,13 @@
             DeviceId deviceId = event.subject().location().deviceId();
             PortNumber port = event.subject().location().port();
             Set<IpAddress> ips = event.subject().ipAddresses();
-            log.debug("Host {}/{} is added at {}:{}", mac, vlanId, deviceId, port);
+            log.info("Host {}/{} is added at {}:{}", mac, vlanId, deviceId, port);
 
             // Populate bridging table entry
+            log.debug("Populate L2 table entry for host {} at {}:{}",
+                      mac, deviceId, port);
             ForwardingObjective.Builder fob =
-                    getForwardingObjectiveBuilder(mac, vlanId, port);
+                    getForwardingObjectiveBuilder(deviceId, mac, vlanId, port);
             flowObjectiveService.forward(deviceId, fob.add(
                     new BridgingTableObjectiveContext(mac, vlanId)
             ));
@@ -835,7 +895,7 @@
 
             // Revoke bridging table entry
             ForwardingObjective.Builder fob =
-                    getForwardingObjectiveBuilder(mac, vlanId, port);
+                    getForwardingObjectiveBuilder(deviceId, mac, vlanId, port);
             flowObjectiveService.forward(deviceId, fob.remove(
                     new BridgingTableObjectiveContext(mac, vlanId)
             ));
@@ -863,7 +923,7 @@
 
             // Revoke previous bridging table entry
             ForwardingObjective.Builder prevFob =
-                    getForwardingObjectiveBuilder(mac, vlanId, prevPort);
+                    getForwardingObjectiveBuilder(prevDeviceId, mac, vlanId, prevPort);
             flowObjectiveService.forward(prevDeviceId, prevFob.remove(
                     new BridgingTableObjectiveContext(mac, vlanId)
             ));
@@ -878,7 +938,7 @@
 
             // Populate new bridging table entry
             ForwardingObjective.Builder newFob =
-                    getForwardingObjectiveBuilder(mac, vlanId, prevPort);
+                    getForwardingObjectiveBuilder(newDeviceId, mac, vlanId, newPort);
             flowObjectiveService.forward(newDeviceId, newFob.add(
                     new BridgingTableObjectiveContext(mac, vlanId)
             ));
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultEdgeGroupHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultEdgeGroupHandler.java
index 6b6d960..32c5365 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultEdgeGroupHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultEdgeGroupHandler.java
@@ -56,9 +56,11 @@
                                           NeighborSetNextObjectiveStoreKey,
                                           Integer> nsNextObjStore,
                                   EventuallyConsistentMap<SubnetNextObjectiveStoreKey,
-                                          Integer> subnetNextObjStore) {
+                                          Integer> subnetNextObjStore,
+                                  EventuallyConsistentMap<PortNextObjectiveStoreKey,
+                                          Integer> portNextObjStore) {
         super(deviceId, appId, config, linkService, flowObjService,
-              nsNextObjStore, subnetNextObjStore);
+              nsNextObjStore, subnetNextObjStore, portNextObjStore);
     }
 
     @Override
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java
index e792bf6..bc394b8 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java
@@ -80,6 +80,8 @@
         NeighborSetNextObjectiveStoreKey, Integer> nsNextObjStore = null;
     protected EventuallyConsistentMap<
             SubnetNextObjectiveStoreKey, Integer> subnetNextObjStore = null;
+    protected EventuallyConsistentMap<
+            PortNextObjectiveStoreKey, Integer> portNextObjStore = null;
 
     protected KryoNamespace.Builder kryo = new KryoNamespace.Builder()
             .register(URI.class).register(HashSet.class)
@@ -93,11 +95,12 @@
                                   DeviceProperties config,
                                   LinkService linkService,
                                   FlowObjectiveService flowObjService,
-                                  EventuallyConsistentMap<
-                                          NeighborSetNextObjectiveStoreKey,
+                                  EventuallyConsistentMap<NeighborSetNextObjectiveStoreKey,
                                           Integer> nsNextObjStore,
                                   EventuallyConsistentMap<SubnetNextObjectiveStoreKey,
-                                          Integer> subnetNextObjStore) {
+                                          Integer> subnetNextObjStore,
+                                  EventuallyConsistentMap<PortNextObjectiveStoreKey,
+                                          Integer> portNextObjStore) {
         this.deviceId = checkNotNull(deviceId);
         this.appId = checkNotNull(appId);
         this.deviceConfig = checkNotNull(config);
@@ -114,6 +117,7 @@
         this.flowObjectiveService = flowObjService;
         this.nsNextObjStore = nsNextObjStore;
         this.subnetNextObjStore = subnetNextObjStore;
+        this.portNextObjStore = portNextObjStore;
 
         populateNeighborMaps();
     }
@@ -133,30 +137,34 @@
      * @throws DeviceConfigNotFoundException if the device configuration is not found
      * @return default group handler type
      */
-    public static DefaultGroupHandler createGroupHandler(DeviceId deviceId,
-                                                         ApplicationId appId,
-                                                         DeviceProperties config,
-                                                         LinkService linkService,
-                                                         FlowObjectiveService flowObjService,
-                                                         EventuallyConsistentMap<
-                                                                 NeighborSetNextObjectiveStoreKey,
-                                                                 Integer> nsNextObjStore,
-                                                         EventuallyConsistentMap<SubnetNextObjectiveStoreKey,
-                                                                 Integer> subnetNextObjStore)
-                                                         throws DeviceConfigNotFoundException {
+    public static DefaultGroupHandler createGroupHandler(
+                                          DeviceId deviceId,
+                                          ApplicationId appId,
+                                          DeviceProperties config,
+                                          LinkService linkService,
+                                          FlowObjectiveService flowObjService,
+                                          EventuallyConsistentMap<NeighborSetNextObjectiveStoreKey,
+                                          Integer> nsNextObjStore,
+                                          EventuallyConsistentMap<SubnetNextObjectiveStoreKey,
+                                          Integer> subnetNextObjStore,
+                                          EventuallyConsistentMap<PortNextObjectiveStoreKey,
+                                          Integer> portNextObjStore)
+                                                  throws DeviceConfigNotFoundException {
         // handle possible exception in the caller
         if (config.isEdgeDevice(deviceId)) {
             return new DefaultEdgeGroupHandler(deviceId, appId, config,
                                                linkService,
                                                flowObjService,
                                                nsNextObjStore,
-                                               subnetNextObjStore);
+                                               subnetNextObjStore,
+                                               portNextObjStore);
         } else {
             return new DefaultTransitGroupHandler(deviceId, appId, config,
                                                   linkService,
                                                   flowObjService,
                                                   nsNextObjStore,
-                                                  subnetNextObjStore);
+                                                  subnetNextObjStore,
+                                                  portNextObjStore);
         }
     }
 
@@ -231,25 +239,21 @@
 
             Integer nextId = nsNextObjStore.
                     get(new NeighborSetNextObjectiveStoreKey(deviceId, ns));
-            if (nextId != null) {
+            if (nextId != null && isMaster) {
                 NextObjective.Builder nextObjBuilder = DefaultNextObjective
                         .builder().withId(nextId)
                         .withType(NextObjective.Type.HASHED).fromApp(appId);
 
                 nextObjBuilder.addTreatment(tBuilder.build());
-
                 log.info("**linkUp in device {}: Adding Bucket "
-                        + "with Port {} to next object id {} and amIMaster:{}",
+                        + "with Port {} to next object id {}",
                         deviceId,
                         newLink.src().port(),
-                        nextId, isMaster);
-
-                if (isMaster) {
-                    NextObjective nextObjective = nextObjBuilder.
-                            addToExisting(new SRNextObjectiveContext(deviceId));
-                    flowObjectiveService.next(deviceId, nextObjective);
-                }
-            } else {
+                        nextId);
+                NextObjective nextObjective = nextObjBuilder.
+                        addToExisting(new SRNextObjectiveContext(deviceId));
+                flowObjectiveService.next(deviceId, nextObjective);
+            } else if (isMaster) {
                 log.warn("linkUp in device {}, but global store has no record "
                         + "for neighbor-set {}", deviceId, ns);
             }
@@ -331,8 +335,8 @@
     }
 
     /**
-     * Returns the next objective associated with the neighborset.
-     * If there is no next objective for this neighborset, this API
+     * Returns the next objective of type hashed associated with the neighborset.
+     * If there is no next objective for this neighborset, this method
      * would create a next objective and return. Optionally metadata can be
      * passed in for the creation of the next objective.
      *
@@ -372,9 +376,10 @@
     }
 
     /**
-     * Returns the next objective associated with the subnet.
-     * If there is no next objective for this subnet, this API
-     * would create a next objective and return.
+     * Returns the next objective of type broadcast associated with the subnet,
+     * or -1 if no such objective exists. Note that this method does NOT create
+     * the next objective as a side-effect. It is expected that is objective is
+     * created at startup from network configuration.
      *
      * @param prefix subnet information
      * @return int if found or -1
@@ -387,6 +392,38 @@
     }
 
     /**
+     * Returns the next objective of type simple associated with the port on the
+     * device, given the treatment. Different treatments to the same port result
+     * in different next objectives. If no such objective exists, this method
+     * creates one and returns the id. Optionally metadata can be passed in for
+     * the creation of the objective.
+     *
+     * @param portNum the port number for the simple next objective
+     * @param treatment the actions to apply on the packets (should include outport)
+     * @param meta optional metadata passed into the creation of the next objective
+     * @return int if found or created, -1 if there are errors during the
+     *          creation of the next objective.
+     */
+    public int getPortNextObjectiveId(PortNumber portNum, TrafficTreatment treatment,
+                                      TrafficSelector meta) {
+        Integer nextId = portNextObjStore.
+                get(new PortNextObjectiveStoreKey(deviceId, portNum, treatment));
+        if (nextId == null) {
+            log.trace("getPortNextObjectiveId in device{}: Next objective id "
+                    + "not found for {} and {} creating", deviceId, portNum);
+            createGroupFromPort(portNum, treatment, meta);
+            nextId = portNextObjStore.get(
+                         new PortNextObjectiveStoreKey(deviceId, portNum, treatment));
+            if (nextId == null) {
+                log.warn("getPortNextObjectiveId: unable to create next obj"
+                        + "for dev:{} port{}", deviceId, portNum);
+                return -1;
+            }
+        }
+        return nextId;
+    }
+
+    /**
      * Checks if the next objective ID (group) for the neighbor set exists or not.
      *
      * @param ns neighbor set to check
@@ -561,7 +598,7 @@
                 }
             }
             if (meta != null) {
-                nextObjBuilder.setMeta(meta);
+                nextObjBuilder.withMeta(meta);
             }
             NextObjective nextObj = nextObjBuilder.
                     add(new SRNextObjectiveContext(deviceId));
@@ -574,7 +611,10 @@
         }
     }
 
-
+    /**
+     * Creates broadcast groups for all ports in the same configured subnet.
+     *
+     */
     public void createGroupsFromSubnetConfig() {
         Map<Ip4Prefix, List<PortNumber>> subnetPortMap =
                 this.deviceConfig.getSubnetPortsMap(this.deviceId);
@@ -612,6 +652,37 @@
         });
     }
 
+
+    /**
+     * Create simple next objective for a single port. The treatments can include
+     * all outgoing actions that need to happen on the packet.
+     *
+     * @param portNum  the outgoing port on the device
+     * @param treatment the actions to apply on the packets (should include outport)
+     * @param meta optional data to pass to the driver
+     */
+    public void createGroupFromPort(PortNumber portNum, TrafficTreatment treatment,
+                                    TrafficSelector meta) {
+        int nextId = flowObjectiveService.allocateNextId();
+        PortNextObjectiveStoreKey key = new PortNextObjectiveStoreKey(
+                                                deviceId, portNum, treatment);
+
+        NextObjective.Builder nextObjBuilder = DefaultNextObjective
+                .builder().withId(nextId)
+                .withType(NextObjective.Type.SIMPLE)
+                .addTreatment(treatment)
+                .fromApp(appId)
+                .withMeta(meta);
+
+        NextObjective nextObj = nextObjBuilder.add();
+        flowObjectiveService.next(deviceId, nextObj);
+        log.debug("createGroupFromPort: Submited next objective {} in device {} "
+                + "for port {}", nextId, deviceId, portNum);
+
+        portNextObjStore.put(key, nextId);
+    }
+
+
     public GroupKey getGroupKey(Object obj) {
         return new DefaultGroupKey(kryo.build().serialize(obj));
     }
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultTransitGroupHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultTransitGroupHandler.java
index 14d77ba..7a43e73 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultTransitGroupHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultTransitGroupHandler.java
@@ -50,9 +50,11 @@
                                         NeighborSetNextObjectiveStoreKey,
                                         Integer> nsNextObjStore,
                                   EventuallyConsistentMap<SubnetNextObjectiveStoreKey,
-                                        Integer> subnetNextObjStore) {
+                                        Integer> subnetNextObjStore,
+                                  EventuallyConsistentMap<PortNextObjectiveStoreKey,
+                                  Integer> portNextObjStore) {
         super(deviceId, appId, config, linkService, flowObjService,
-              nsNextObjStore, subnetNextObjStore);
+              nsNextObjStore, subnetNextObjStore, portNextObjStore);
     }
 
     @Override
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/PolicyGroupHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/PolicyGroupHandler.java
index 5514207..ef143dc 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/PolicyGroupHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/PolicyGroupHandler.java
@@ -68,9 +68,11 @@
                               EventuallyConsistentMap<NeighborSetNextObjectiveStoreKey,
                                       Integer> nsNextObjStore,
                               EventuallyConsistentMap<SubnetNextObjectiveStoreKey,
-                                      Integer> subnetNextObjStore) {
+                                      Integer> subnetNextObjStore,
+                              EventuallyConsistentMap<PortNextObjectiveStoreKey,
+                              Integer> portNextObjStore) {
         super(deviceId, appId, config, linkService, flowObjService,
-              nsNextObjStore, subnetNextObjStore);
+              nsNextObjStore, subnetNextObjStore, portNextObjStore);
     }
 
     public PolicyGroupIdentifier createPolicyGroupChain(String id,
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/PortNextObjectiveStoreKey.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/PortNextObjectiveStoreKey.java
new file mode 100644
index 0000000..5555565
--- /dev/null
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/PortNextObjectiveStoreKey.java
@@ -0,0 +1,77 @@
+package org.onosproject.segmentrouting.grouphandler;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.TrafficTreatment;
+
+import java.util.Objects;
+
+/**
+ * Class definition of Key for Device/Port to NextObjective store. Since there
+ * can be multiple next objectives to the same physical port, we differentiate
+ * between them by including the treatment in the key.
+ */
+public class PortNextObjectiveStoreKey {
+    private final DeviceId deviceId;
+    private final PortNumber portNum;
+    private final TrafficTreatment treatment;
+
+    public PortNextObjectiveStoreKey(DeviceId deviceId, PortNumber portNum,
+                                     TrafficTreatment treatment) {
+        this.deviceId = deviceId;
+        this.portNum = portNum;
+        this.treatment = treatment;
+    }
+
+    /**
+     * Gets device id in this PortNextObjectiveStoreKey.
+     *
+     * @return device id
+     */
+    public DeviceId deviceId() {
+        return deviceId;
+    }
+
+    /**
+     * Gets port information in this PortNextObjectiveStoreKey.
+     *
+     * @return port information
+     */
+    public PortNumber portNumber() {
+        return portNum;
+    }
+
+    /**
+     * Gets treatment information in this PortNextObjectiveStoreKey.
+     *
+     * @return treatment information
+     */
+    public TrafficTreatment treatment() {
+        return treatment;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof PortNextObjectiveStoreKey)) {
+            return false;
+        }
+        PortNextObjectiveStoreKey that =
+                (PortNextObjectiveStoreKey) o;
+        return (Objects.equals(this.deviceId, that.deviceId) &&
+                Objects.equals(this.portNum, that.portNum) &&
+                Objects.equals(this.treatment, that.treatment));
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(deviceId, portNum, treatment);
+    }
+
+    @Override
+    public String toString() {
+        return "Device: " + deviceId + " Port: " + portNum + " Treatment: " + treatment;
+    }
+}
diff --git a/cli/src/main/java/org/onosproject/cli/net/GroupsListCommand.java b/cli/src/main/java/org/onosproject/cli/net/GroupsListCommand.java
index ee6d31b..2b25744 100644
--- a/cli/src/main/java/org/onosproject/cli/net/GroupsListCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/net/GroupsListCommand.java
@@ -119,7 +119,7 @@
     }
 
     private void printGroups(DeviceId deviceId, List<Group> groups) {
-        print("deviceId=%s", deviceId);
+        print("deviceId=%s, groupCount=%s", deviceId, groups.size());
         for (Group group : groups) {
             print(FORMAT, Integer.toHexString(group.id().id()), group.state(), group.type(),
                   group.bytes(), group.packets(), group.appId().name());
diff --git a/core/api/src/main/java/org/onosproject/net/flowobjective/DefaultFilteringObjective.java b/core/api/src/main/java/org/onosproject/net/flowobjective/DefaultFilteringObjective.java
index 06305bf..4d9d722 100644
--- a/core/api/src/main/java/org/onosproject/net/flowobjective/DefaultFilteringObjective.java
+++ b/core/api/src/main/java/org/onosproject/net/flowobjective/DefaultFilteringObjective.java
@@ -196,7 +196,7 @@
         }
 
         @Override
-        public Builder setMeta(TrafficTreatment treatment) {
+        public Builder withMeta(TrafficTreatment treatment) {
             this.meta = treatment;
             return this;
         }
diff --git a/core/api/src/main/java/org/onosproject/net/flowobjective/DefaultNextObjective.java b/core/api/src/main/java/org/onosproject/net/flowobjective/DefaultNextObjective.java
index 4701589..bd58050 100644
--- a/core/api/src/main/java/org/onosproject/net/flowobjective/DefaultNextObjective.java
+++ b/core/api/src/main/java/org/onosproject/net/flowobjective/DefaultNextObjective.java
@@ -181,7 +181,7 @@
         }
 
         @Override
-        public Builder setMeta(TrafficSelector meta) {
+        public Builder withMeta(TrafficSelector meta) {
             this.meta = meta;
             return this;
         }
diff --git a/core/api/src/main/java/org/onosproject/net/flowobjective/FilteringObjective.java b/core/api/src/main/java/org/onosproject/net/flowobjective/FilteringObjective.java
index 29257c6..8ed793d 100644
--- a/core/api/src/main/java/org/onosproject/net/flowobjective/FilteringObjective.java
+++ b/core/api/src/main/java/org/onosproject/net/flowobjective/FilteringObjective.java
@@ -133,7 +133,7 @@
          * @param treatment traffic treatment to use
          * @return a filtering builder
          */
-        Builder setMeta(TrafficTreatment treatment);
+        Builder withMeta(TrafficTreatment treatment);
 
         /**
          * Assigns an application id.
diff --git a/core/api/src/main/java/org/onosproject/net/flowobjective/NextObjective.java b/core/api/src/main/java/org/onosproject/net/flowobjective/NextObjective.java
index 08916eb..36098d7 100644
--- a/core/api/src/main/java/org/onosproject/net/flowobjective/NextObjective.java
+++ b/core/api/src/main/java/org/onosproject/net/flowobjective/NextObjective.java
@@ -147,7 +147,7 @@
          * @param selector match conditions
          * @return an objective builder
          */
-        Builder setMeta(TrafficSelector selector);
+        Builder withMeta(TrafficSelector selector);
 
         /**
          * Builds the next objective that will be added.
diff --git a/core/net/src/main/java/org/onosproject/net/flowobjective/impl/FlowObjectiveManager.java b/core/net/src/main/java/org/onosproject/net/flowobjective/impl/FlowObjectiveManager.java
index 5ecdc7a..33200b1 100644
--- a/core/net/src/main/java/org/onosproject/net/flowobjective/impl/FlowObjectiveManager.java
+++ b/core/net/src/main/java/org/onosproject/net/flowobjective/impl/FlowObjectiveManager.java
@@ -16,7 +16,6 @@
 package org.onosproject.net.flowobjective.impl;
 
 import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -53,9 +52,11 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.Collections;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutorService;
 
 import static com.google.common.base.Preconditions.checkNotNull;
@@ -228,8 +229,10 @@
                 flowObjectiveStore.getNextGroup(fwd.nextId()) == null) {
             log.trace("Queuing forwarding objective for nextId {}", fwd.nextId());
             // TODO: change to computeIfAbsent
-            Set<PendingNext> pnext = pendingForwards.putIfAbsent(fwd.nextId(),
-                                         Sets.newHashSet(new PendingNext(deviceId, fwd)));
+            Set<PendingNext> newset = Collections.newSetFromMap(
+                                          new ConcurrentHashMap<PendingNext, Boolean>());
+            newset.add(new PendingNext(deviceId, fwd));
+            Set<PendingNext> pnext = pendingForwards.putIfAbsent(fwd.nextId(), newset);
             if (pnext != null) {
                 pnext.add(new PendingNext(deviceId, fwd));
             }
diff --git a/core/store/dist/src/main/java/org/onosproject/store/group/impl/DistributedGroupStore.java b/core/store/dist/src/main/java/org/onosproject/store/group/impl/DistributedGroupStore.java
index 83319c3..cc32a73 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/group/impl/DistributedGroupStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/group/impl/DistributedGroupStore.java
@@ -350,7 +350,7 @@
         // Check if a group is existing with the same key
         Group existingGroup = getGroup(groupDesc.deviceId(), groupDesc.appCookie());
         if (existingGroup != null) {
-            log.warn("Group already exists with the same key {} in dev:{} with id:{}",
+            log.warn("Group already exists with the same key {} in dev:{} with id:0x{}",
                      groupDesc.appCookie(), groupDesc.deviceId(),
                      Integer.toHexString(existingGroup.id().id()));
             return;
diff --git a/drivers/src/main/java/org/onosproject/driver/pipeline/CpqdOFDPA2Pipeline.java b/drivers/src/main/java/org/onosproject/driver/pipeline/CpqdOFDPA2Pipeline.java
index 937c9ac..02522cf 100644
--- a/drivers/src/main/java/org/onosproject/driver/pipeline/CpqdOFDPA2Pipeline.java
+++ b/drivers/src/main/java/org/onosproject/driver/pipeline/CpqdOFDPA2Pipeline.java
@@ -39,7 +39,9 @@
 import org.onosproject.net.flow.FlowRuleOperationsContext;
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criteria;
 import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.EthCriterion;
 import org.onosproject.net.flow.criteria.EthTypeCriterion;
 import org.onosproject.net.flow.criteria.IPCriterion;
 import org.onosproject.net.flow.criteria.MplsBosCriterion;
@@ -62,6 +64,14 @@
 
     private final Logger log = getLogger(getClass());
 
+    /*
+     * Cpqd emulation does not require the non-OF standard rules for
+     * matching untagged packets.
+     *
+     * (non-Javadoc)
+     * @see org.onosproject.driver.pipeline.OFDPA2Pipeline#processVlanIdFilter
+     */
+
     @Override
     protected List<FlowRule> processVlanIdFilter(PortCriterion portCriterion,
                                                  VlanIdCriterion vidCriterion,
@@ -122,16 +132,101 @@
         return rules;
     }
 
+    /*
+     * Cpqd emulation does not handle vlan tags and mpls labels correctly.
+     * Workaround requires popping off the VLAN tags in the TMAC table.
+     *
+     * (non-Javadoc)
+     * @see org.onosproject.driver.pipeline.OFDPA2Pipeline#processEthDstFilter
+     */
     @Override
-    protected Collection<FlowRule> processSpecific(ForwardingObjective fwd) {
+    protected List<FlowRule> processEthDstFilter(PortCriterion portCriterion,
+                                                 EthCriterion ethCriterion,
+                                                 VlanIdCriterion vidCriterion,
+                                                 VlanId assignedVlan,
+                                                 ApplicationId applicationId) {
+        //handling untagged packets via assigned VLAN
+        if (vidCriterion.vlanId() == VlanId.NONE) {
+            vidCriterion = (VlanIdCriterion) Criteria.matchVlanId(assignedVlan);
+        }
+        // ofdpa cannot match on ALL portnumber, so we need to use separate
+        // rules for each port.
+        List<PortNumber> portnums = new ArrayList<PortNumber>();
+        if (portCriterion.port() == PortNumber.ALL) {
+            for (Port port : deviceService.getPorts(deviceId)) {
+                if (port.number().toLong() > 0 && port.number().toLong() < OFPP_MAX) {
+                    portnums.add(port.number());
+                }
+            }
+        } else {
+            portnums.add(portCriterion.port());
+        }
+
+        List<FlowRule> rules = new ArrayList<FlowRule>();
+        for (PortNumber pnum : portnums) {
+            // for unicast IP packets
+            TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+            TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+            selector.matchInPort(pnum);
+            selector.matchVlanId(vidCriterion.vlanId());
+            selector.matchEthType(Ethernet.TYPE_IPV4);
+            selector.matchEthDst(ethCriterion.mac());
+            /*
+             * Note: CpqD switches do not handle MPLS-related operation properly
+             * for a packet with VLAN tag. We pop VLAN here as a workaround.
+             * Side effect: HostService learns redundant hosts with same MAC but
+             * different VLAN. No known side effect on the network reachability.
+             */
+            treatment.popVlan();
+            treatment.transition(UNICAST_ROUTING_TABLE);
+            FlowRule rule = DefaultFlowRule.builder()
+                    .forDevice(deviceId)
+                    .withSelector(selector.build())
+                    .withTreatment(treatment.build())
+                    .withPriority(DEFAULT_PRIORITY)
+                    .fromApp(applicationId)
+                    .makePermanent()
+                    .forTable(TMAC_TABLE).build();
+            rules.add(rule);
+            //for MPLS packets
+            selector = DefaultTrafficSelector.builder();
+            treatment = DefaultTrafficTreatment.builder();
+            selector.matchInPort(pnum);
+            selector.matchVlanId(vidCriterion.vlanId());
+            selector.matchEthType(Ethernet.MPLS_UNICAST);
+            selector.matchEthDst(ethCriterion.mac());
+            // workaround here again
+            treatment.popVlan();
+            treatment.transition(MPLS_TABLE_0);
+            rule = DefaultFlowRule.builder()
+                    .forDevice(deviceId)
+                    .withSelector(selector.build())
+                    .withTreatment(treatment.build())
+                    .withPriority(DEFAULT_PRIORITY)
+                    .fromApp(applicationId)
+                    .makePermanent()
+                    .forTable(TMAC_TABLE).build();
+            rules.add(rule);
+        }
+        return rules;
+    }
+
+    /*
+     * Cpqd emulation allows MPLS ecmp.
+     *
+     * (non-Javadoc)
+     * @see org.onosproject.driver.pipeline.OFDPA2Pipeline#processEthTypeSpecific
+     */
+    @Override
+    protected Collection<FlowRule> processEthTypeSpecific(ForwardingObjective fwd) {
         TrafficSelector selector = fwd.selector();
         EthTypeCriterion ethType =
                 (EthTypeCriterion) selector.getCriterion(Criterion.Type.ETH_TYPE);
         if ((ethType == null) ||
                 (ethType.ethType().toShort() != Ethernet.TYPE_IPV4) &&
                 (ethType.ethType().toShort() != Ethernet.MPLS_UNICAST)) {
-            log.warn("processSpecific: Unsupported "
-                    + "forwarding objective criteraia");
+            log.warn("processSpecific: Unsupported forwarding objective criteria"
+                    + "ethType:{} in dev:{}", ethType, deviceId);
             fail(fwd, ObjectiveError.UNSUPPORTED);
             return Collections.emptySet();
         }
@@ -143,8 +238,8 @@
                 .matchIPDst(((IPCriterion)
                         selector.getCriterion(Criterion.Type.IPV4_DST)).ip());
             forTableId = UNICAST_ROUTING_TABLE;
-            log.debug("processing IPv4 specific forwarding objective {} hash{} in dev:{}",
-                      fwd.id(), fwd.hashCode(), deviceId);
+            log.debug("processing IPv4 specific forwarding objective {} -> next:{}"
+                    + " in dev:{}", fwd.id(), fwd.nextId(), deviceId);
         } else {
             filteredSelector
                 .matchEthType(Ethernet.MPLS_UNICAST)
@@ -156,8 +251,8 @@
                 filteredSelector.matchMplsBos(bos.mplsBos());
             }
             forTableId = MPLS_TABLE_1;
-            log.debug("processing MPLS specific forwarding objective {} hash:{} in dev {}",
-                      fwd.id(), fwd.hashCode(), deviceId);
+            log.debug("processing MPLS specific forwarding objective {} -> next:{}"
+                    + " in dev {}", fwd.id(), fwd.nextId(), deviceId);
         }
 
         TrafficTreatment.Builder tb = DefaultTrafficTreatment.builder();
@@ -197,7 +292,6 @@
         return Collections.singletonList(ruleBuilder.build());
     }
 
-
     @Override
     protected void initializePipeline() {
         processPortTable();
@@ -210,7 +304,6 @@
         processAclTable();
     }
 
-    @Override
     protected void processPortTable() {
         FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
         TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
@@ -239,7 +332,6 @@
         }));
     }
 
-    @Override
     protected void processTmacTable() {
         //table miss entry
         FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
@@ -270,7 +362,6 @@
         }));
     }
 
-    @Override
     protected void processIpTable() {
         //table miss entry
         FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
@@ -278,6 +369,7 @@
         TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
         selector = DefaultTrafficSelector.builder();
         treatment = DefaultTrafficTreatment.builder();
+        treatment.deferred().setOutput(PortNumber.CONTROLLER);
         treatment.transition(ACL_TABLE);
         FlowRule rule = DefaultFlowRule.builder()
                 .forDevice(deviceId)
@@ -301,7 +393,6 @@
         }));
     }
 
-    @Override
     protected void processMplsTable() {
         //table miss entry
         FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
@@ -374,7 +465,6 @@
         }));
     }
 
-    @Override
     protected void processAclTable() {
         //table miss entry - catch all to executed action-set
         FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
diff --git a/drivers/src/main/java/org/onosproject/driver/pipeline/OFDPA2Pipeline.java b/drivers/src/main/java/org/onosproject/driver/pipeline/OFDPA2Pipeline.java
index 863caeb..5f84b43 100644
--- a/drivers/src/main/java/org/onosproject/driver/pipeline/OFDPA2Pipeline.java
+++ b/drivers/src/main/java/org/onosproject/driver/pipeline/OFDPA2Pipeline.java
@@ -18,7 +18,6 @@
 import static org.onlab.util.Tools.groupedThreads;
 import static org.slf4j.LoggerFactory.getLogger;
 
-import java.nio.ByteBuffer;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -28,6 +27,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
@@ -35,14 +35,9 @@
 import java.util.stream.Collectors;
 
 import org.onlab.osgi.ServiceDirectory;
-import org.onlab.packet.Data;
 import org.onlab.packet.Ethernet;
-import org.onlab.packet.IPv4;
-import org.onlab.packet.IpPrefix;
-import org.onlab.packet.MPLS;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.MplsLabel;
-import org.onlab.packet.UDP;
 import org.onlab.packet.VlanId;
 import org.onlab.util.KryoNamespace;
 import org.onosproject.core.ApplicationId;
@@ -99,10 +94,6 @@
 import org.onosproject.net.group.GroupKey;
 import org.onosproject.net.group.GroupListener;
 import org.onosproject.net.group.GroupService;
-import org.onosproject.net.packet.DefaultOutboundPacket;
-import org.onosproject.net.packet.OutboundPacket;
-import org.onosproject.net.packet.PacketContext;
-import org.onosproject.net.packet.PacketProcessor;
 import org.onosproject.net.packet.PacketService;
 import org.onosproject.store.serializers.KryoNamespaces;
 import org.slf4j.Logger;
@@ -160,7 +151,6 @@
     protected ApplicationId driverId;
     protected PacketService packetService;
     protected DeviceService deviceService;
-    private InternalPacketProcessor processor = new InternalPacketProcessor();
     protected KryoNamespace appKryo = new KryoNamespace.Builder()
         .register(KryoNamespaces.API)
         .register(GroupKey.class)
@@ -170,7 +160,7 @@
         .register(ArrayDeque.class)
         .build();
 
-    private Cache<GroupKey, OfdpaNextGroup> pendingNextObjectives;
+    private Cache<GroupKey, List<OfdpaNextGroup>> pendingNextObjectives;
     private ConcurrentHashMap<GroupKey, Set<GroupChainElem>> pendingGroups;
 
     private ScheduledExecutorService groupChecker =
@@ -196,10 +186,12 @@
         pendingNextObjectives = CacheBuilder.newBuilder()
                 .expireAfterWrite(20, TimeUnit.SECONDS)
                 .removalListener((
-                     RemovalNotification<GroupKey, OfdpaNextGroup> notification) -> {
+                     RemovalNotification<GroupKey, List<OfdpaNextGroup>> notification) -> {
                          if (notification.getCause() == RemovalCause.EXPIRED) {
-                             fail(notification.getValue().nextObjective(),
-                                  ObjectiveError.GROUPINSTALLATIONFAILED);
+                             notification.getValue().forEach(ofdpaNextGrp ->
+                                 fail(ofdpaNextGrp.nextObj,
+                                      ObjectiveError.GROUPINSTALLATIONFAILED));
+
                          }
                 }).build();
 
@@ -212,7 +204,6 @@
         flowObjectiveStore = context.store();
         packetService = serviceDirectory.get(PacketService.class);
         deviceService = serviceDirectory.get(DeviceService.class);
-        packetService.addProcessor(processor, PacketProcessor.director(2));
         groupService.addListener(new InnerGroupListener());
 
         driverId = coreService.registerApplication(
@@ -271,7 +262,6 @@
                 log.warn("Unknown forwarding type {}", fwd.op());
         }
 
-
         flowRuleService.apply(flowOpsBuilder.build(new FlowRuleOperationsContext() {
             @Override
             public void onSuccess(FlowRuleOperations ops) {
@@ -283,7 +273,6 @@
                 fail(fwd, ObjectiveError.FLOWINSTALLATIONFAILED);
             }
         }));
-
     }
 
     @Override
@@ -697,17 +686,57 @@
      *            returned if there is an issue in processing the objective.
      */
     protected Collection<FlowRule> processSpecific(ForwardingObjective fwd) {
-        TrafficSelector selector = fwd.selector();
-        EthTypeCriterion ethType =
-                (EthTypeCriterion) selector.getCriterion(Criterion.Type.ETH_TYPE);
-        if ((ethType == null) ||
-                (ethType.ethType().toShort() != Ethernet.TYPE_IPV4) &&
-                (ethType.ethType().toShort() != Ethernet.MPLS_UNICAST)) {
-            log.warn("processSpecific: Unsupported "
-                    + "forwarding objective criteraia");
+        log.trace("Processing specific fwd objective:{} in dev:{} with next:{}",
+                  fwd.id(), deviceId, fwd.nextId());
+        boolean isEthTypeObj = isSupportedEthTypeObjective(fwd);
+        boolean isEthDstObj = isSupportedEthDstObjective(fwd);
+
+        if (isEthTypeObj) {
+            return processEthTypeSpecific(fwd);
+        } else if (isEthDstObj) {
+            return processEthDstSpecific(fwd);
+        } else {
+            log.warn("processSpecific: Unsupported forwarding objective "
+                    + "criteria fwd:{} in dev:{}", fwd.nextId(), deviceId);
             fail(fwd, ObjectiveError.UNSUPPORTED);
             return Collections.emptySet();
         }
+    }
+
+    private boolean isSupportedEthTypeObjective(ForwardingObjective fwd) {
+        TrafficSelector selector = fwd.selector();
+        EthTypeCriterion ethType = (EthTypeCriterion) selector
+                .getCriterion(Criterion.Type.ETH_TYPE);
+        if ((ethType == null) ||
+                ((ethType.ethType().toShort() != Ethernet.TYPE_IPV4) &&
+                        (ethType.ethType().toShort() != Ethernet.MPLS_UNICAST))) {
+            return false;
+        }
+        return true;
+    }
+
+    private boolean isSupportedEthDstObjective(ForwardingObjective fwd) {
+        TrafficSelector selector = fwd.selector();
+        EthCriterion ethDst = (EthCriterion) selector
+                .getCriterion(Criterion.Type.ETH_DST);
+        VlanIdCriterion vlanId = (VlanIdCriterion) selector
+                .getCriterion(Criterion.Type.VLAN_VID);
+        if (ethDst == null && vlanId == null) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Handles forwarding rules to the IP and MPLS tables.
+     *
+     * @param fwd the forwarding objective
+     * @return A collection of flow rules, or an empty set
+     */
+    protected Collection<FlowRule> processEthTypeSpecific(ForwardingObjective fwd) {
+        TrafficSelector selector = fwd.selector();
+        EthTypeCriterion ethType =
+                (EthTypeCriterion) selector.getCriterion(Criterion.Type.ETH_TYPE);
 
         int forTableId = -1;
         TrafficSelector.Builder filteredSelector = DefaultTrafficSelector.builder();
@@ -716,8 +745,8 @@
                 .matchIPDst(((IPCriterion)
                         selector.getCriterion(Criterion.Type.IPV4_DST)).ip());
             forTableId = UNICAST_ROUTING_TABLE;
-            log.debug("processing IPv4 specific forwarding objective {} in dev:{}",
-                      fwd.id(), deviceId);
+            log.debug("processing IPv4 specific forwarding objective {} -> next:{}"
+                    + " in dev:{}", fwd.id(), fwd.nextId(), deviceId);
         } else {
             filteredSelector
                 .matchEthType(Ethernet.MPLS_UNICAST)
@@ -729,8 +758,8 @@
                 filteredSelector.matchMplsBos(bos.mplsBos());
             }
             forTableId = MPLS_TABLE_1;
-            log.debug("processing MPLS specific forwarding objective {} in dev {}",
-                      fwd.id(), deviceId);
+            log.debug("processing MPLS specific forwarding objective {} -> next:{}"
+                    + " in dev {}", fwd.id(), fwd.nextId(), deviceId);
         }
 
         TrafficTreatment.Builder tb = DefaultTrafficTreatment.builder();
@@ -754,6 +783,7 @@
                 // MPLS interface, or a MPLS SWAP (with-same) but that would
                 // have to be handled in the next-objective. Also the pop-mpls
                 // logic used here won't work in non-BoS case.
+                fail(fwd, ObjectiveError.FLOWINSTALLATIONFAILED);
                 return Collections.emptySet();
             }
 
@@ -762,7 +792,8 @@
             // we only need the top level group's key to point the flow to it
             Group group = groupService.getGroup(deviceId, gkeys.get(0).peekFirst());
             if (group == null) {
-                log.warn("The group left!");
+                log.warn("Group with key:{} for next-id:{} not found in dev:{}",
+                         gkeys.get(0).peekFirst(), fwd.nextId(), deviceId);
                 fail(fwd, ObjectiveError.GROUPMISSING);
                 return Collections.emptySet();
             }
@@ -786,6 +817,88 @@
         return Collections.singletonList(ruleBuilder.build());
     }
 
+    /**
+     * Handles forwarding rules to the L2 bridging table. Flow actions are not
+     * allowed in the bridging table - instead we use L2 Interface group or
+     * L2 flood group
+     *
+     * @param fwd the forwarding objective
+     * @return A collection of flow rules, or an empty set
+     */
+    protected Collection<FlowRule> processEthDstSpecific(ForwardingObjective fwd) {
+        List<FlowRule> rules = new ArrayList<>();
+
+        // Build filtered selector
+        TrafficSelector selector = fwd.selector();
+        EthCriterion ethCriterion = (EthCriterion) selector
+                .getCriterion(Criterion.Type.ETH_DST);
+        VlanIdCriterion vlanIdCriterion = (VlanIdCriterion) selector
+                .getCriterion(Criterion.Type.VLAN_VID);
+
+        if (vlanIdCriterion == null) {
+            log.warn("Forwarding objective for bridging requires vlan. Not "
+                    + "installing fwd:{} in dev:{}", fwd.id(), deviceId);
+            fail(fwd, ObjectiveError.BADPARAMS);
+            return Collections.emptySet();
+        }
+
+        TrafficSelector.Builder filteredSelectorBuilder =
+                DefaultTrafficSelector.builder();
+        // Do not match MacAddress for subnet broadcast entry
+        if (!ethCriterion.mac().equals(MacAddress.NONE)) {
+            filteredSelectorBuilder.matchEthDst(ethCriterion.mac());
+            log.debug("processing L2 forwarding objective:{} -> next:{} in dev:{}",
+                      fwd.id(), fwd.nextId(), deviceId);
+        } else {
+            log.debug("processing L2 Broadcast forwarding objective:{} -> next:{} "
+                    + "in dev:{} for vlan:{}",
+                      fwd.id(), fwd.nextId(), deviceId, vlanIdCriterion.vlanId());
+        }
+        filteredSelectorBuilder.matchVlanId(vlanIdCriterion.vlanId());
+        TrafficSelector filteredSelector = filteredSelectorBuilder.build();
+
+        if (fwd.treatment() != null) {
+            log.warn("Ignoring traffic treatment in fwd rule {} meant for L2 table"
+                    + "for dev:{}. Expecting only nextId", fwd.id(), deviceId);
+        }
+
+        TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
+        if (fwd.nextId() != null) {
+            NextGroup next = flowObjectiveStore.getNextGroup(fwd.nextId());
+            if (next != null) {
+                List<Deque<GroupKey>> gkeys = appKryo.deserialize(next.data());
+                // we only need the top level group's key to point the flow to it
+                Group group = groupService.getGroup(deviceId, gkeys.get(0).peekFirst());
+                if (group != null) {
+                    treatmentBuilder.deferred().group(group.id());
+                } else {
+                    log.warn("Group with key:{} for next-id:{} not found in dev:{}",
+                             gkeys.get(0).peekFirst(), fwd.nextId(), deviceId);
+                    fail(fwd, ObjectiveError.GROUPMISSING);
+                    return Collections.emptySet();
+                }
+            }
+        }
+        treatmentBuilder.immediate().transition(ACL_TABLE);
+        TrafficTreatment filteredTreatment = treatmentBuilder.build();
+
+        // Build bridging table entries
+        FlowRule.Builder flowRuleBuilder = DefaultFlowRule.builder();
+        flowRuleBuilder.fromApp(fwd.appId())
+                .withPriority(fwd.priority())
+                .forDevice(deviceId)
+                .withSelector(filteredSelector)
+                .withTreatment(filteredTreatment)
+                .forTable(BRIDGING_TABLE);
+        if (fwd.permanent()) {
+            flowRuleBuilder.makePermanent();
+        } else {
+            flowRuleBuilder.makeTemporary(fwd.timeout());
+        }
+        rules.add(flowRuleBuilder.build());
+        return rules;
+    }
+
     private void pass(Objective obj) {
         if (obj.context().isPresent()) {
             obj.context().get().onSuccess(obj);
@@ -842,9 +955,26 @@
      * @param nextObj  the nextObjective of type SIMPLE
      */
     private void processSimpleNextObjective(NextObjective nextObj) {
-        // break up simple next objective to GroupChain objects
         TrafficTreatment treatment = nextObj.next().iterator().next();
+        // determine if plain L2 or L3->L2
+        boolean plainL2 = true;
+        for (Instruction ins : treatment.allInstructions()) {
+            if (ins.type() == Instruction.Type.L2MODIFICATION) {
+                L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
+                if (l2ins.subtype() == L2SubType.ETH_DST ||
+                        l2ins.subtype() == L2SubType.ETH_SRC) {
+                    plainL2 = false;
+                    break;
+                }
+            }
+        }
 
+        if (plainL2) {
+            createL2InterfaceGroup(nextObj);
+            return;
+        }
+
+        // break up simple next objective to GroupChain objects
         GroupInfo groupInfo = createL2L3Chain(treatment, nextObj.id(),
                                               nextObj.appId(), false,
                                               nextObj.meta());
@@ -860,8 +990,8 @@
                                            Collections.singletonList(gkeyChain),
                                            nextObj);
 
-        // store l3groupkey with the ofdpaGroupChain for the nextObjective that depends on it
-        pendingNextObjectives.put(groupInfo.outerGrpDesc.appCookie(), ofdpaGrp);
+        // store l3groupkey with the ofdpaNextGroup for the nextObjective that depends on it
+        updatePendingNextObjective(groupInfo.outerGrpDesc.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
@@ -869,6 +999,98 @@
         groupService.addGroup(groupInfo.innerGrpDesc);
     }
 
+    private void updatePendingNextObjective(GroupKey key, OfdpaNextGroup value) {
+        List<OfdpaNextGroup> nextList = new CopyOnWriteArrayList<OfdpaNextGroup>();
+        nextList.add(value);
+        List<OfdpaNextGroup> ret = pendingNextObjectives.asMap()
+                .putIfAbsent(key, nextList);
+        if (ret != null) {
+            ret.add(value);
+        }
+    }
+
+    /**
+     * Creates a simple L2 Interface Group.
+     *
+     * @param nextObj the next Objective
+     */
+    private void createL2InterfaceGroup(NextObjective nextObj) {
+        // only allowed actions are vlan pop and outport
+        TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
+        PortNumber portNum = null;
+        for (Instruction ins : nextObj.next().iterator().next().allInstructions()) {
+            if (ins.type() == Instruction.Type.L2MODIFICATION) {
+                L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
+                switch (l2ins.subtype()) {
+                case VLAN_POP:
+                    ttb.add(l2ins);
+                    break;
+                default:
+                    break;
+                }
+            } else if (ins.type() == Instruction.Type.OUTPUT) {
+                portNum = ((OutputInstruction) ins).port();
+                ttb.add(ins);
+            } else {
+                log.warn("Driver does not handle this type of TrafficTreatment"
+                        + " instruction in simple nextObjectives:  {}", ins.type());
+            }
+        }
+        //use the vlanid associated with the port
+        VlanId vlanid = port2Vlan.get(portNum);
+
+        if (vlanid == null && nextObj.meta() != null) {
+            // use metadata vlan info if available
+            Criterion vidCriterion = nextObj.meta().getCriterion(Type.VLAN_VID);
+            if (vidCriterion != null) {
+                vlanid = ((VlanIdCriterion) vidCriterion).vlanId();
+            }
+        }
+
+        if (vlanid == null) {
+            log.error("Driver cannot process an L2/L3 group chain without "
+                    + "egress vlan information for dev: {} port:{}",
+                    deviceId, portNum);
+            return;
+        }
+
+        // assemble information for ofdpa l2interface group
+        Integer l2groupId = L2INTERFACEMASK | (vlanid.toShort() << 16) | (int) portNum.toLong();
+        // a globally unique groupkey that is different for ports in the same devices
+        // 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 = 0x0ffffff & (deviceId.hashCode() << 8 | (int) portNum.toLong());
+        final GroupKey l2groupkey = new DefaultGroupKey(appKryo.serialize(l2gk));
+
+        // create group description for the l2interfacegroup
+        GroupBucket l2interfaceGroupBucket =
+                DefaultGroupBucket.createIndirectGroupBucket(ttb.build());
+        GroupDescription l2groupDescription =
+                             new DefaultGroupDescription(
+                                     deviceId,
+                                     GroupDescription.Type.INDIRECT,
+                                     new GroupBuckets(Collections.singletonList(
+                                                          l2interfaceGroupBucket)),
+                                     l2groupkey,
+                                     l2groupId,
+                                     nextObj.appId());
+        log.debug("Trying L2Interface: device:{} gid:{} gkey:{} nextId:{}",
+                  deviceId, Integer.toHexString(l2groupId),
+                  l2groupkey, nextObj.id());
+
+        // create object for local and distributed storage
+        Deque<GroupKey> singleKey = new ArrayDeque<>();
+        singleKey.addFirst(l2groupkey);
+        OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(
+                                           Collections.singletonList(singleKey),
+                                           nextObj);
+
+        // store l2groupkey for the nextObjective that depends on it
+        updatePendingNextObjective(l2groupkey, ofdpaGrp);
+        // send the group description to the group service
+        groupService.addGroup(l2groupDescription);
+    }
+
     /**
      * Creates one of two possible group-chains from the treatment
      * passed in. Depending on the MPLS boolean, this method either creates
@@ -895,6 +1117,7 @@
         TrafficTreatment.Builder innerTtb = DefaultTrafficTreatment.builder();
         VlanId vlanid = null;
         long portNum = 0;
+        boolean setVlan = false, popVlan = false;
         for (Instruction ins : treatment.allInstructions()) {
             if (ins.type() == Instruction.Type.L2MODIFICATION) {
                 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
@@ -908,9 +1131,11 @@
                 case VLAN_ID:
                     vlanid = ((ModVlanIdInstruction) l2ins).vlanId();
                     outerTtb.setVlanId(vlanid);
+                    setVlan = true;
                     break;
                 case VLAN_POP:
                     innerTtb.popVlan();
+                    popVlan = true;
                     break;
                 case DEC_MPLS_TTL:
                 case MPLS_LABEL:
@@ -935,12 +1160,11 @@
             vlanid = port2Vlan.get(PortNumber.portNumber(portNum));
         }
 
-        if (vlanid == null) {
-            // use metadata
-            for (Criterion metaCriterion : meta.criteria()) {
-                if (metaCriterion.type() == Type.VLAN_VID) {
-                    vlanid = ((VlanIdCriterion) metaCriterion).vlanId();
-                }
+        if (vlanid == null && meta != null) {
+            // use metadata if available
+            Criterion vidCriterion = meta.getCriterion(Type.VLAN_VID);
+            if (vidCriterion != null) {
+                vlanid = ((VlanIdCriterion) vidCriterion).vlanId();
             }
         }
 
@@ -951,6 +1175,14 @@
             return null;
         }
 
+        if (!setVlan && !popVlan) {
+            // untagged outgoing port
+            TrafficTreatment.Builder temp = DefaultTrafficTreatment.builder();
+            temp.popVlan();
+            innerTtb.build().allInstructions().forEach(i -> temp.add(i));
+            innerTtb = temp;
+        }
+
         // assemble information for ofdpa l2interface group
         Integer l2groupId = L2INTERFACEMASK | (vlanid.toShort() << 16) | (int) portNum;
         // a globally unique groupkey that is different for ports in the same devices
@@ -1077,6 +1309,7 @@
             }
 
             // also ensure that all ports are in the same vlan
+            // XXX maybe HA issue here?
             VlanId thisvlanid = port2Vlan.get(portNum);
             if (vlanid == null) {
                 vlanid = thisvlanid;
@@ -1151,7 +1384,7 @@
 
         // store l2floodgroupkey with the ofdpaGroupChain for the nextObjective
         // that depends on it
-        pendingNextObjectives.put(l2floodgroupkey, ofdpaGrp);
+        updatePendingNextObjective(l2floodgroupkey, ofdpaGrp);
 
         for (GroupDescription l2intGrpDesc : l2interfaceGroupDescs) {
             // store all l2groupkeys with the groupChainElem for the l2floodgroup
@@ -1336,7 +1569,7 @@
 
         // store l3ecmpGroupKey with the ofdpaGroupChain for the nextObjective
         // that depends on it
-        pendingNextObjectives.put(l3ecmpGroupKey, ofdpaGrp);
+        updatePendingNextObjective(l3ecmpGroupKey, ofdpaGrp);
 
         log.debug("Trying L3ECMP: device:{} gid:{} gkey:{} nextId:{}",
                   deviceId, Integer.toHexString(l3ecmpGroupId),
@@ -1422,16 +1655,18 @@
                         processGroupChain(gce);
                     }
                 } else {
-                    OfdpaNextGroup obj = pendingNextObjectives.getIfPresent(key);
-                    if (obj != null) {
-                        log.info("Group service processed group key {} in device:{}. "
-                                + "Done implementing next objective: {} <<-->> gid:{}",
-                                key, deviceId, obj.nextObjective().id(),
-                                Integer.toHexString(groupService.getGroup(deviceId, key)
-                                                    .givenGroupId()));
-                        pass(obj.nextObjective());
+                    List<OfdpaNextGroup> objList = pendingNextObjectives.getIfPresent(key);
+                    if (objList != null) {
                         pendingNextObjectives.invalidate(key);
-                        flowObjectiveStore.putNextGroup(obj.nextObjective().id(), obj);
+                        objList.forEach(obj -> {
+                            log.info("Group service processed group key {} in device:{}. "
+                                    + "Done implementing next objective: {} <<-->> gid:{}",
+                                    key, deviceId, obj.nextObjective().id(),
+                                    Integer.toHexString(groupService.getGroup(deviceId, key)
+                                                        .givenGroupId()));
+                            pass(obj.nextObjective());
+                            flowObjectiveStore.putNextGroup(obj.nextObjective().id(), obj);
+                        });
                     }
                 }
             });
@@ -1455,16 +1690,18 @@
                         processGroupChain(gce);
                     }
                 } else {
-                    OfdpaNextGroup obj = pendingNextObjectives.getIfPresent(key);
-                    if (obj != null) {
-                        log.info("group ADDED with key {} in dev {}.. Done implementing next "
-                                + "objective: {} <<-->> gid:{}",
-                                key, deviceId, obj.nextObjective().id(),
-                                Integer.toHexString(groupService.getGroup(deviceId, key)
-                                                    .givenGroupId()));
-                        pass(obj.nextObjective());
+                    List<OfdpaNextGroup> objList = pendingNextObjectives.getIfPresent(key);
+                    if (objList != null) {
                         pendingNextObjectives.invalidate(key);
-                        flowObjectiveStore.putNextGroup(obj.nextObjective().id(), obj);
+                        objList.forEach(obj -> {
+                            log.info("group ADDED with key {} in dev {}.. Done implementing next "
+                                    + "objective: {} <<-->> gid:{}",
+                                    key, deviceId, obj.nextObjective().id(),
+                                    Integer.toHexString(groupService.getGroup(deviceId, key)
+                                                        .givenGroupId()));
+                            pass(obj.nextObjective());
+                            flowObjectiveStore.putNextGroup(obj.nextObjective().id(), obj);
+                        });
                     }
                 }
             }
@@ -1550,418 +1787,6 @@
                     " waiting-on-groups: " + waitOnGroups.get() +
                     " device: " + deviceId);
         }
-
-    }
-
-    //////////////////////////////////////
-    //  Test code to be used for future
-    //  static-flow-pusher app
-    //////////////////////////////////////
-
-    public void processStaticFlows() {
-        //processPortTable();
-        processGroupTable();
-        processVlanTable();
-        processTmacTable();
-        processIpTable();
-        //processMcastTable();
-        //processBridgingTable();
-        processAclTable();
-        sendPackets();
-        processMplsTable();
-    }
-
-    protected void processGroupTable() {
-        TrafficTreatment.Builder act = DefaultTrafficTreatment.builder();
-
-        act.popVlan(); // to send out untagged packets
-        act.setOutput(PortNumber.portNumber(24));
-        GroupBucket bucket =
-                DefaultGroupBucket.createIndirectGroupBucket(act.build());
-        final GroupKey groupkey = new DefaultGroupKey(appKryo.serialize(500));
-        Integer groupId = 0x00c80018; //l2 interface, vlan 200, port 24
-        GroupDescription groupDescription = new DefaultGroupDescription(deviceId,
-                             GroupDescription.Type.INDIRECT,
-                             new GroupBuckets(Collections.singletonList(bucket)),
-                             groupkey,
-                             groupId,
-                             driverId);
-        groupService.addGroup(groupDescription);
-
-        TrafficTreatment.Builder act2 = DefaultTrafficTreatment.builder();
-        act2.setOutput(PortNumber.portNumber(40));
-        GroupBucket bucket2 = DefaultGroupBucket.createIndirectGroupBucket(act2.build());
-        final GroupKey groupkey2 = new DefaultGroupKey(appKryo.serialize(502));
-        Integer groupId2 = 0x00c50028; //l2 interface, vlan 197, port 40
-        GroupDescription groupDescription2 = new DefaultGroupDescription(deviceId,
-                             GroupDescription.Type.INDIRECT,
-                             new GroupBuckets(Collections.singletonList(bucket2)),
-                             groupkey2,
-                             groupId2,
-                             driverId);
-        groupService.addGroup(groupDescription2);
-
-        while (groupService.getGroup(deviceId, groupkey2) == null) {
-            try {
-                Thread.sleep(500);
-            } catch (InterruptedException e) {
-                // TODO Auto-generated catch block
-                e.printStackTrace();
-            }
-        }
-
-        //Now for L3 Unicast group
-        TrafficTreatment.Builder act3 = DefaultTrafficTreatment.builder();
-        act3.setEthDst(MacAddress.valueOf(0x2020));
-        act3.setEthSrc(MacAddress.valueOf(0x1010));
-        act3.setVlanId(VlanId.vlanId((short) 200));
-        act3.group(new DefaultGroupId(0x00c80018)); // point to L2 interface
-        // MPLS interface group - does not work for popping single label
-        //Integer secGroupId = MPLSINTERFACEMASK | 38; // 0x90000026
-        Integer groupId3 = L3UNICASTMASK | 1; // 0x20000001
-        GroupBucket bucket3 =
-                DefaultGroupBucket.createIndirectGroupBucket(act3.build());
-        final GroupKey groupkey3 = new DefaultGroupKey(appKryo.serialize(503));
-        GroupDescription groupDescription3 = new DefaultGroupDescription(deviceId,
-                             GroupDescription.Type.INDIRECT,
-                             new GroupBuckets(Collections.singletonList(bucket3)),
-                             groupkey3,
-                             groupId3,
-                             driverId);
-        groupService.addGroup(groupDescription3);
-
-        //Another L3 Unicast group
-        TrafficTreatment.Builder act4 = DefaultTrafficTreatment.builder();
-        act4.setEthDst(MacAddress.valueOf(0x3030));
-        act4.setEthSrc(MacAddress.valueOf(0x1010));
-        act4.setVlanId(VlanId.vlanId((short) 197));
-        act4.group(new DefaultGroupId(0x00c50028)); // point to L2 interface
-        Integer groupId4 = L3UNICASTMASK | 2; // 0x20000002
-        GroupBucket bucket4 =
-                DefaultGroupBucket.createIndirectGroupBucket(act4.build());
-        final GroupKey groupkey4 = new DefaultGroupKey(appKryo.serialize(504));
-        GroupDescription groupDescription4 = new DefaultGroupDescription(deviceId,
-                             GroupDescription.Type.INDIRECT,
-                             new GroupBuckets(Collections.singletonList(bucket4)),
-                             groupkey4,
-                             groupId4,
-                             driverId);
-        groupService.addGroup(groupDescription4);
-
-        while (groupService.getGroup(deviceId, groupkey4) == null) {
-            try {
-                Thread.sleep(500);
-            } catch (InterruptedException e) {
-                // TODO Auto-generated catch block
-                e.printStackTrace();
-            }
-        }
-
-        // L3 ecmp group
-        TrafficTreatment.Builder act5 = DefaultTrafficTreatment.builder();
-        act5.group(new DefaultGroupId(0x20000001));
-        TrafficTreatment.Builder act6 = DefaultTrafficTreatment.builder();
-        act6.group(new DefaultGroupId(0x20000002));
-        GroupBucket buckete1 =
-                DefaultGroupBucket.createSelectGroupBucket(act5.build());
-        GroupBucket buckete2 =
-                DefaultGroupBucket.createSelectGroupBucket(act6.build());
-        List<GroupBucket> bktlist = new ArrayList<GroupBucket>();
-        bktlist.add(buckete1);
-        bktlist.add(buckete2);
-        final GroupKey groupkey5 = new DefaultGroupKey(appKryo.serialize(505));
-        Integer groupId5 = L3ECMPMASK | 5; // 0x70000005
-        GroupDescription groupDescription5 = new DefaultGroupDescription(deviceId,
-                             GroupDescription.Type.SELECT,
-                             new GroupBuckets(bktlist),
-                             groupkey5,
-                             groupId5,
-                             driverId);
-        groupService.addGroup(groupDescription5);
-
-
-    }
-
-    @SuppressWarnings("deprecation")
-    protected void processMplsTable() {
-        FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
-        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
-        selector.matchEthType(Ethernet.MPLS_UNICAST);
-        selector.matchMplsLabel(MplsLabel.mplsLabel(0xff)); //255
-        selector.matchMplsBos(true);
-        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
-        treatment.decMplsTtl(); // nw_ttl does not work
-        treatment.copyTtlIn();
-        treatment.popMpls(Ethernet.TYPE_IPV4);
-        treatment.deferred().group(new DefaultGroupId(0x20000001)); // point to L3 Unicast
-        //treatment.deferred().group(new DefaultGroupId(0x70000005)); // point to L3 ECMP
-        treatment.transition(ACL_TABLE);
-        FlowRule test = DefaultFlowRule.builder().forDevice(deviceId)
-                .withSelector(selector.build()).withTreatment(treatment.build())
-                .withPriority(DEFAULT_PRIORITY).fromApp(driverId).makePermanent()
-                .forTable(24).build();
-        ops = ops.add(test);
-
-        flowRuleService.apply(ops.build(new FlowRuleOperationsContext() {
-            @Override
-            public void onSuccess(FlowRuleOperations ops) {
-                log.info("Initialized mpls table");
-            }
-
-            @Override
-            public void onError(FlowRuleOperations ops) {
-                log.info("Failed to initialize mpls table");
-            }
-        }));
-
-    }
-
-    protected void processPortTable() {
-        FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
-        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
-        selector.matchInPort(PortNumber.portNumber(0)); // should be maskable?
-        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
-        treatment.transition(VLAN_TABLE);
-        FlowRule tmisse = DefaultFlowRule.builder()
-                .forDevice(deviceId)
-                .withSelector(selector.build())
-                .withTreatment(treatment.build())
-                .withPriority(LOWEST_PRIORITY)
-                .fromApp(driverId)
-                .makePermanent()
-                .forTable(PORT_TABLE).build();
-        ops = ops.add(tmisse);
-
-        flowRuleService.apply(ops.build(new FlowRuleOperationsContext() {
-            @Override
-            public void onSuccess(FlowRuleOperations ops) {
-                log.info("Initialized port table");
-            }
-
-            @Override
-            public void onError(FlowRuleOperations ops) {
-                log.info("Failed to initialize port table");
-            }
-        }));
-
-    }
-
-    private void processVlanTable() {
-        // Table miss entry is not required as ofdpa default is to drop
-        // In OF terms, the absence of a t.m.e. also implies drop
-        FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
-        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
-        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
-        selector.matchInPort(PortNumber.portNumber(12));
-        selector.matchVlanId(VlanId.vlanId((short) 100));
-        treatment.transition(TMAC_TABLE);
-        FlowRule rule = DefaultFlowRule.builder()
-                .forDevice(deviceId)
-                .withSelector(selector.build())
-                .withTreatment(treatment.build())
-                .withPriority(DEFAULT_PRIORITY)
-                .fromApp(driverId)
-                .makePermanent()
-                .forTable(VLAN_TABLE).build();
-        ops =  ops.add(rule);
-        flowRuleService.apply(ops.build(new FlowRuleOperationsContext() {
-            @Override
-            public void onSuccess(FlowRuleOperations ops) {
-                log.info("Initialized vlan table");
-            }
-
-            @Override
-            public void onError(FlowRuleOperations ops) {
-                log.info("Failed to initialize vlan table");
-            }
-        }));
-    }
-
-    protected void processTmacTable() {
-        //table miss entry
-        FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
-        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
-        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
-        selector.matchInPort(PortNumber.portNumber(12));
-        selector.matchVlanId(VlanId.vlanId((short) 100));
-        selector.matchEthType(Ethernet.TYPE_IPV4);
-        selector.matchEthDst(MacAddress.valueOf("00:00:00:00:00:02"));
-        treatment.transition(UNICAST_ROUTING_TABLE);
-        FlowRule rule = DefaultFlowRule.builder()
-                .forDevice(deviceId)
-                .withSelector(selector.build())
-                .withTreatment(treatment.build())
-                .withPriority(DEFAULT_PRIORITY)
-                .fromApp(driverId)
-                .makePermanent()
-                .forTable(TMAC_TABLE).build();
-        ops = ops.add(rule);
-
-        selector.matchEthType(Ethernet.MPLS_UNICAST);
-        treatment.transition(MPLS_TABLE_0);
-        FlowRule rulempls = DefaultFlowRule.builder()
-                .forDevice(deviceId)
-                .withSelector(selector.build())
-                .withTreatment(treatment.build())
-                .withPriority(DEFAULT_PRIORITY)
-                .fromApp(driverId)
-                .makePermanent()
-                .forTable(TMAC_TABLE).build();
-        ops = ops.add(rulempls);
-
-        flowRuleService.apply(ops.build(new FlowRuleOperationsContext() {
-            @Override
-            public void onSuccess(FlowRuleOperations ops) {
-                log.info("Initialized tmac table");
-            }
-
-            @Override
-            public void onError(FlowRuleOperations ops) {
-                log.info("Failed to initialize tmac table");
-            }
-        }));
-    }
-
-    protected void processIpTable() {
-        //table miss entry
-        FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
-        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
-        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
-        selector.matchEthType(Ethernet.TYPE_IPV4);
-        selector.matchIPDst(IpPrefix.valueOf("2.0.0.0/16"));
-        treatment.deferred().group(new DefaultGroupId(0x20000001));
-        treatment.transition(ACL_TABLE);
-        FlowRule rule = DefaultFlowRule.builder()
-                .forDevice(deviceId)
-                .withSelector(selector.build())
-                .withTreatment(treatment.build())
-                .withPriority(30000)
-                .fromApp(driverId)
-                .makePermanent()
-                .forTable(UNICAST_ROUTING_TABLE).build();
-        ops =  ops.add(rule);
-        flowRuleService.apply(ops.build(new FlowRuleOperationsContext() {
-            @Override
-            public void onSuccess(FlowRuleOperations ops) {
-                log.info("Initialized IP table");
-            }
-
-            @Override
-            public void onError(FlowRuleOperations ops) {
-                log.info("Failed to initialize unicast IP table");
-            }
-        }));
-    }
-
-    protected void processAclTable() {
-        //table miss entry
-        FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
-        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
-        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
-        selector.matchEthDst(MacAddress.valueOf("00:00:00:00:00:02"));
-        treatment.deferred().group(new DefaultGroupId(0x20000001));
-        FlowRule rule = DefaultFlowRule.builder()
-                .forDevice(deviceId)
-                .withSelector(selector.build())
-                .withTreatment(treatment.build())
-                .withPriority(60000)
-                .fromApp(driverId)
-                .makePermanent()
-                .forTable(ACL_TABLE).build();
-        ops =  ops.add(rule);
-        flowRuleService.apply(ops.build(new FlowRuleOperationsContext() {
-            @Override
-            public void onSuccess(FlowRuleOperations ops) {
-                log.info("Initialized Acl table");
-            }
-
-            @Override
-            public void onError(FlowRuleOperations ops) {
-                log.info("Failed to initialize Acl table");
-            }
-        }));
-    }
-
-    private void sendPackets() {
-        Ethernet eth = new Ethernet();
-        eth.setDestinationMACAddress("00:00:00:00:00:02");
-        eth.setSourceMACAddress("00:00:00:11:22:33");
-        eth.setVlanID((short) 100);
-        eth.setEtherType(Ethernet.MPLS_UNICAST);
-        MPLS mplsPkt = new MPLS();
-        mplsPkt.setLabel(255);
-        mplsPkt.setTtl((byte) 5);
-
-        IPv4 ipv4 = new IPv4();
-
-        ipv4.setDestinationAddress("4.0.5.6");
-        ipv4.setSourceAddress("1.0.2.3");
-        ipv4.setTtl((byte) 64);
-        ipv4.setChecksum((short) 0);
-
-        UDP udp = new UDP();
-        udp.setDestinationPort(666);
-        udp.setSourcePort(333);
-        udp.setPayload(new Data(new byte[]{(byte) 1, (byte) 2}));
-        udp.setChecksum((short) 0);
-
-        ipv4.setPayload(udp);
-        mplsPkt.setPayload(ipv4);
-        eth.setPayload(mplsPkt);
-
-        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .setOutput(PortNumber.portNumber(24))
-                .build();
-        OutboundPacket packet = new DefaultOutboundPacket(deviceId,
-                                                          treatment,
-                                                          ByteBuffer.wrap(eth.serialize()));
-
-
-        Ethernet eth2 = new Ethernet();
-        eth2.setDestinationMACAddress("00:00:00:00:00:02");
-        eth2.setSourceMACAddress("00:00:00:11:22:33");
-        eth2.setVlanID((short) 100);
-        eth2.setEtherType(Ethernet.TYPE_IPV4);
-
-        IPv4 ipv42 = new IPv4();
-        ipv42.setDestinationAddress("2.0.0.2");
-        ipv42.setSourceAddress("1.0.9.9");
-        ipv42.setTtl((byte) 64);
-        ipv42.setChecksum((short) 0);
-
-        UDP udp2 = new UDP();
-        udp2.setDestinationPort(999);
-        udp2.setSourcePort(333);
-        udp2.setPayload(new Data(new byte[]{(byte) 1, (byte) 2}));
-        udp2.setChecksum((short) 0);
-
-        ipv42.setPayload(udp2);
-        eth2.setPayload(ipv42);
-
-        TrafficTreatment treatment2 = DefaultTrafficTreatment.builder()
-                .setOutput(PortNumber.portNumber(26))
-                .build();
-        OutboundPacket packet2 = new DefaultOutboundPacket(deviceId,
-                                                          treatment2,
-                                                          ByteBuffer.wrap(eth2.serialize()));
-
-
-        log.info("Emitting packets now");
-        packetService.emit(packet);
-        packetService.emit(packet);
-        packetService.emit(packet2);
-        packetService.emit(packet);
-        packetService.emit(packet);
-        log.info("Done emitting packets");
-    }
-
-    private class InternalPacketProcessor implements PacketProcessor {
-
-        @Override
-        public void process(PacketContext context) {
-
-
-        }
     }
 
 }
diff --git a/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTP.java b/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTP.java
index 8ac5eec..dba4557 100644
--- a/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTP.java
+++ b/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTP.java
@@ -287,24 +287,14 @@
             case SIMPLE:
                 Collection<TrafficTreatment> treatments = nextObjective.next();
                 if (treatments.size() == 1) {
-                    TrafficTreatment treatment = treatments.iterator().next();
-                    GroupBucket bucket = DefaultGroupBucket
-                            .createIndirectGroupBucket(treatment);
-                    final GroupKey key = new DefaultGroupKey(
-                            appKryo.serialize(nextObjective
-                                    .id()));
-                    GroupDescription groupDescription = new DefaultGroupDescription(
-                            deviceId,
-                            GroupDescription.Type.INDIRECT,
-                            new GroupBuckets(
-                                    Collections.singletonList(bucket)),
-                            key,
-                            null,
-                            nextObjective.appId());
-                    log.debug("Creating SIMPLE group for next objective id {} "
-                            + "in dev:{}", nextObjective.id(), deviceId);
-                    pendingGroups.put(key, nextObjective);
-                    groupService.addGroup(groupDescription);
+                    // Spring Open TTP converts simple nextObjective to flow-actions
+                    // in a dummy group
+                    TrafficTreatment treatment = nextObjective.next().iterator().next();
+                    log.debug("Converting SIMPLE group for next objective id {} " +
+                            "to {} flow-actions in device:{}", nextObjective.id(),
+                            treatment.allInstructions().size(), deviceId);
+                    flowObjectiveStore.putNextGroup(nextObjective.id(),
+                                                    new SpringOpenGroup(null, treatment));
                 }
                 break;
             case HASHED:
@@ -624,8 +614,9 @@
             if (next != null) {
                 SpringOpenGroup soGroup = appKryo.deserialize(next.data());
                 if (soGroup.dummy) {
-                    log.debug("Adding flow-actions for fwd. obj. {} "
-                            + "in dev: {}", fwd.id(), deviceId);
+                    log.debug("Adding {} flow-actions for fwd. obj. {} -> next:{} "
+                            + "in dev: {}", soGroup.treatment.allInstructions().size(),
+                            fwd.id(), fwd.nextId(), deviceId);
                     for (Instruction ins : soGroup.treatment.allInstructions()) {
                         treatmentBuilder.add(ins);
                     }
@@ -639,7 +630,8 @@
                     }
                     treatmentBuilder.deferred().group(group.id());
                     log.debug("Adding OUTGROUP action to group:{} for fwd. obj. {} "
-                            + "in dev: {}", group.id(), fwd.id(), deviceId);
+                            + "for next:{} in dev: {}", group.id(), fwd.id(),
+                            fwd.nextId(), deviceId);
                 }
             } else {
                 log.warn("processSpecific: No associated next objective object");
@@ -705,10 +697,11 @@
             if (next != null) {
                 SpringOpenGroup soGrp = appKryo.deserialize(next.data());
                 if (soGrp.dummy) {
-                    log.debug("Adding flow-actions for fwd. obj. {} "
-                            + "in dev: {}", fwd.id(), deviceId);
+                    log.debug("Adding {} flow-actions for fwd. obj. {} "
+                            + "in dev: {}", soGrp.treatment.allInstructions().size(),
+                            fwd.id(), deviceId);
                     for (Instruction ins : soGrp.treatment.allInstructions()) {
-                        treatmentBuilder.add(ins);
+                        treatmentBuilder.deferred().add(ins);
                     }
                 } else {
                     GroupKey key = soGrp.key;
@@ -773,6 +766,12 @@
         return rules;
     }
 
+    /*
+     * Note: CpqD switches do not handle MPLS-related operation properly
+     * for a packet with VLAN tag. We pop VLAN here as a workaround.
+     * Side effect: HostService learns redundant hosts with same MAC but
+     * different VLAN. No known side effect on the network reachability.
+     */
     protected List<FlowRule> processEthDstFilter(EthCriterion ethCriterion,
                                        VlanIdCriterion vlanIdCriterion,
                                        FilteringObjective filt,
@@ -783,12 +782,6 @@
             vlanIdCriterion = (VlanIdCriterion) Criteria.matchVlanId(assignedVlan);
         }
 
-        /*
-         * Note: CpqD switches do not handle MPLS-related operation properly
-         * for a packet with VLAN tag. We pop VLAN here as a workaround.
-         * Side effect: HostService learns redundant hosts with same MAC but
-         * different VLAN. No known side effect on the network reachability.
-         */
         List<FlowRule> rules = new ArrayList<>();
         TrafficSelector.Builder selectorIp = DefaultTrafficSelector
                 .builder();