Changes to vRouter to accomodate ofdpa and softrouter pipelines.
Adding arp-spa to flow from vRouter to distinguish between multiple untagged
interfaces with the same macAddress.

Change-Id: Ifd6e00f70c538c780c0f5728d9ba960a4c70b1db
diff --git a/apps/routing/src/main/java/org/onosproject/routing/impl/ControlPlaneRedirectManager.java b/apps/routing/src/main/java/org/onosproject/routing/impl/ControlPlaneRedirectManager.java
index 2294435..b78b0ff 100644
--- a/apps/routing/src/main/java/org/onosproject/routing/impl/ControlPlaneRedirectManager.java
+++ b/apps/routing/src/main/java/org/onosproject/routing/impl/ControlPlaneRedirectManager.java
@@ -22,6 +22,7 @@
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.onlab.packet.EthType;
+import org.onlab.packet.VlanId;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 import org.onosproject.incubator.net.intf.Interface;
@@ -40,8 +41,10 @@
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.DefaultNextObjective;
 import org.onosproject.net.flowobjective.FlowObjectiveService;
 import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.NextObjective;
 import org.onosproject.net.host.InterfaceIpAddress;
 import org.onosproject.routing.RoutingService;
 import org.onosproject.routing.config.RouterConfig;
@@ -110,7 +113,7 @@
                 routingAppId, RoutingService.ROUTER_CONFIG_CLASS);
 
         if (config == null) {
-            log.info("Router config not available");
+            log.warn("Router config not available");
             return;
         }
 
@@ -145,6 +148,23 @@
         PortNumber controlPlanePort = controlPlaneConnectPoint.port();
 
         for (InterfaceIpAddress ip : intf.ipAddresses()) {
+            // create nextObjectives for forwarding to this interface and the
+            // controlPlaneConnectPoint
+            int cpNextId, intfNextId;
+            if (intf.vlan() == VlanId.NONE) {
+                cpNextId = createNextObjective(deviceId, controlPlanePort,
+                               VlanId.vlanId(SingleSwitchFibInstaller.ASSIGNED_VLAN),
+                               true);
+                intfNextId = createNextObjective(deviceId, intf.connectPoint().port(),
+                               VlanId.vlanId(SingleSwitchFibInstaller.ASSIGNED_VLAN),
+                               true);
+            } else {
+                cpNextId = createNextObjective(deviceId, controlPlanePort,
+                                               intf.vlan(), false);
+                intfNextId = createNextObjective(deviceId, intf.connectPoint().port(),
+                                                 intf.vlan(), false);
+            }
+
             // IPv4 to router
             TrafficSelector toSelector = DefaultTrafficSelector.builder()
                     .matchInPort(intf.connectPoint().port())
@@ -154,12 +174,8 @@
                     .matchIPDst(ip.ipAddress().toIpPrefix())
                     .build();
 
-            TrafficTreatment toTreatment = DefaultTrafficTreatment.builder()
-                    .setOutput(controlPlanePort)
-                    .build();
-
             flowObjectiveService.forward(deviceId,
-                    buildForwardingObjective(toSelector, toTreatment, true));
+                    buildForwardingObjective(toSelector, null, cpNextId, true));
 
             // IPv4 from router
             TrafficSelector fromSelector = DefaultTrafficSelector.builder()
@@ -170,12 +186,8 @@
                     .matchIPSrc(ip.ipAddress().toIpPrefix())
                     .build();
 
-            TrafficTreatment intfTreatment = DefaultTrafficTreatment.builder()
-                    .setOutput(intf.connectPoint().port())
-                    .build();
-
             flowObjectiveService.forward(deviceId,
-                    buildForwardingObjective(fromSelector, intfTreatment, true));
+                    buildForwardingObjective(fromSelector, null, intfNextId, true));
 
             // ARP to router
             toSelector = DefaultTrafficSelector.builder()
@@ -184,13 +196,12 @@
                     .matchVlanId(intf.vlan())
                     .build();
 
