Route reprogamming using group substitution during next hop movement

Change-Id: Idf8362dac522722ca67747e245bfd836e6ee6292
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
index 2d494c3..95350d9 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
@@ -1116,11 +1116,12 @@
      * @param hostMac MAC address of the next hop
      * @param hostVlanId Vlan ID of the nexthop
      * @param outPort port where the next hop attaches to
+     * @param directHost host is of type direct or indirect
      */
     void populateRoute(DeviceId deviceId, IpPrefix prefix,
-                       MacAddress hostMac, VlanId hostVlanId, PortNumber outPort) {
+                       MacAddress hostMac, VlanId hostVlanId, PortNumber outPort, boolean directHost) {
         if (shouldProgram(deviceId)) {
-            srManager.routingRulePopulator.populateRoute(deviceId, prefix, hostMac, hostVlanId, outPort);
+            srManager.routingRulePopulator.populateRoute(deviceId, prefix, hostMac, hostVlanId, outPort, directHost);
         }
     }
 
@@ -1133,11 +1134,12 @@
      * @param hostMac MAC address of the next hop
      * @param hostVlanId Vlan ID of the nexthop
      * @param outPort port that next hop attaches to
+     * @param directHost host is of type direct or indirect
      */
     void revokeRoute(DeviceId deviceId, IpPrefix prefix,
-                     MacAddress hostMac, VlanId hostVlanId, PortNumber outPort) {
+                     MacAddress hostMac, VlanId hostVlanId, PortNumber outPort, boolean directHost) {
         if (shouldProgram(deviceId)) {
-            srManager.routingRulePopulator.revokeRoute(deviceId, prefix, hostMac, hostVlanId, outPort);
+            srManager.routingRulePopulator.revokeRoute(deviceId, prefix, hostMac, hostVlanId, outPort, directHost);
         }
     }
 
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/HostHandler.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/HostHandler.java
index 9286ef0..4b4ac5c 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/HostHandler.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/HostHandler.java
@@ -463,9 +463,9 @@
 
         log.info("{} routing rule for {} at {}", revoke ? "Revoking" : "Populating", ip, location);
         if (revoke) {
-            srManager.defaultRoutingHandler.revokeRoute(deviceId, ip.toIpPrefix(), mac, vlanId, port);
+            srManager.defaultRoutingHandler.revokeRoute(deviceId, ip.toIpPrefix(), mac, vlanId, port, true);
         } else {
-            srManager.defaultRoutingHandler.populateRoute(deviceId, ip.toIpPrefix(), mac, vlanId, port);
+            srManager.defaultRoutingHandler.populateRoute(deviceId, ip.toIpPrefix(), mac, vlanId, port, true);
         }
     }
 
@@ -550,10 +550,10 @@
                 ipPrefixSet.forEach(ipPrefix -> {
                     if (install && ipPrefix.contains(hostIpAddress)) {
                             srManager.defaultRoutingHandler.populateRoute(cp.deviceId(), hostIpAddress.toIpPrefix(),
-                                                                         host.mac(), host.vlan(), cp.port());
+                                                                         host.mac(), host.vlan(), cp.port(), true);
                     } else if (!install && ipPrefix.contains(hostIpAddress)) {
                             srManager.defaultRoutingHandler.revokeRoute(cp.deviceId(), hostIpAddress.toIpPrefix(),
-                                                                       host.mac(), host.vlan(), cp.port());
+                                                                       host.mac(), host.vlan(), cp.port(), true);
                     }
                 });
             }));
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/RouteHandler.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/RouteHandler.java
index 40cdf53..7dbbbeb 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/RouteHandler.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/RouteHandler.java
@@ -32,6 +32,8 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Collection;
 import java.util.Objects;
 import java.util.Optional;
@@ -101,7 +103,7 @@
                 srManager.deviceConfiguration.addSubnet(location, prefix);
                 log.debug("RouteAdded populateRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
                 srManager.defaultRoutingHandler.populateRoute(location.deviceId(), prefix,
-                        nextHopMac, nextHopVlan, location.port());
+                        nextHopMac, nextHopVlan, location.port(), false);
             });
         });
     }
@@ -175,7 +177,7 @@
                 srManager.deviceConfiguration.addSubnet(location, prefix);
                 log.debug("RouteUpdated. populateRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
                 srManager.defaultRoutingHandler.populateRoute(location.deviceId(), prefix,
-                        nextHopMac, nextHopVlan, location.port());
+                        nextHopMac, nextHopVlan, location.port(), false);
             });
         });
     }
@@ -221,7 +223,7 @@
 
                     log.debug("RouteRemoved. revokeRoute {}, {}, {}, {}", location, prefix, nextHopMac, nextHopVlan);
                     srManager.defaultRoutingHandler.revokeRoute(pairDeviceId.get(), prefix,
-                            nextHopMac, vlanId, pairLocalPort.get());
+                            nextHopMac, vlanId, pairLocalPort.get(), false);
                 }
             });
         });
@@ -231,6 +233,8 @@
         log.info("processHostMovedEvent {}", event);
         MacAddress hostMac = event.subject().mac();
         VlanId hostVlanId = event.subject().vlan();