-            toTreatment = DefaultTrafficTreatment.builder()
-                    .setOutput(controlPlanePort)
+            TrafficTreatment puntTreatment = DefaultTrafficTreatment.builder()
                     .punt()
                     .build();
 
             flowObjectiveService.forward(deviceId,
-                    buildForwardingObjective(toSelector, toTreatment, true));
+                    buildForwardingObjective(toSelector, puntTreatment, cpNextId, true));
 
             // ARP from router
             fromSelector = DefaultTrafficSelector.builder()
@@ -198,15 +209,11 @@
                     .matchEthSrc(intf.mac())
                     .matchVlanId(intf.vlan())
                     .matchEthType(EthType.EtherType.ARP.ethType().toShort())
-                    .build();
-
-            intfTreatment = DefaultTrafficTreatment.builder()
-                    .setOutput(intf.connectPoint().port())
-                    .punt()
+                    .matchArpSpa(ip.ipAddress().getIp4Address())
                     .build();
 
             flowObjectiveService.forward(deviceId,
-                    buildForwardingObjective(fromSelector, intfTreatment, true));
+            buildForwardingObjective(fromSelector, puntTreatment, intfNextId, true));
         }
     }
 
@@ -219,33 +226,85 @@
                 .matchIPProtocol((byte) OSPF_IP_PROTO)
                 .build();
 
-        TrafficTreatment toTreatment = DefaultTrafficTreatment.builder()
-                .setOutput(controlPlaneConnectPoint.port())
-                .build();
-
+        // create nextObjectives for forwarding to the controlPlaneConnectPoint
+        DeviceId deviceId = controlPlaneConnectPoint.deviceId();
+        PortNumber controlPlanePort = controlPlaneConnectPoint.port();
+        int cpNextId;
+        if (intf.vlan() == VlanId.NONE) {
+            cpNextId = createNextObjective(deviceId, controlPlanePort,
+                           VlanId.vlanId(SingleSwitchFibInstaller.ASSIGNED_VLAN),
+                           true);
+        } else {
+            cpNextId = createNextObjective(deviceId, controlPlanePort,
+                                           intf.vlan(), false);
+        }
+        log.debug("ospf flows intf:{} nextid:{}", intf, cpNextId);
         flowObjectiveService.forward(controlPlaneConnectPoint.deviceId(),
-                buildForwardingObjective(toSelector, toTreatment, ospfEnabled));
+                buildForwardingObjective(toSelector, null, cpNextId, ospfEnabled));
     }
 
     /**
-     * Builds a forwarding objective from the given selector and treatment.
+     * Creates a next objective for forwarding to a port. Handles metadata for
+     * some pipelines that require vlan information for egress port.
+     *
+     * @param deviceId the device on which the next objective is being created
+     * @param portNumber the egress port
+     * @param vlanId vlan information for egress port
+     * @param popVlan if vlan tag should be popped or not
+     * @return nextId of the next objective created
+     */
+    private int createNextObjective(DeviceId deviceId, PortNumber portNumber,
+                                    VlanId vlanId, boolean popVlan) {
+        int nextId = flowObjectiveService.allocateNextId();
+        NextObjective.Builder nextObjBuilder = DefaultNextObjective
+                .builder().withId(nextId)
+                .withType(NextObjective.Type.SIMPLE)
+                .fromApp(appId);
+
+        TrafficTreatment.Builder ttBuilder = DefaultTrafficTreatment.builder();
+        if (popVlan) {
+            ttBuilder.popVlan();
+        }
+        ttBuilder.setOutput(portNumber);
+
+        // setup metadata to pass to nextObjective - indicate the vlan on egress
+        // if needed by the switch pipeline.
+        TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder();
+        metabuilder.matchVlanId(vlanId);
+
+        nextObjBuilder.withMeta(metabuilder.build());
+        nextObjBuilder.addTreatment(ttBuilder.build());
+        log.debug("Submited next objective {} in device {} for port/vlan {}/{}",
+                nextId, deviceId, portNumber, vlanId);
+        flowObjectiveService.next(deviceId, nextObjBuilder.add());
+        return nextId;
+    }
+
+    /**
+     * Builds a forwarding objective from the given selector, treatment and nextId.
      *
      * @param selector selector
-     * @param treatment treatment
+     * @param treatment treatment to apply to packet, can be null
+     * @param nextId next objective to point to for forwarding packet
      * @param add true to create an add objective, false to create a remove
      *            objective
      * @return forwarding objective
      */
     private ForwardingObjective buildForwardingObjective(TrafficSelector selector,
                                                          TrafficTreatment treatment,
+                                                         int nextId,
                                                          boolean add) {
-
-        ForwardingObjective.Builder fobBuilder = DefaultForwardingObjective.builder()
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .fromApp(appId)
-                .withPriority(PRIORITY)
-                .withFlag(ForwardingObjective.Flag.VERSATILE);
+        DefaultForwardingObjective.Builder fobBuilder = DefaultForwardingObjective.builder();
+        fobBuilder.withSelector(selector);
+        if (treatment != null) {
+            fobBuilder.withTreatment(treatment);
+        }
+        if (nextId != -1) {
+            fobBuilder.nextStep(nextId);
+        }
+        fobBuilder.fromApp(appId)
+            .withPriority(PRIORITY)
+            .withFlag(ForwardingObjective.Flag.VERSATILE);
 
         return add ? fobBuilder.add() : fobBuilder.remove();
     }