+        // map of nextId for prev port in each device
+        Map<DeviceId, Integer> nextIdMap = new HashMap<>();
 
         affectedRoutes(hostMac, hostVlanId).forEach(affectedRoute -> {
             IpPrefix prefix = affectedRoute.prefix();
@@ -245,8 +249,22 @@
             Set<DeviceId> newDeviceIds = newLocations.stream().map(HostLocation::deviceId)
                     .collect(Collectors.toSet());
 
+            // Set of deviceIDs of the previous locations where the host was connected
+            // Used to determine if host moved to different connect points
+            // on same device or moved to a different device altogether
+            Set<DeviceId> oldDeviceIds = prevLocations.stream().map(HostLocation::deviceId)
+                    .collect(Collectors.toSet());
+
             // For each old location
             Sets.difference(prevLocations, newLocations).forEach(prevLocation -> {
+                //find next Id for each old port, add to map
+                int nextId = srManager.getNextIdForHostPort(prevLocation.deviceId(), hostMac,
+                                                             hostVlanId, prevLocation.port());
+                log.debug("HostMoved. NextId For Host {}, {}, {}, {} {}", prevLocation, prefix,
+                                                             hostMac, hostVlanId, nextId);
+
+                nextIdMap.put(prevLocation.deviceId(), nextId);
+
                 // Remove flows for unchanged IPs only when the host moves from a switch to another.
                 // Otherwise, do not remove and let the adding part update the old flow
                 if (newDeviceIds.contains(prevLocation.deviceId())) {
@@ -265,7 +283,7 @@
 
                 log.debug("HostMoved. revokeRoute {}, {}, {}, {}", prevLocation, prefix, hostMac, hostVlanId);
                 srManager.defaultRoutingHandler.revokeRoute(prevLocation.deviceId(), prefix,
-                        hostMac, hostVlanId, prevLocation.port());
+                        hostMac, hostVlanId, prevLocation.port(), false);
             });
 
             // For each new location, add all new IPs.
@@ -273,9 +291,20 @@
                 log.debug("HostMoved. addSubnet {}, {}", newLocation, prefix);
                 srManager.deviceConfiguration.addSubnet(newLocation, prefix);
 
-                log.debug("HostMoved. populateRoute {}, {}, {}, {}", newLocation, prefix, hostMac, hostVlanId);
-                srManager.defaultRoutingHandler.populateRoute(newLocation.deviceId(), prefix,
-                        hostMac, hostVlanId, newLocation.port());
+                //its a new connect point, not a move from an existing device, populateRoute
+                if (!oldDeviceIds.contains(newLocation.deviceId())) {
+                   log.debug("HostMoved. populateRoute {}, {}, {}, {}", newLocation, prefix, hostMac, hostVlanId);
+                   srManager.defaultRoutingHandler.populateRoute(newLocation.deviceId(), prefix,
+                          hostMac, hostVlanId, newLocation.port(), false);
+                } else {
+                  // update same flow to point to new nextObj
+                  VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(newLocation)).orElse(hostVlanId);
+                  //use nextIdMap to send new port details to update the nextId group bucket
+                  log.debug("HostMoved. update L3 Ucast Group Bucket {}, {}, {} --> {}",
+                                         newLocation, hostMac, vlanId, nextIdMap.get(newLocation.deviceId()));
+                  srManager.updateMacVlanTreatment(newLocation.deviceId(), hostMac, vlanId,
+                          newLocation.port(), nextIdMap.get(newLocation.deviceId()));
+                }
             });
 
         });
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
index 3b9b30c..a0ff4e1 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
@@ -303,15 +303,16 @@
      * @param hostMac MAC address of the next hop
      * @param hostVlanId Vlan ID of the nexthop
      * @param outPort port where the next hop attaches to
+     * @param directHost host is of type direct or indirect
      */
     void populateRoute(DeviceId deviceId, IpPrefix prefix,
-                              MacAddress hostMac, VlanId hostVlanId, PortNumber outPort) {
+                              MacAddress hostMac, VlanId hostVlanId, PortNumber outPort, boolean directHost) {
         log.debug("Populate direct routing entry for route {} at {}:{}",
                 prefix, deviceId, outPort);
         ForwardingObjective.Builder fwdBuilder;
         try {
             fwdBuilder = routingFwdObjBuilder(deviceId, prefix, hostMac,
-                                              hostVlanId, outPort, false);
+                                              hostVlanId, outPort, directHost, false);
         } catch (DeviceConfigNotFoundException e) {
             log.warn(e.getMessage() + " Aborting direct populateRoute");
             return;
@@ -342,15 +343,16 @@
      * @param hostMac MAC address of the next hop
      * @param hostVlanId Vlan ID of the nexthop
      * @param outPort port that next hop attaches to
+     * @param directHost host is of type direct or indirect
      */
     void revokeRoute(DeviceId deviceId, IpPrefix prefix,
-            MacAddress hostMac, VlanId hostVlanId, PortNumber outPort) {
+            MacAddress hostMac, VlanId hostVlanId, PortNumber outPort, boolean directHost) {
         log.debug("Revoke IP table entry for route {} at {}:{}",
                 prefix, deviceId, outPort);
         ForwardingObjective.Builder fwdBuilder;
         try {
             fwdBuilder = routingFwdObjBuilder(deviceId, prefix, hostMac,
-                                              hostVlanId, outPort, true);
+                                              hostVlanId, outPort, directHost, true);
         } catch (DeviceConfigNotFoundException e) {
             log.warn(e.getMessage() + " Aborting revokeIpRuleForHost.");
             return;
@@ -379,16 +381,19 @@
      * @param hostVlanId Vlan ID of the nexthop
      * @param outPort port where the nexthop attaches to
      * @param revoke true if forwarding objective is meant to revoke forwarding rule
+     * @param directHost host is direct or indirect
      * @return forwarding objective builder
      * @throws DeviceConfigNotFoundException if given device is not configured
      */
+
     private ForwardingObjective.Builder routingFwdObjBuilder(
             DeviceId deviceId, IpPrefix prefix,
             MacAddress hostMac, VlanId hostVlanId, PortNumber outPort,
-            boolean revoke)
+            boolean directHost, boolean revoke)
             throws DeviceConfigNotFoundException {
         MacAddress deviceMac;
         deviceMac = config.getDeviceMac(deviceId);
+        int nextObjId = -1;
 
         ConnectPoint connectPoint = new ConnectPoint(deviceId, outPort);
         VlanId untaggedVlan = srManager.getUntaggedVlanId(connectPoint);
@@ -426,18 +431,26 @@
                     + " in tagged vlan", hostMac, hostVlanId, connectPoint);
             return null;
         }