diff --git a/apps/routing/src/main/java/org/onosproject/routing/impl/SingleSwitchFibInstaller.java b/apps/routing/src/main/java/org/onosproject/routing/impl/SingleSwitchFibInstaller.java
index a30c42c..ee9958c 100644
--- a/apps/routing/src/main/java/org/onosproject/routing/impl/SingleSwitchFibInstaller.java
+++ b/apps/routing/src/main/java/org/onosproject/routing/impl/SingleSwitchFibInstaller.java
@@ -34,6 +34,7 @@
 import org.onosproject.core.CoreService;
 import org.onosproject.incubator.net.intf.Interface;
 import org.onosproject.incubator.net.intf.InterfaceService;
+import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.config.NetworkConfigEvent;
 import org.onosproject.net.config.NetworkConfigListener;
@@ -80,6 +81,8 @@
     private static final int PRIORITY_OFFSET = 100;
     private static final int PRIORITY_MULTIPLIER = 5;
 
+    public static final short ASSIGNED_VLAN = 4094;
+
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected CoreService coreService;
 
@@ -103,7 +106,8 @@
     // Device id of data-plane switch - should be learned from config
     private DeviceId deviceId;
 
-    private ApplicationId appId;
+    private ConnectPoint controlPlaneConnectPoint;
+
     private ApplicationId routerAppId;
 
     // Reference count for how many times a next hop is used by a route
@@ -121,11 +125,8 @@
 
     @Activate
     protected void activate() {
-        // TODO why are there two of the same app ID?
         routerAppId = coreService.registerApplication(RoutingService.ROUTER_APP_ID);
 
-        appId = coreService.getAppId(RoutingService.ROUTER_APP_ID);
-
         deviceListener = new InternalDeviceListener();
         deviceService.addListener(deviceListener);
 
@@ -156,6 +157,8 @@
             log.info("Router config not available");
             return;
         }
+        controlPlaneConnectPoint = routerConfig.getControlPlaneConnectPoint();
+        log.info("Control Plane Connect Point: {}", controlPlaneConnectPoint);
 
         deviceId = routerConfig.getControlPlaneConnectPoint().deviceId();
 
@@ -231,7 +234,7 @@
         int priority = prefix.prefixLength() * PRIORITY_MULTIPLIER + PRIORITY_OFFSET;
 
         ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective.builder()
-                .fromApp(appId)
+                .fromApp(routerAppId)
                 .makePermanent()
                 .withSelector(selector)
                 .withPriority(priority)
@@ -266,23 +269,30 @@
                     .setEthSrc(egressIntf.mac())
                     .setEthDst(nextHop.mac());
 
+            TrafficSelector.Builder metabuilder = null;
             if (!egressIntf.vlan().equals(VlanId.NONE)) {
                 treatment.pushVlan()
                         .setVlanId(egressIntf.vlan())
                         .setVlanPcp((byte) 0);
+            } else {
+                // untagged outgoing port may require internal vlan in some pipelines
+                metabuilder = DefaultTrafficSelector.builder();
+                metabuilder.matchVlanId(VlanId.vlanId(ASSIGNED_VLAN));
             }
 
             treatment.setOutput(egressIntf.connectPoint().port());
 
             int nextId = flowObjectiveService.allocateNextId();
-
-            NextObjective nextObjective = DefaultNextObjective.builder()
+            NextObjective.Builder nextBuilder = DefaultNextObjective.builder()
                     .withId(nextId)
                     .addTreatment(treatment.build())
                     .withType(NextObjective.Type.SIMPLE)
-                    .fromApp(appId)
-                    .add(); // TODO add callbacks
+                    .fromApp(routerAppId);
+            if (metabuilder != null) {
+                nextBuilder.withMeta(metabuilder.build());
+            }
 
+            NextObjective nextObjective = nextBuilder.add(); // TODO add callbacks
             flowObjectiveService.next(deviceId, nextObjective);
 
             nextHops.put(nextHop.ip(), nextId);
@@ -329,33 +339,48 @@
             }
 
             FilteringObjective.Builder fob = DefaultFilteringObjective.builder();
+            // first add filter for the interface
             fob.withKey(Criteria.matchInPort(intf.connectPoint().port()))
-                    .addCondition(Criteria.matchEthDst(intf.mac()))
-                    .addCondition(Criteria.matchVlanId(intf.vlan()));
-
+                .addCondition(Criteria.matchEthDst(intf.mac()))
+                .addCondition(Criteria.matchVlanId(intf.vlan()));
             fob.withPriority(PRIORITY_OFFSET);
+            if (intf.vlan() == VlanId.NONE) {
+                TrafficTreatment tt = DefaultTrafficTreatment.builder()
+                        .pushVlan().setVlanId(VlanId.vlanId(ASSIGNED_VLAN)).build();
+                fob.withMeta(tt);
+            }
 
-            fob.permit().fromApp(appId);
-            flowObjectiveService.filter(
-                    deviceId,
-                    fob.add(new ObjectiveContext() {
-                        @Override
-                        public void onSuccess(Objective objective) {
-                            log.info("Successfully installed interface based "
-                                    + "filtering objectives for intf {}", intf);
-                        }
-
-                        @Override
-                        public void onError(Objective objective,
-                                            ObjectiveError error) {
-                            log.error("Failed to install interface filters for intf {}: {}",
-                                    intf, error);
-                            // TODO something more than just logging
-                        }
-                    }));
+            fob.permit().fromApp(routerAppId);
+            sendFilteringObjective(install, fob, intf);
+            if (controlPlaneConnectPoint != null) {
+                // then add the same mac/vlan filters for control-plane connect point
+                fob.withKey(Criteria.matchInPort(controlPlaneConnectPoint.port()));
+                sendFilteringObjective(install, fob, intf);
+            }
         }
     }
 