-        // if the objective is to revoke an existing rule, and for some reason
-        // the next-objective does not exist, then a new one should not be created
-        int portNextObjId = srManager.getPortNextObjectiveId(deviceId, outPort,
+
+        if (directHost) {
+          // if the objective is to revoke an existing rule, and for some reason
+          // the next-objective does not exist, then a new one should not be created
+          nextObjId = srManager.getPortNextObjectiveId(deviceId, outPort,
                                           tbuilder.build(), mbuilder.build(), !revoke);
-        if (portNextObjId == -1) {
-            // Warning log will come from getPortNextObjective method
+        } else {
+          // if the objective is to revoke an existing rule, and for some reason
+          // the next-objective does not exist, then a new one should not be created
+          nextObjId = srManager.getMacVlanNextObjectiveId(deviceId, hostMac, hostVlanId,
+                                          tbuilder.build(), mbuilder.build(), !revoke);
+        }
+        if (nextObjId == -1) {
+            // Warning log will come from getMacVlanNextObjective method
             return null;
         }
 
         return DefaultForwardingObjective.builder()
                 .withSelector(sbuilder.build())
-                .nextStep(portNextObjId)
+                .nextStep(nextObjId)
                 .fromApp(srManager.appId).makePermanent()
                 .withPriority(getPriorityFromPrefix(prefix))
                 .withFlag(ForwardingObjective.Flag.SPECIFIC);
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
index bc29531..0125439 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
@@ -36,6 +36,7 @@
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
 import org.onlab.packet.VlanId;
+import org.onlab.packet.MacAddress;
 import org.onlab.util.KryoNamespace;
 import org.onlab.util.Tools;
 import org.onosproject.cfg.ComponentConfigService;
@@ -124,6 +125,7 @@
 import org.onosproject.segmentrouting.mcast.McastStoreKey;
 import org.onosproject.segmentrouting.storekey.PortNextObjectiveStoreKey;
 import org.onosproject.segmentrouting.storekey.VlanNextObjectiveStoreKey;
+import org.onosproject.segmentrouting.storekey.MacVlanNextObjectiveStoreKey;
 import org.onosproject.segmentrouting.storekey.XConnectStoreKey;
 import org.onosproject.segmentrouting.xconnect.api.XconnectService;
 import org.onosproject.store.serializers.KryoNamespaces;
@@ -309,11 +311,18 @@
             vlanNextObjStore = null;
     /**
      * Per device next objective ID store with (device id + port + treatment + meta) as key.
-     * Used to keep track on L2 interface group and L3 unicast group information.
+     * Used to keep track on L2 interface group and L3 unicast group information for direct hosts.
      */
     private EventuallyConsistentMap<PortNextObjectiveStoreKey, Integer>
             portNextObjStore = null;
 
+    /**
+     * Per device next objective ID store with (device id + MAC address + vlan) as key.
+     * Used to keep track of L3 unicast group for indirect hosts.
+     */
+    private EventuallyConsistentMap<MacVlanNextObjectiveStoreKey, Integer>
+            macVlanNextObjStore = null;
+
     private EventuallyConsistentMap<String, Tunnel> tunnelStore = null;
     private EventuallyConsistentMap<String, Policy> policyStore = null;
 
@@ -429,6 +438,15 @@
                 .withTimestampProvider((k, v) -> new WallClockTimestamp())
                 .build();
 
+        log.debug("Creating EC map macvlannextobjectivestore");
+        EventuallyConsistentMapBuilder<MacVlanNextObjectiveStoreKey, Integer>
+                macVlanNextObjMapBuilder = storageService.eventuallyConsistentMapBuilder();
+        macVlanNextObjStore = macVlanNextObjMapBuilder
+                .withName("macvlannextobjectivestore")
+                .withSerializer(createSerializer())
+                .withTimestampProvider((k, v) -> new WallClockTimestamp())
+                .build();
+
         log.debug("Creating EC map subnetnextobjectivestore");
         EventuallyConsistentMapBuilder<PortNextObjectiveStoreKey, Integer>
                 portNextObjMapBuilder = storageService.eventuallyConsistentMapBuilder();
@@ -546,7 +564,8 @@
                           L2Tunnel.class,
                           L2TunnelPolicy.class,
                           DefaultL2Tunnel.class,
-                          DefaultL2TunnelPolicy.class
+                          DefaultL2TunnelPolicy.class,
+                          MacVlanNextObjectiveStoreKey.class
                 );
     }
 
@@ -594,6 +613,7 @@
 
         dsNextObjStore.destroy();
         vlanNextObjStore.destroy();
+        macVlanNextObjStore.destroy();
         portNextObjStore.destroy();
         tunnelStore.destroy();
         policyStore.destroy();
@@ -781,6 +801,15 @@
     }
 
     @Override
+    public ImmutableMap<MacVlanNextObjectiveStoreKey, Integer> getMacVlanNextObjStore() {
+        if (macVlanNextObjStore != null) {
+            return ImmutableMap.copyOf(macVlanNextObjStore.entrySet());
+        } else {
+            return ImmutableMap.of();
+        }
+    }
+
+    @Override
     public ImmutableMap<PortNextObjectiveStoreKey, Integer> getPortNextObjStore() {
         if (portNextObjStore != null) {
             return ImmutableMap.copyOf(portNextObjStore.entrySet());
@@ -823,6 +852,13 @@
                 }
             });
         }
+        if (macVlanNextObjStore != null) {
+            macVlanNextObjStore.entrySet().forEach(e -> {
+                if (e.getValue() == nextId) {
+                    macVlanNextObjStore.remove(e.getKey());
+                }
+            });
+        }
         if (portNextObjStore != null) {
             portNextObjStore.entrySet().forEach(e -> {
                 if (e.getValue() == nextId) {
@@ -943,8 +979,18 @@
     }
 
     /**
+     * Per device next objective ID store with (device id + MAC address + vlan) as key.
+     * Used to keep track on L3 Unicast group information for indirect hosts.
+     *
+     * @return mac vlan next object store
+     */
+    public EventuallyConsistentMap<MacVlanNextObjectiveStoreKey, Integer> macVlanNextObjStore() {
+        return macVlanNextObjStore;
+    }
+
+    /**
      * Per device next objective ID store with (device id + port + treatment + meta) as key.
-     * Used to keep track on L2 interface group and L3 unicast group information.
+     * Used to keep track on L2 interface group and L3 unicast group information for direct hosts.
      *
      * @return port next object store.
      */
@@ -1105,6 +1151,76 @@
     }
 
     /**
+     * Returns the next Objective ID for the given mac and vlan, 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,
+     * and should be created, a new one is created and its id is returned.
+     *
+     * @param deviceId Device ID
+     * @param macAddr mac of host for which Next ID is required.
+     * @param vlanId vlan of host for which Next ID is required.
+     * @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
+     * @param createIfMissing true if a next object should be created if not found
+     * @return next objective ID or -1 if an error occurred during retrieval or creation
+     */
+    public int getMacVlanNextObjectiveId(DeviceId deviceId, MacAddress macAddr, VlanId vlanId,
+                                      TrafficTreatment treatment,
+                                      TrafficSelector meta,
+                                      boolean createIfMissing) {
+        DefaultGroupHandler ghdlr = groupHandlerMap.get(deviceId);
+        if (ghdlr != null) {
+            return ghdlr.getMacVlanNextObjectiveId(macAddr, vlanId, treatment, meta, createIfMissing);
+        } else {
+            log.warn("getMacVlanNextObjectiveId query - groupHandler for device {}"
+                    + " not found", deviceId);
+            return -1;
+        }
+    }
+
+    /**
+     * Returns the next ID for the given host port from the store.
+     *
+     * @param deviceId Device ID
+     * @param hostMac mac of host for which Next ID is required.
+     * @param hostVlanId vlan of host for which Next ID is required.
+     * @param port port of device for which next ID is required.
+     * @return next objective ID or -1 if an error occurred during retrieval
+     */
+    public int getNextIdForHostPort(DeviceId deviceId, MacAddress hostMac,
+                                     VlanId hostVlanId, PortNumber port) {
+        DefaultGroupHandler ghdlr = groupHandlerMap.get(deviceId);
+        if (ghdlr != null) {
+            return ghdlr.getNextIdForHostPort(hostMac, hostVlanId, port);
+        } else {
+            log.warn("getNextIdForHostPort query - groupHandler for device {}"
+                    + " not found", deviceId);
+            return -1;
+        }
+    }
+
+    /**
+     * Updates the next objective for the given nextId .
+     *
+     * @param deviceId Device ID
+     * @param hostMac mac of host for which Next obj is to be updated.
+     * @param hostVlanId vlan of host for which Next obj is to be updated.
+     * @param port port with which to update the Next Obj.
+     * @param nextId of Next Obj which needs to be updated.
+     */
+    public void updateMacVlanTreatment(DeviceId deviceId, MacAddress hostMac,
+                             VlanId hostVlanId, PortNumber port, int nextId) {
+        DefaultGroupHandler ghdlr = groupHandlerMap.get(deviceId);
+        if (ghdlr != null) {
+            ghdlr.updateL3UcastGroupBucket(hostMac, hostVlanId, port, nextId);
+        } else {
+            log.warn("updateL3UcastGroupBucket query - groupHandler for device {}"
+                    + " not found", deviceId);
+        }
+    }
+
+
+    /**
      * Returns the group handler object for the specified device id.
      *
      * @param devId the device identifier
@@ -1399,6 +1515,9 @@
         vlanNextObjStore.entrySet().stream()
                 .filter(entry -> entry.getKey().deviceId().equals(device.id()))
                 .forEach(entry -> vlanNextObjStore.remove(entry.getKey()));
+        macVlanNextObjStore.entrySet().stream()
+                .filter(entry -> entry.getKey().deviceId().equals(device.id()))
+                .forEach(entry -> macVlanNextObjStore.remove(entry.getKey()));
         portNextObjStore.entrySet().stream()
                 .filter(entry -> entry.getKey().deviceId().equals(device.id()))
                 .forEach(entry -> portNextObjStore.remove(entry.getKey()));
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java
index 0cd1884..2f2f420 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java
@@ -42,6 +42,7 @@
 import org.onosproject.segmentrouting.mcast.McastStoreKey;
 import org.onosproject.segmentrouting.storekey.PortNextObjectiveStoreKey;
 import org.onosproject.segmentrouting.storekey.VlanNextObjectiveStoreKey;
+import org.onosproject.segmentrouting.storekey.MacVlanNextObjectiveStoreKey;
 
 import java.util.List;
 import java.util.Map;
@@ -226,6 +227,13 @@
     ImmutableMap<VlanNextObjectiveStoreKey, Integer> getVlanNextObjStore();
 
     /**
+     * Returns the Mac Vlan next objective store.
+     *
+     * @return current contents of the macVlanNextObjStore
+     */
+    ImmutableMap<MacVlanNextObjectiveStoreKey, Integer> getMacVlanNextObjStore();
+
+    /**
      * Returns the port next objective store.
      *
      * @return current contents of the portNextObjStore
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/NextMacVlanCommand.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/NextMacVlanCommand.java
new file mode 100644
index 0000000..e952915
--- /dev/null
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/cli/NextMacVlanCommand.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.segmentrouting.cli;
+
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.segmentrouting.SegmentRoutingService;
+import org.onosproject.segmentrouting.storekey.MacVlanNextObjectiveStoreKey;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Map;
+
+/**
+ * Command to read the current state of the macVlanNextObjStore.
+ */
+@Command(scope = "onos", name = "sr-next-mac-vlan",
+        description = "Displays the current next-hop / next-id it mapping")
+public class NextMacVlanCommand extends AbstractShellCommand {
+    @Override
+    protected void execute() {
+        SegmentRoutingService srService =
+                AbstractShellCommand.get(SegmentRoutingService.class);
+        print(srService.getMacVlanNextObjStore());
+    }
+
+    private void print(Map<MacVlanNextObjectiveStoreKey, Integer> macVlanNextObjStore) {
+        ArrayList<MacVlanNextObjectiveStoreKey> a = new ArrayList<>(macVlanNextObjStore.keySet());
+        a.sort(Comparator
+                .comparing((MacVlanNextObjectiveStoreKey o) -> o.deviceId().toString())
+                .thenComparing((MacVlanNextObjectiveStoreKey o) -> o.vlanId().toString())
+                .thenComparing((MacVlanNextObjectiveStoreKey o) -> o.macAddr().toString()));
+
+        StringBuilder builder = new StringBuilder();
+        a.forEach(k ->
+            builder.append("\n")
+                    .append(k)
+                    .append(" --> ")
+                    .append(macVlanNextObjStore.get(k))
+        );
+        print(builder.toString());
+    }
+}
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java
index 967f83c..27ec08e 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java
@@ -44,9 +44,11 @@
 import org.onosproject.segmentrouting.SegmentRoutingManager;
 import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
 import org.onosproject.segmentrouting.config.DeviceProperties;
+import org.onosproject.segmentrouting.config.DeviceConfiguration;
 import org.onosproject.segmentrouting.storekey.DestinationSetNextObjectiveStoreKey;
 import org.onosproject.segmentrouting.storekey.PortNextObjectiveStoreKey;
 import org.onosproject.segmentrouting.storekey.VlanNextObjectiveStoreKey;
+import org.onosproject.segmentrouting.storekey.MacVlanNextObjectiveStoreKey;
 import org.onosproject.store.service.EventuallyConsistentMap;
 import org.slf4j.Logger;
 
@@ -89,6 +91,8 @@
     protected MacAddress nodeMacAddr = null;
     protected LinkService linkService;
     protected FlowObjectiveService flowObjectiveService;
+    private DeviceConfiguration config;
+
     /**
      * local store for neighbor-device-ids and the set of ports on this device
      * that connect to the same neighbor.
@@ -107,6 +111,9 @@
     // distributed store for (device+subnet-ip-prefix) mapped to next-id
     protected EventuallyConsistentMap<VlanNextObjectiveStoreKey, Integer>
             vlanNextObjStore = null;
+    // distributed store for (device+mac+vlan+treatment) mapped to next-id
+    protected EventuallyConsistentMap<MacVlanNextObjectiveStoreKey, Integer>
+            macVlanNextObjStore = null;
     // distributed store for (device+port+treatment) mapped to next-id
     protected EventuallyConsistentMap<PortNextObjectiveStoreKey, Integer>
             portNextObjStore = null;
@@ -146,6 +153,7 @@
         this.dsNextObjStore = srManager.dsNextObjStore();
         this.vlanNextObjStore = srManager.vlanNextObjStore();
         this.portNextObjStore = srManager.portNextObjStore();
+        this.macVlanNextObjStore = srManager.macVlanNextObjStore();
         this.srManager = srManager;
         executorService.scheduleWithFixedDelay(new BucketCorrector(), 10,
                                                VERIFY_INTERVAL,
@@ -805,6 +813,52 @@
     }
 
     /**
+     * Returns the next objective of type simple associated with the mac/vlan on the
+     * device, given the treatment. Different treatments to the same mac/vlan result
+     * in different next objectives. If no such objective exists, this method
+     * creates one (if requested) and returns the id. Optionally metadata can be passed in for
+     * the creation of the objective. Typically this is used for L2 and L3 forwarding
+     * to compute nodes and containers/VMs on the compute nodes directly attached
+     * to the switch.
+     *
+     * @param macAddr the mac addr for the simple next objective
+     * @param vlanId the vlan 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
+     * @param createIfMissing true if a next object should be created if not found
+     * @return int if found or created, -1 if there are errors during the
+     *          creation of the next objective.
+     */
+    public int getMacVlanNextObjectiveId(MacAddress macAddr, VlanId vlanId, TrafficTreatment treatment,
+                                      TrafficSelector meta, boolean createIfMissing) {
+
+        Integer nextId = macVlanNextObjStore
+                .get(new MacVlanNextObjectiveStoreKey(deviceId, macAddr, vlanId));
+
+        if (nextId != null) {
+            return nextId;
+        }
+
+        log.debug("getMacVlanNextObjectiveId in device {}: Next objective id "
+                + "not found for host : {}/{} .. {}", deviceId, macAddr, vlanId,
+                (createIfMissing) ? "creating" : "aborting");
+
+        if (!createIfMissing) {
+            return -1;
+        }
+
+        /* create missing next objective */
+        nextId = createGroupFromMacVlan(macAddr, vlanId, treatment, meta);
+        if (nextId == null) {
+            log.warn("getMacVlanNextObjectiveId: unable to create next obj"
+                    + "for dev:{} host:{}/{}", deviceId, macAddr, vlanId);
+            return -1;
+        }
+        return nextId;
+    }
+
+
+    /**
      * 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
@@ -1162,6 +1216,45 @@
     }
 
     /**
+     * Create simple next objective for an indirect host mac/vlan. The treatments can include
+     * all outgoing actions that need to happen on the packet.
+     *
+     * @param macAddr the mac address of the host
+     * @param vlanId the vlan of the host
+     * @param treatment the actions to apply on the packets (should include outport)
+     * @param meta optional data to pass to the driver
+     * @return next objective ID
+     */
+    public int createGroupFromMacVlan(MacAddress macAddr, VlanId vlanId, TrafficTreatment treatment,
+                                    TrafficSelector meta) {
+        int nextId = flowObjectiveService.allocateNextId();
+        MacVlanNextObjectiveStoreKey key = new MacVlanNextObjectiveStoreKey(deviceId, macAddr, vlanId);
+
+        NextObjective.Builder nextObjBuilder = DefaultNextObjective
+                .builder().withId(nextId)
+                .withType(NextObjective.Type.SIMPLE)
+                .addTreatment(treatment)
+                .fromApp(appId)
+                .withMeta(meta);
+
+        ObjectiveContext context = new DefaultObjectiveContext(
+            (objective) ->
+                log.debug("createGroupFromMacVlan installed "
+                        + "NextObj {} on {}", nextId, deviceId),
+            (objective, error) -> {
+                log.warn("createGroupFromMacVlan failed to install NextObj {} on {}: {}", nextId, deviceId, error);
+                srManager.invalidateNextObj(objective.id());
+            });
+        NextObjective nextObj = nextObjBuilder.add(context);
+        flowObjectiveService.next(deviceId, nextObj);
+        log.debug("createGroupFromMacVlan: Submited next objective {} in device {} "
+                + "for host {}/{}", nextId, deviceId, macAddr, vlanId);
+
+        macVlanNextObjStore.put(key, nextId);
+        return nextId;
+    }
+
+    /**
      * Create simple next objective for a single port. The treatments can include
      * all outgoing actions that need to happen on the packet.
      *
@@ -1242,6 +1335,7 @@
             portNextObjStore.remove(key);
         }
     }
+
     /**
      * Removes groups for the next objective ID given.
      *
@@ -1387,6 +1481,94 @@
     }
 
     /**
+     * Returns the next ID for the given host port from the store.
+     *
+     * @param hostMac mac of host for which Next ID is required.
+     * @param hostVlanId vlan of host for which Next ID is required.
+     * @param port port of device for which next ID is required.
+     * @return next objective ID or -1 if an error occurred during retrieval
+     */
+    public int getNextIdForHostPort(MacAddress hostMac, VlanId hostVlanId, PortNumber port) {
+
+       MacAddress deviceMac;
+        try {
+            deviceMac = deviceConfig.getDeviceMac(deviceId);
+        } catch (DeviceConfigNotFoundException e) {
+            log.warn(e.getMessage() + " in getNextIdForHostPort");
+            return -1;
+        }
+
+        TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
+        tbuilder.deferred()
+                .setEthDst(hostMac)
+                .setEthSrc(deviceMac)
+                .setVlanId(hostVlanId)
+                .setOutput(port);
+
+        TrafficSelector metadata =
+                 DefaultTrafficSelector.builder().matchVlanId(hostVlanId).build();
+
+        int nextId = getMacVlanNextObjectiveId(hostMac, hostVlanId,
+                tbuilder.build(), metadata, true);
+
+        log.debug("getNextId : hostMac {}, deviceMac {}, port {}, hostVlanId {} Treatment {} Meta {} nextId {} ",
+                                          hostMac, deviceMac, port, hostVlanId, tbuilder.build(), metadata, nextId);
+
+        return nextId;
+    }
+
+    /**
+     * Updates the next objective for the given nextId .
+     *
+     * @param hostMac mac of host for which Next obj is to be updated.
+     * @param hostVlanId vlan of host for which Next obj is to be updated.
+     * @param port port with which to update the Next Obj.
+     * @param nextId of Next Obj which needs to be updated.
+     */
+    public void updateL3UcastGroupBucket(MacAddress hostMac, VlanId hostVlanId, PortNumber port, int nextId) {
+
+       MacAddress deviceMac;
+        try {
+            deviceMac = deviceConfig.getDeviceMac(deviceId);
+        } catch (DeviceConfigNotFoundException e) {
+            log.warn(e.getMessage() + " in updateL3UcastGroupBucket");
+            return;
+        }
+
+        TrafficSelector metadata =
+                 DefaultTrafficSelector.builder().matchVlanId(hostVlanId).build();
+
+        TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
+        tbuilder.deferred()
+                .setEthDst(hostMac)
+                .setEthSrc(deviceMac)
+                .setVlanId(hostVlanId)
+                .setOutput(port);
+
+        log.debug(" update L3Ucast : deviceMac {}, port {}, host {}/{}, nextid {}, Treatment {} Meta {}",
+                                     deviceMac, port, hostMac, hostVlanId, nextId, tbuilder.build(), metadata);
+
+        NextObjective.Builder nextObjBuilder = DefaultNextObjective
+                .builder().withId(nextId)
+                .withType(NextObjective.Type.SIMPLE).fromApp(appId)
+                .addTreatment(tbuilder.build())
+                .withMeta(metadata);
+
+        ObjectiveContext context = new DefaultObjectiveContext(
+                (objective) -> log.debug(" NextId {} successfully updated host {} vlan {} with port {}",
+                                         nextId, hostMac, hostVlanId, port),
+                (objective, error) -> {
+                    log.warn(" NextId {} failed to update host {} vlan {} with port {}, error : {}",
+                                         nextId, hostMac, hostVlanId, port, error);
+                    srManager.invalidateNextObj(objective.id());
+                });
+
+        NextObjective nextObj = nextObjBuilder.modify(context);
+        flowObjectiveService.next(deviceId, nextObj);
+
+    }
+
+    /**
      * Adds a single port to the L2FG or removes it from the L2FG.
      *
      * @param vlanId the vlan id corresponding to this port
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/storekey/DestinationSetNextObjectiveStoreKey.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/storekey/DestinationSetNextObjectiveStoreKey.java
index 9b6e621..0baa769 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/storekey/DestinationSetNextObjectiveStoreKey.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/storekey/DestinationSetNextObjectiveStoreKey.java
@@ -21,6 +21,8 @@
 import org.onosproject.net.DeviceId;
 import org.onosproject.segmentrouting.grouphandler.DestinationSet;
 
+import static com.google.common.base.MoreObjects.toStringHelper;
+
 /**
  * Key of Destination set next objective store.
  */
@@ -84,6 +86,9 @@
 
     @Override
     public String toString() {
-        return "Device: " + deviceId + " " + ds;
+        return toStringHelper(getClass())
+                .add("deviceId", deviceId)
+                .add("ds", ds)
+                .toString();
     }
 }
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/storekey/MacVlanNextObjectiveStoreKey.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/storekey/MacVlanNextObjectiveStoreKey.java
new file mode 100644
index 0000000..4a1ab78
--- /dev/null
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/storekey/MacVlanNextObjectiveStoreKey.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.segmentrouting.storekey;
+
+import org.onlab.packet.VlanId;
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.DeviceId;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Key of Device/Vlan/MacAddr to NextObjective store.
+ */
+public class MacVlanNextObjectiveStoreKey {
+    private final DeviceId deviceId;
+    private final MacAddress macAddr;
+    private final VlanId vlanId;
+
+    /**
+     * Constructs the key of the next objective store.
+     *
+     * @param deviceId device ID
+     * @param macAddr mac of host
+     * @param vlanId vlan of host
+     */
+    public MacVlanNextObjectiveStoreKey(DeviceId deviceId, MacAddress macAddr, VlanId vlanId) {
+        this.deviceId = deviceId;
+        this.macAddr = macAddr;
+        this.vlanId = vlanId;
+    }
+
+    /**
+     * Gets device id in this MacVlanNextObjectiveStoreKey.
+     *
+     * @return device id
+     */
+    public DeviceId deviceId() {
+        return deviceId;
+    }
+
+    /**
+     * Gets vlan information in this MacVlanNextObjectiveStoreKey.
+     *
+     * @return vlan information
+     */
+    public VlanId vlanId() {
+        return vlanId;
+    }
+
+    /**
+     * Gets mac information in this MacVlanNextObjectiveStoreKey.
+     *
+     * @return mac information
+     */
+    public MacAddress macAddr() {
+        return macAddr;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof MacVlanNextObjectiveStoreKey)) {
+            return false;
+        }
+        MacVlanNextObjectiveStoreKey that =
+                (MacVlanNextObjectiveStoreKey) o;
+        return (Objects.equals(this.deviceId, that.deviceId) &&
+                Objects.equals(this.vlanId, that.vlanId) &&
+                Objects.equals(this.macAddr, that.macAddr));
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(deviceId, vlanId, macAddr);
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(getClass())
+                .add("deviceId", deviceId)
+                .add("vlanId", vlanId)
+                .add("macAddr", macAddr)
+                .toString();
+    }
+}
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/storekey/PortNextObjectiveStoreKey.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/storekey/PortNextObjectiveStoreKey.java
index 9aa41db..1269bce 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/storekey/PortNextObjectiveStoreKey.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/storekey/PortNextObjectiveStoreKey.java
@@ -22,6 +22,8 @@
 
 import java.util.Objects;
 