+    private void sendFilteringObjective(boolean install, FilteringObjective.Builder fob,
+                                        Interface intf) {
+        flowObjectiveService.filter(
+            deviceId,
+            fob.add(new ObjectiveContext() {
+                @Override
+                public void onSuccess(Objective objective) {
+                    log.info("Successfully installed interface based "
+                            + "filtering objectives for intf {}", intf);
+                }
+
+                @Override
+                public void onError(Objective objective,
+                                    ObjectiveError error) {
+                    log.error("Failed to install interface filters for intf {}: {}",
+                              intf, error);
+                    // TODO something more than just logging
+                }
+            }));
+    }
+
     private class InternalFibListener implements FibListener {
 
         @Override
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 6a0d3e1..54ee2a6 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
@@ -387,11 +387,12 @@
                 Set<PendingNext> pending = pendingForwards.remove(event.subject());
 
                 if (pending == null) {
-                    log.debug("Nothing pending for this obj event");
+                    log.warn("Nothing pending for this obj event {}", event);
                     return;
                 }
 
-                log.debug("Processing pending forwarding objectives {}", pending.size());
+                log.debug("Processing {} pending forwarding objectives for nextId {}",
+                         pending.size(), event.subject());
                 pending.forEach(p -> getDevicePipeliner(p.deviceId())
                                 .forward(p.forwardingObjective()));
             }
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/OFDPA2Pipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/OFDPA2Pipeline.java
index 1cfb937..ba54fac 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/OFDPA2Pipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/OFDPA2Pipeline.java
@@ -584,34 +584,59 @@
                     + "nextId or Treatment", fwd.selector(), fwd.appId());
             return Collections.emptySet();
         }
+
         // XXX driver does not currently do type checking as per Tables 65-67 in
         // OFDPA 2.0 spec. The only allowed treatment is a punt to the controller.