+import static com.google.common.base.MoreObjects.toStringHelper;
+
 /**
  * Key of Device/Port to NextObjective store.
  *
@@ -111,8 +113,11 @@
 
     @Override
     public String toString() {
-        return "ConnectPoint: " + deviceId + "/" + portNum +
-                " Treatment: " + treatment +
-                " Meta: " + meta;
+        return toStringHelper(getClass())
+                .add("deviceId", deviceId)
+                .add("portNum", portNum)
+                .add("treatment", treatment)
+                .add("meta", meta)
+                .toString();
     }
 }
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/storekey/VlanNextObjectiveStoreKey.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/storekey/VlanNextObjectiveStoreKey.java
index 5d9f2fd..55a95cd 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/storekey/VlanNextObjectiveStoreKey.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/storekey/VlanNextObjectiveStoreKey.java
@@ -21,6 +21,8 @@
 
 import java.util.Objects;
 
+import static com.google.common.base.MoreObjects.toStringHelper;
+
 /**
  * Key of VLAN to NextObjective store.
  */
@@ -79,6 +81,9 @@
 
     @Override
     public String toString() {
-        return "deviceId: " + deviceId + " vlanId: " + vlanId;
+        return toStringHelper(getClass())
+                .add("deviceId", deviceId)
+                .add("vlanId", vlanId)
+                .toString();
     }
 }
diff --git a/apps/segmentrouting/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/segmentrouting/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index 71f986b..344946b 100644
--- a/apps/segmentrouting/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/apps/segmentrouting/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -55,6 +55,9 @@
             <action class="org.onosproject.segmentrouting.cli.NextPortCommand"/>
         </command>
         <command>
+            <action class="org.onosproject.segmentrouting.cli.NextMacVlanCommand"/>
+        </command>
+        <command>
             <action class="org.onosproject.segmentrouting.cli.PseudowireNextListCommand"/>
         </command>
         <command>
diff --git a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/HostHandlerTest.java b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/HostHandlerTest.java
index f72c264..d0ca6cd 100644
--- a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/HostHandlerTest.java
+++ b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/HostHandlerTest.java
@@ -563,7 +563,7 @@
         // We should expect only one bridging flow and one routing flow programmed on 1A
         mockDefaultRoutingHandler.populateBridging(DEV3, P2, HOST_MAC, HOST_VLAN_UNTAGGED);
         expectLastCall().times(1);
-        mockDefaultRoutingHandler.populateRoute(DEV3, HOST_IP11.toIpPrefix(), HOST_MAC, HOST_VLAN_UNTAGGED, P2);
+        mockDefaultRoutingHandler.populateRoute(DEV3, HOST_IP11.toIpPrefix(), HOST_MAC, HOST_VLAN_UNTAGGED, P2, true);
         expectLastCall().times(1);
         replay(mockDefaultRoutingHandler);
 