-        if (fwd.treatment() != null &&
-                fwd.treatment().allInstructions().size() == 1 &&
-                fwd.treatment().allInstructions().get(0).type() == Instruction.Type.OUTPUT) {
-            OutputInstruction o = (OutputInstruction) fwd.treatment().allInstructions().get(0);
-            if (o.port() == PortNumber.CONTROLLER) {
-                FlowRule.Builder ruleBuilder = DefaultFlowRule.builder()
-                        .fromApp(fwd.appId())
-                        .withPriority(fwd.priority())
-                        .forDevice(deviceId)
-                        .withSelector(fwd.selector())
-                        .withTreatment(fwd.treatment())
-                        .makePermanent()
-                        .forTable(ACL_TABLE);
-                return Collections.singletonList(ruleBuilder.build());
-            } else {
-                log.warn("Only allowed treatments in versatile forwarding "
-                        + "objectives are punts to the controller");
-                return Collections.emptySet();
+        TrafficTreatment.Builder ttBuilder = DefaultTrafficTreatment.builder();
+        if (fwd.treatment() != null) {
+            for (Instruction ins : fwd.treatment().allInstructions()) {
+                if (ins instanceof OutputInstruction) {
+                    OutputInstruction o = (OutputInstruction) ins;
+                    if (o.port() == PortNumber.CONTROLLER) {
+                        ttBuilder.add(o);
+                    } else {
+                        log.warn("Only allowed treatments in versatile forwarding "
+                                + "objectives are punts to the controller");
+                    }
+                } else {
+                    log.warn("Cannot process instruction in versatile fwd {}", ins);
+                }
             }
         }
-
         if (fwd.nextId() != null) {
-            // XXX overide case
-            log.warn("versatile objective --> next Id not yet implemeted");
+            // overide case
+            NextGroup next = getGroupForNextObjective(fwd.nextId());
+            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) {
+                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();
+            }
+            ttBuilder.deferred().group(group.id());
         }
-        return Collections.emptySet();
+       // ensure that match does not include vlan = NONE as OF-DPA does not
+       // match untagged packets this way in the ACL table.
+       TrafficSelector.Builder selBuilder = DefaultTrafficSelector.builder();
+       for (Criterion criterion : fwd.selector().criteria()) {
+           if (criterion instanceof VlanIdCriterion &&
+               ((VlanIdCriterion) criterion).vlanId() == VlanId.NONE) {
+               continue;
+           }
+           selBuilder.add(criterion);
+       }
+
+        FlowRule.Builder ruleBuilder = DefaultFlowRule.builder()
+                .fromApp(fwd.appId())
+                .withPriority(fwd.priority())
+                .forDevice(deviceId)
+                .withSelector(selBuilder.build())
+                .withTreatment(ttBuilder.build())
+                .makePermanent()
+                .forTable(ACL_TABLE);
+        return Collections.singletonList(ruleBuilder.build());
     }
 
     /**
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/SoftRouterPipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/SoftRouterPipeline.java
index 8e5ce91..6aa7f91 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/SoftRouterPipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/SoftRouterPipeline.java
@@ -42,6 +42,8 @@
 import org.onosproject.net.flow.criteria.IPCriterion;
 import org.onosproject.net.flow.criteria.PortCriterion;
 import org.onosproject.net.flow.criteria.VlanIdCriterion;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
 import org.onosproject.net.flowobjective.FilteringObjective;
 import org.onosproject.net.flowobjective.FlowObjectiveStore;
 import org.onosproject.net.flowobjective.ForwardingObjective;
@@ -50,6 +52,7 @@
 import org.onosproject.net.flowobjective.ObjectiveError;
 import org.onosproject.store.serializers.KryoNamespaces;
 import org.slf4j.Logger;
+import static org.onlab.util.Tools.delay;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -314,19 +317,50 @@
     }
 
     /**
-     * SoftRouter has a single versatile table - the filter table. All versatile
-     * flow rules must include the filtering rules.
+     * SoftRouter has a single versatile table - the filter table.
+     * This table can be used to filter entries that reach the next table (FIB table).
+     * It can also be used to punt packets to the controller and/or bypass
+     * the FIB table to forward out of a port.
      *
      * @param fwd The forwarding objective of type versatile
      * @return  A collection of flow rules meant to be delivered to the flowrule
      *          subsystem. May return empty collection in case of failures.
      */
     private Collection<FlowRule> processVersatile(ForwardingObjective fwd) {
+        log.debug("Received versatile fwd: to next:{}", fwd.nextId());
         Collection<FlowRule> flowrules = new ArrayList<>();
+        if (fwd.nextId() == null && fwd.treatment() == null) {
+            log.error("Forwarding objective {} from {} must contain "
+                    + "nextId or Treatment", fwd.selector(), fwd.appId());
+            return Collections.emptySet();
+        }
+        TrafficTreatment.Builder ttBuilder = DefaultTrafficTreatment.builder();
+        if (fwd.treatment() != null) {
+            fwd.treatment().immediate().forEach(ins -> ttBuilder.add(ins));
+        }
+        //convert nextId to flow actions
+        if (fwd.nextId() != null) {
+            // only acceptable value is output to port
+            NextGroup next = flowObjectiveStore.getNextGroup(fwd.nextId());
+            if (next == null) {
+                log.error("next-id {} does not exist in store", fwd.nextId());
+                return Collections.emptySet();
+            }
+            TrafficTreatment nt = appKryo.deserialize(next.data());
+            if (nt == null)  {
+                log.error("Error in deserializing next-id {}", fwd.nextId());
+                return Collections.emptySet();
+            }
+            for (Instruction ins : nt.allInstructions()) {
+                if (ins instanceof OutputInstruction) {
+                    ttBuilder.add(ins);
+                }
+            }
+        }
 
         FlowRule rule = DefaultFlowRule.builder()
                 .withSelector(fwd.selector())
-                .withTreatment(fwd.treatment())
+                .withTreatment(ttBuilder.build())
                 .makePermanent()
                 .forDevice(deviceId)
                 .fromApp(fwd.appId())
@@ -350,7 +384,7 @@
      *
      */
     private Collection<FlowRule> processSpecific(ForwardingObjective fwd) {
-        log.debug("Processing specific forwarding objective");
+        log.debug("Processing specific forwarding objective to next:{}", fwd.nextId());
         TrafficSelector selector = fwd.selector();
         EthTypeCriterion ethType =
                 (EthTypeCriterion) selector.getCriterion(Criterion.Type.ETH_TYPE);
@@ -411,6 +445,9 @@
      */
     private void processSimpleNextObjective(NextObjective nextObj) {
         // Simple next objective has a single treatment (not a collection)
+        log.debug("Received nextObj {}", nextObj.id());
+        // delay processing to emulate group creation
+        delay(50);
         TrafficTreatment treatment = nextObj.next().iterator().next();
         flowObjectiveStore.putNextGroup(nextObj.id(),
                                         new DummyGroup(treatment));