diff --git a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockRoutingRulePopulator.java b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockRoutingRulePopulator.java
index 4eab7c0..77eeed2 100644
--- a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockRoutingRulePopulator.java
+++ b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/MockRoutingRulePopulator.java
@@ -38,7 +38,7 @@
 
     @Override
     public void populateRoute(DeviceId deviceId, IpPrefix prefix,
-                              MacAddress hostMac, VlanId hostVlanId, PortNumber outPort) {
+                              MacAddress hostMac, VlanId hostVlanId, PortNumber outPort, boolean directHost) {
         MockRoutingTableKey rtKey = new MockRoutingTableKey(deviceId, prefix);
         MockRoutingTableValue rtValue = new MockRoutingTableValue(outPort, hostMac, hostVlanId);
         routingTable.put(rtKey, rtValue);
@@ -46,9 +46,9 @@
 
     @Override
     public void revokeRoute(DeviceId deviceId, IpPrefix prefix,
-                            MacAddress hostMac, VlanId hostVlanId, PortNumber outPort) {
+                            MacAddress hostMac, VlanId hostVlanId, PortNumber outPort, boolean directHost) {
         MockRoutingTableKey rtKey = new MockRoutingTableKey(deviceId, prefix);
         MockRoutingTableValue rtValue = new MockRoutingTableValue(outPort, hostMac, hostVlanId);
         routingTable.remove(rtKey, rtValue);
     }
-}
\ No newline at end of file
+}
diff --git a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/RouteHandlerTest.java b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/RouteHandlerTest.java
index 494b9ec..ade5d0c 100644
--- a/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/RouteHandlerTest.java
+++ b/apps/segmentrouting/app/src/test/java/org/onosproject/segmentrouting/RouteHandlerTest.java
@@ -595,4 +595,4 @@
 
         verify(srManager.deviceConfiguration);
     }
-}
\ No newline at end of file
+}
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2GroupHandler.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2GroupHandler.java
index ec44b13..b5bf40f 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2GroupHandler.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2GroupHandler.java
@@ -744,7 +744,7 @@
                 }
             }
             if (portNum == null) {
-                log.warn("Can't find output port for the bucket {}.", treatment);
+                log.debug("Can't find output port for the bucket {}.", treatment);
                 continue;
             }
             // assemble info for l2 interface group
@@ -770,12 +770,89 @@
             log.debug("Trying L2-Interface: device:{} gid:{} gkey:{} nextid:{}",
                     deviceId, Integer.toHexString(l2InterfaceGroupId),
                     l2InterfaceGroupKey, nextObj.id());
+
             groupInfoBuilder.add(new GroupInfo(l2InterfaceGroupDescription,
                     l2InterfaceGroupDescription));
         }
         return groupInfoBuilder.build();
     }
 
+    private GroupInfo prepareL3UnicastGroup(NextObjective nextObj, NextGroup next) {
+
+       ImmutableList.Builder<GroupInfo> groupInfoBuilder = ImmutableList.builder();
+       TrafficTreatment treatment = nextObj.next().iterator().next();
+
+       VlanId assignedVlan = readVlanFromSelector(nextObj.meta());
+       if (assignedVlan == null) {
+            log.warn("VLAN ID required by next obj is missing. Abort.");
+            return null;
+       }
+
+       List<GroupInfo> l2GroupInfos = prepareL2InterfaceGroup(nextObj, assignedVlan);
+       GroupDescription l2InterfaceGroupDesc = l2GroupInfos.get(0).innerMostGroupDesc();
+       GroupKey l2groupkey = l2InterfaceGroupDesc.appCookie();
+
+       TrafficTreatment.Builder outerTtb = DefaultTrafficTreatment.builder();
+       VlanId vlanid = null;
+       MacAddress srcMac;
+       MacAddress dstMac;
+       for (Instruction ins : treatment.allInstructions()) {
+            if (ins.type() == Instruction.Type.L2MODIFICATION) {
+                L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
+                switch (l2ins.subtype()) {
+                    case ETH_DST:
+                        dstMac = ((L2ModificationInstruction.ModEtherInstruction) l2ins).mac();
+                        outerTtb.setEthDst(dstMac);
+                        break;
+                    case ETH_SRC:
+                        srcMac = ((L2ModificationInstruction.ModEtherInstruction) l2ins).mac();
+                        outerTtb.setEthSrc(srcMac);
+                        break;
+                    case VLAN_ID:
+                        vlanid = ((L2ModificationInstruction.ModVlanIdInstruction) l2ins).vlanId();
+                            outerTtb.setVlanId(vlanid);
+                        break;
+                    default:
+                        break;
+                }
+            } else {
+                log.debug("Driver does not handle this type of TrafficTreatment"
+                        + " instruction in l2l3chain:  {} - {}", ins.type(), ins);
+            }
+       }
+
+       GroupId l2groupId = new GroupId(l2InterfaceGroupDesc.givenGroupId());
+       outerTtb.group(l2groupId);
+
+       // we need the top level group's key to point the flow to it
+       List<Deque<GroupKey>> gkeys = appKryo.deserialize(next.data());
+       GroupKey l3groupkey = gkeys.get(0).peekFirst();
+       GroupId grpId = groupService.getGroup(deviceId, l3groupkey).id();
+       int l3groupId = grpId.id();
+
+       // create the l3unicast group description to wait for the
+       // l2 interface group to be processed
+       GroupBucket l3UnicastGroupBucket =  DefaultGroupBucket.createIndirectGroupBucket(outerTtb.build());
+
+       GroupDescription l3UnicastGroupDescription = new DefaultGroupDescription(deviceId,
+                                                        GroupDescription.Type.INDIRECT,
+                                                        new GroupBuckets(Collections.singletonList(
+                                                        l3UnicastGroupBucket)), l3groupkey,
+                                                        l3groupId, nextObj.appId());
+
+      // store l2groupkey with the groupChainElem for the outer-group that depends on it
+      GroupChainElem gce = new GroupChainElem(l3UnicastGroupDescription, 1, false, deviceId);
+      updatePendingGroups(l2groupkey, gce);
+
+      log.debug("Trying L3-Interface: device:{} gid:{} gkey:{} nextid:{}",
+                        deviceId, Integer.toHexString(l3groupId), l3groupkey, nextObj.id());
+
+      groupInfoBuilder.add(new GroupInfo(l2InterfaceGroupDesc,
+                    l3UnicastGroupDescription));
+
+      return groupInfoBuilder.build().iterator().next();
+    }
+
     private void createL2FloodGroup(NextObjective nextObj, VlanId vlanId,
                                     List<GroupInfo> groupInfos) {
         // assemble info for l2 flood group. Since there can be only one flood
@@ -1553,6 +1630,64 @@
     }
 
     /**
+     * modifies group with next objective.
+     *
+     * @param nextObjective the NextObjective
+     * @param nextGroup the NextGroup
+    */
+    protected void modifyBucketFromGroup(NextObjective nextObjective, NextGroup nextGroup) {
+        switch (nextObjective.type()) {
+            case SIMPLE:
+                Collection<TrafficTreatment> treatments = nextObjective.next();
+                if (treatments.size() != 1) {
+                    log.error("Next Objectives of type Simple should only have a "
+                                    + "single Traffic Treatment. Next Objective Id:{}",
+                            nextObjective.id());
+                    fail(nextObjective, ObjectiveError.BADPARAMS);
+                    return;
+                }
+                modifySimpleNextObjective(nextObjective, nextGroup);
+                break;
+            default:
+                fail(nextObjective, ObjectiveError.UNKNOWN);
+                log.warn("Unknown next objective type {}", nextObjective.type());
+        }
+    }
+
+     /**
+     * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
+     * a chain of groups. The simple Next Objective passed in by the application
+     * is broken up into a group chain. The following chains can be modified
+     * depending on the parameters in the Next Objective.
+     * 1. L2 Interface group (no chaining)
+     * 2. L3 Unicast group -> L2 Interface group
+     *
+     * @param nextObj  the nextObjective of type SIMPLE
+     */
+    private void modifySimpleNextObjective(NextObjective nextObj, NextGroup nextGroup) {
+        TrafficTreatment treatment = nextObj.next().iterator().next();
+        // determine if plain L2 or L3->L2 chain
+        boolean plainL2 = true;
+        for (Instruction ins : treatment.allInstructions()) {
+            if (ins.type() == Instruction.Type.L2MODIFICATION) {
+                L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
+                if (l2ins.subtype() == L2ModificationInstruction.L2SubType.ETH_DST ||
+                        l2ins.subtype() == L2ModificationInstruction.L2SubType.ETH_SRC ||
+                        l2ins.subtype() == L2ModificationInstruction.L2SubType.VLAN_ID) {
+                    plainL2 = false;
+                }
+            }
+        }
+        if (plainL2) {
+            modifyBucketInL2Group(nextObj, nextGroup);
+        } else {
+            modifyBucketInL3Group(nextObj, nextGroup);
+        }
+        return;
+    }
+
+
+    /**
      * Modify buckets in the L2 interface group.
      *
      * @param nextObjective a next objective that contains information for the
@@ -1560,13 +1695,8 @@
      * @param next the representation of the existing group-chains for this next
      *             objective, from which the innermost group buckets to remove are determined
      */
-    protected void modifyBucketFromGroup(NextObjective nextObjective, NextGroup next) {
-        if (nextObjective.type() != NextObjective.Type.SIMPLE) {
-            log.warn("ModifyBucketFromGroup cannot be applied to nextType:{} in dev:{} for next:{}",
-                     nextObjective.type(), deviceId, nextObjective.id());
-            fail(nextObjective, ObjectiveError.UNSUPPORTED);
-            return;
-        }
+
+    protected void modifyBucketInL2Group(NextObjective nextObjective, NextGroup next) {
 
         VlanId assignedVlan = readVlanFromSelector(nextObjective.meta());
         if (assignedVlan == null) {
@@ -1600,8 +1730,54 @@
                                             new OfdpaNextGroup(modifiedGroupKeys,
                                                                nextObjective));
         }
+
     }
 
+    protected void modifyBucketInL3Group(NextObjective nextObjective, NextGroup next) {
+
+        //get l3 group
+        GroupInfo groupInfo = prepareL3UnicastGroup(nextObjective, next);
+        if (groupInfo == null) {
+            log.warn("Null groupInfo retrieved for next obj. Abort.");
+            fail(nextObjective, ObjectiveError.BADPARAMS);
+            return;
+        }
+
+        GroupDescription l3UnicastGroupDesc = groupInfo.nextGroupDesc();
+
+        // Replace group bucket for L3 UC interface group
+        groupService.setBucketsForGroup(deviceId, l3UnicastGroupDesc.appCookie(),
+                        l3UnicastGroupDesc.buckets(), l3UnicastGroupDesc.appCookie(),
+                        l3UnicastGroupDesc.appId());
+
+        // create object for local and distributed storage
+        Deque<GroupKey> gkeyChain = new ArrayDeque<>();
+        gkeyChain.addFirst(groupInfo.innerMostGroupDesc().appCookie());
+        gkeyChain.addFirst(groupInfo.nextGroupDesc().appCookie());
+        List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
+        allGroupKeys.add(gkeyChain);
+        OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObjective);
+        // store l3groupkey with the ofdpaNextGroup for the nextObjective that depends on it
+        updatePendingNextObjective(groupInfo.nextGroupDesc().appCookie(), ofdpaGrp);
+
+        // update store - synchronize access as there may be multiple threads
+        // trying to update bucket from the same group, each with its own
+        // potentially stale copy of allActiveKeys
+        synchronized (flowObjectiveStore) {
+
+            List<Deque<GroupKey>> modifiedGroupKeys = Lists.newArrayList();
+            ArrayDeque<GroupKey> top = new ArrayDeque<>();
+            top.add(l3UnicastGroupDesc.appCookie());
+            top.add(groupInfo.innerMostGroupDesc().appCookie()); //l2 group key
+            modifiedGroupKeys.add(top);
+
+            flowObjectiveStore.putNextGroup(nextObjective.id(),
+                                            new OfdpaNextGroup(modifiedGroupKeys,
+                                                               nextObjective));
+        }
+    }
+
+
     /**
      *  Checks existing buckets in {@link NextGroup}  to verify if they match
      *  the buckets in the given {@link NextObjective}. Adds or removes buckets
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2Pipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2Pipeline.java
index 0bfc113..46e2e75 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2Pipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2Pipeline.java
@@ -407,8 +407,8 @@
                          nextObjective.id(), deviceId);
                 return;
             }
-            log.debug("Processing NextObjective id {} in dev {} - modify bucket",
-                      nextObjective.id(), deviceId);
+            log.debug("Processing NextObjective id {} in dev {} group {} - modify bucket",
+                      nextObjective.id(), deviceId, nextGroup);
             groupHandler.modifyBucketFromGroup(nextObjective, nextGroup);
             break;
         case VERIFY:
diff --git a/tools/build/conf/src/main/resources/onos/suppressions.xml b/tools/build/conf/src/main/resources/onos/suppressions.xml
index 0f537f2..2c7672f7 100644
--- a/tools/build/conf/src/main/resources/onos/suppressions.xml
+++ b/tools/build/conf/src/main/resources/onos/suppressions.xml
@@ -30,6 +30,7 @@
     <suppress files="org.onlab.packet.*" checks="AbbreviationAsWordInName" />
     <suppress files="org.onlab.jdvue.*" checks="AbbreviationAsWordInName" />
     <suppress files="org.onosproject.driver.pipeline.*" checks="AbbreviationAsWordInName" />
+    <suppress files="org.onosproject.driver.pipeline.ofdpa.Ofdpa2GroupHandler.java" checks="FileLength" />
     <suppress files="org.onosproject.segmentrouting.*" checks="AbbreviationAsWordInName" />
 
     <!-- These files carry AT&T copyrights -->
diff --git a/tools/package/runtime/bin/onos-diagnostics b/tools/package/runtime/bin/onos-diagnostics
index 39d2c06..253cfbe 100755
--- a/tools/package/runtime/bin/onos-diagnostics
+++ b/tools/package/runtime/bin/onos-diagnostics
@@ -109,6 +109,7 @@
     "sr-next-vlan"
     "sr-next-pw"
     "sr-next-xconnect"
+    "sr-next-mac-vlan"
     "dhcp-relay"
 
     "mcast-host-routes"