Routing/bridging rules on the same leaf pair should always be programmed by the same ONOS instance

Main change:
- Implement new logic for shouldHandleRouting, along with corresponding unit tests. Also rename it to shouldProgram

Side changes:
- Refactor revokeSubnet such that it is only invoked on the instance that should handle routing change
- Move the following methods to RoutingRulePopulator and follow the same design pattern as populate/revoke subnet/route
    - populateBridging, revokeBridging, updateBridging
    - updateFwdObj
- Make sure the following methods in RoutingRulePopulator are always invoked by DefaultRoutingHandler with shouldProgram check
    - populateRoute, revokeRoute
    - populateSubnet, revokeSubnet
    - populateBridging, revokeBridging, updateBridging
    - updateFwdObj

Change-Id: I903129271ede91c45ebf0d973e06faeae46c157a
diff --git a/app/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java b/app/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
index ae20b3f..236121c 100644
--- a/app/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
+++ b/app/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java
@@ -37,14 +37,18 @@
 import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
 import org.onosproject.segmentrouting.config.DeviceConfiguration;
 import org.onosproject.segmentrouting.grouphandler.DefaultGroupHandler;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.Serializer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.time.Instant;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
@@ -82,6 +86,10 @@
         = newScheduledThreadPool(1, groupedThreads("retryftr", "retry-%d", log));
     private Instant lastRoutingChange;
 
+    // Keep track on which ONOS instance should program the device pair.
+    // There should be only one instance that programs the same pair.
+    Map<Set<DeviceId>, NodeId> shouldProgram;
+
     /**
      * Represents the default routing population status.
      */
@@ -105,12 +113,16 @@
      *
      * @param srManager SegmentRoutingManager object
      */
-    public DefaultRoutingHandler(SegmentRoutingManager srManager) {
+    DefaultRoutingHandler(SegmentRoutingManager srManager) {
         this.srManager = srManager;
         this.rulePopulator = checkNotNull(srManager.routingRulePopulator);
         this.config = checkNotNull(srManager.deviceConfiguration);
         this.populationStatus = Status.IDLE;
         this.currentEcmpSpgMap = Maps.newHashMap();
+        this.shouldProgram = srManager.storageService.<Set<DeviceId>, NodeId>consistentMapBuilder()
+                .withName("sr-should-program")
+                .withSerializer(Serializer.using(KryoNamespaces.API))
+                .build().asJavaMap();
     }
 
     /**
@@ -203,13 +215,12 @@
                     updatedEcmpSpgMap.put(pairDev.get(), ecmpSpgUpdated);
                     edgePairs.add(new EdgePair(dstSw, pairDev.get()));
                 }
-                DeviceId ret = shouldHandleRouting(dstSw);
-                if (ret == null) {
+
+                if (!shouldProgram(dstSw)) {
                     continue;
                 }
-                Set<DeviceId> devsToProcess = Sets.newHashSet(dstSw, ret);
                 // To do a full reroute, assume all routes have changed
-                for (DeviceId dev : devsToProcess) {
+                for (DeviceId dev : deviceAndItsPair(dstSw)) {
                     for (DeviceId targetSw : srManager.deviceConfiguration.getRouters()) {
                         if (targetSw.equals(dev)) {
                             continue;
@@ -301,8 +312,7 @@
                         log.warn("populateSubnet: no updated graph for dev:{}"
                                 + " ... creating", cp.deviceId());
                     }
-                    DeviceId retId = shouldHandleRouting(cp.deviceId());
-                    if (retId == null) {
+                    if (!shouldProgram(cp.deviceId())) {
                         continue;
                     }
                     handleRouting = true;
@@ -317,9 +327,7 @@
                     log.warn("populateSubnet: no updated graph for dev:{}"
                             + " ... creating", dstSw);
                 }
-                if (srManager.mastershipService.isLocalMaster(dstSw)) {
-                    handleRouting = true;
-                }
+                handleRouting = shouldProgram(dstSw);
             }
 
             if (!handleRouting) {
@@ -1066,7 +1074,10 @@
     protected boolean revokeSubnet(Set<IpPrefix> subnets) {
         statusLock.lock();
         try {
-            return srManager.routingRulePopulator.revokeIpRuleForSubnet(subnets);
+            return Sets.newHashSet(srManager.deviceService.getAvailableDevices()).stream()
+                    .map(Device::id)
+                    .filter(this::shouldProgram)
+                    .allMatch(targetSw -> srManager.routingRulePopulator.revokeIpRuleForSubnet(targetSw, subnets));
         } finally {
             statusLock.unlock();
         }
@@ -1084,7 +1095,7 @@
      */
     void populateRoute(DeviceId deviceId, IpPrefix prefix,
                        MacAddress hostMac, VlanId hostVlanId, PortNumber outPort) {
-        if (srManager.mastershipService.isLocalMaster(deviceId)) {
+        if (shouldProgram(deviceId)) {
             srManager.routingRulePopulator.populateRoute(deviceId, prefix, hostMac, hostVlanId, outPort);
         }
     }
@@ -1101,11 +1112,38 @@
      */
     void revokeRoute(DeviceId deviceId, IpPrefix prefix,
                      MacAddress hostMac, VlanId hostVlanId, PortNumber outPort) {
-        if (srManager.mastershipService.isLocalMaster(deviceId)) {
+        if (shouldProgram(deviceId)) {
             srManager.routingRulePopulator.revokeRoute(deviceId, prefix, hostMac, hostVlanId, outPort);
         }
     }
 
+    void populateBridging(DeviceId deviceId, PortNumber port, MacAddress mac, VlanId vlanId) {
+        if (shouldProgram(deviceId)) {
+            srManager.routingRulePopulator.populateBridging(deviceId, port, mac, vlanId);
+        }
+    }
+
+    void revokeBridging(DeviceId deviceId, PortNumber port, MacAddress mac, VlanId vlanId) {
+        if (shouldProgram(deviceId)) {
+            srManager.routingRulePopulator.revokeBridging(deviceId, port, mac, vlanId);
+        }
+    }
+
+    void updateBridging(DeviceId deviceId, PortNumber portNum, MacAddress hostMac,
+                        VlanId vlanId, boolean popVlan, boolean install) {
+        if (shouldProgram(deviceId)) {
+            srManager.routingRulePopulator.updateBridging(deviceId, portNum, hostMac, vlanId, popVlan, install);
+        }
+    }
+
+    void updateFwdObj(DeviceId deviceId, PortNumber portNumber, IpPrefix prefix, MacAddress hostMac,
+                      VlanId vlanId, boolean popVlan, boolean install) {
+        if (shouldProgram(deviceId)) {
+            srManager.routingRulePopulator.updateFwdObj(deviceId, portNumber, prefix, hostMac,
+                    vlanId, popVlan, install);
+        }
+    }
+
     /**
      * Remove ECMP graph entry for the given device. Typically called when
      * device is no longer available.
@@ -1150,12 +1188,10 @@
         for (Device sw : srManager.deviceService.getDevices()) {
             log.debug("Computing the impacted routes for device {} due to link fail",
                       sw.id());
-            DeviceId retId = shouldHandleRouting(sw.id());
-            if (retId == null) {
+            if (!shouldProgram(sw.id())) {
                 continue;
             }
-            Set<DeviceId> devicesToProcess = Sets.newHashSet(retId, sw.id());
-            for (DeviceId rootSw : devicesToProcess) {
+            for (DeviceId rootSw : deviceAndItsPair(sw.id())) {
                 EcmpShortestPathGraph ecmpSpg = currentEcmpSpgMap.get(rootSw);
                 if (ecmpSpg == null) {
                     log.warn("No existing ECMP graph for switch {}. Aborting optimized"
@@ -1219,12 +1255,10 @@
 
         for (Device sw : srManager.deviceService.getDevices()) {
             log.debug("Computing the impacted routes for device {}", sw.id());
-            DeviceId retId = shouldHandleRouting(sw.id());
-            if (retId == null) {
+            if (!shouldProgram(sw.id())) {
                 continue;
             }
-            Set<DeviceId> devicesToProcess = Sets.newHashSet(retId, sw.id());
-            for (DeviceId rootSw : devicesToProcess) {
+            for (DeviceId rootSw : deviceAndItsPair(sw.id())) {
                 if (log.isTraceEnabled()) {
                     log.trace("Device links for dev: {}", rootSw);
                     for (Link link: srManager.linkService.getDeviceLinks(rootSw)) {
@@ -1375,47 +1409,87 @@
     }
 
     /**
-     * Determines whether this controller instance should handle routing for the
+     * Determines whether this controller instance should program the
      * given {@code deviceId}, based on mastership and pairDeviceId if one exists.
-     * Returns null if this instance should not handle routing for given {@code deviceId}.
-     * Otherwise the returned value could be the given deviceId itself, or the
-     * deviceId for the paired edge device. In the latter case, this instance
-     * should handle routing for both the given device and the paired device.
+     * <p>
+     * Once an instance is elected, it will be the only instance responsible for programming
+     * both devices in the pair until it goes down.
      *
      * @param deviceId device identifier to consider for routing
-     * @return null or deviceId which could be the same as the given deviceId
-     *          or the deviceId of a paired edge device
+     * @return true if current instance should handle the routing for given device
      */
-    private DeviceId shouldHandleRouting(DeviceId deviceId) {
-        if (!srManager.mastershipService.isLocalMaster(deviceId)) {
-            log.debug("Not master for dev:{} .. skipping routing, may get handled "
-                    + "elsewhere as part of paired devices", deviceId);
-            return null;
-        }
-        NodeId myNode = srManager.mastershipService.getMasterFor(deviceId);
-        Optional<DeviceId> pairDev = srManager.getPairDeviceId(deviceId);
+    boolean shouldProgram(DeviceId deviceId) {
+        Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(deviceId);
 
-        if (pairDev.isPresent()) {
-            if (!srManager.deviceService.isAvailable(pairDev.get())) {
-                log.warn("pairedDev {} not available .. routing both this dev:{} "
-                        + "and pair without mastership check for pair",
-                          pairDev, deviceId);
-                return pairDev.get(); // handle both temporarily
-            }
-            NodeId pairMasterNode = srManager.mastershipService.getMasterFor(pairDev.get());
-            if (myNode.compareTo(pairMasterNode) <= 0) {
-                log.debug("Handling routing for both dev:{} pair-dev:{}; myNode: {}"
-                        + " pairMaster:{} compare:{}", deviceId, pairDev,
-                        myNode, pairMasterNode,
-                        myNode.compareTo(pairMasterNode));
-                return pairDev.get(); // handle both
-            } else {
-                log.debug("PairDev node: {} should handle routing for dev:{} and "
-                        + "pair-dev:{}", pairMasterNode, deviceId, pairDev);
-                return null; // handle neither
-            }
+        NodeId currentNodeId = srManager.clusterService.getLocalNode().id();
+        NodeId masterNodeId = srManager.mastershipService.getMasterFor(deviceId);
+        Optional<NodeId> pairMasterNodeId = pairDeviceId.map(srManager.mastershipService::getMasterFor);
+        log.debug("Evaluate shouldProgram {}/pair={}. current={}, master={}, pairMaster={}",
+                deviceId, pairDeviceId, currentNodeId, masterNodeId, pairMasterNodeId);
+
+        // No pair device configured. Only handle when current instance is the master of the device
+        if (!pairDeviceId.isPresent()) {
+            log.debug("No pair device. current={}, master={}", currentNodeId, masterNodeId);
+            return currentNodeId.equals(masterNodeId);
         }
-        return deviceId; // not paired, just handle given device
+
+        // Should not handle if current instance is not the master of either switch
+        if (!currentNodeId.equals(masterNodeId) &&
+                !(pairMasterNodeId.isPresent() && currentNodeId.equals(pairMasterNodeId.get()))) {
+            log.debug("Current node {} is neither the master of target device {} nor pair device {}",
+                    currentNodeId, deviceId, pairDeviceId);
+            return false;
+        }
+
+        Set<DeviceId> key = Sets.newHashSet(deviceId, pairDeviceId.get());
+
+        NodeId king = shouldProgram.compute(key, ((k, v) -> {
+            if (v == null) {
+                // There is no value in the map. Elect a node
+                return elect(Lists.newArrayList(masterNodeId, pairMasterNodeId.orElse(null)));
+            } else {
+                if (v.equals(masterNodeId) || v.equals(pairMasterNodeId.orElse(null))) {
+                    // Use the node in the map if it is still alive and is a master of any of the two switches
+                    return v;
+                } else {
+                    // Previously elected node is no longer the master of either switch. Re-elect a node.
+                    return elect(Lists.newArrayList(masterNodeId, pairMasterNodeId.orElse(null)));
+                }
+            }
+        }));
+
+        if (king != null) {
+            log.debug("{} should handle routing for {}/pair={}", king, deviceId, pairDeviceId);
+            return king.equals(currentNodeId);
+        } else {
+            log.error("Fail to elect a king for {}/pair={}. Abort.", deviceId, pairDeviceId);
+            return false;
+        }
+    }
+
+    /**
+     * Elects a node who should take responsibility of programming devices.
+     * @param nodeIds list of candidate node ID
+     *
+     * @return NodeId of the node that gets elected, or null if none of the node can be elected
+     */
+    private NodeId elect(List<NodeId> nodeIds) {
+        // Remove all null elements. This could happen when some device has no master
+        nodeIds.removeAll(Collections.singleton(null));
+        nodeIds.sort(null);
+        return nodeIds.size() == 0 ? null : nodeIds.get(0);
+    }
+
+    /**
+     * Returns a set of device ID, containing given device and its pair device if exist.
+     *
+     * @param deviceId Device ID
+     * @return a set of device ID, containing given device and its pair device if exist.
+     */
+    private Set<DeviceId> deviceAndItsPair(DeviceId deviceId) {
+        Set<DeviceId> ret = Sets.newHashSet(deviceId);
+        srManager.getPairDeviceId(deviceId).ifPresent(ret::add);
+        return ret;
     }
 
     /**
diff --git a/app/src/main/java/org/onosproject/segmentrouting/HostHandler.java b/app/src/main/java/org/onosproject/segmentrouting/HostHandler.java
index 908a339..9590632 100644
--- a/app/src/main/java/org/onosproject/segmentrouting/HostHandler.java
+++ b/app/src/main/java/org/onosproject/segmentrouting/HostHandler.java
@@ -25,15 +25,6 @@
 import org.onosproject.net.Host;
 import org.onosproject.net.HostLocation;
 import org.onosproject.net.PortNumber;
-import org.onosproject.net.flow.DefaultTrafficSelector;
-import org.onosproject.net.flow.DefaultTrafficTreatment;
-import org.onosproject.net.flow.TrafficSelector;
-import org.onosproject.net.flow.TrafficTreatment;
-import org.onosproject.net.flowobjective.DefaultForwardingObjective;
-import org.onosproject.net.flowobjective.DefaultObjectiveContext;
-import org.onosproject.net.flowobjective.FlowObjectiveService;
-import org.onosproject.net.flowobjective.ForwardingObjective;
-import org.onosproject.net.flowobjective.ObjectiveContext;
 import org.onosproject.net.host.HostEvent;
 import org.onosproject.net.host.HostLocationProbingService.ProbeMode;
 import org.onosproject.net.host.HostService;
@@ -61,7 +52,6 @@
 
     protected final SegmentRoutingManager srManager;
     private HostService hostService;
-    private FlowObjectiveService flowObjectiveService;
 
     /**
      * Constructs the HostHandler.
@@ -71,7 +61,6 @@
     HostHandler(SegmentRoutingManager srManager) {
         this.srManager = srManager;
         hostService = srManager.hostService;
-        flowObjectiveService = srManager.flowObjectiveService;
     }
 
     protected void init(DeviceId devId) {
@@ -108,19 +97,16 @@
         Set<IpAddress> ips = host.ipAddresses();
         log.info("Host {}/{} is added at {}", hostMac, hostVlanId, locations);
 
-        if (srManager.isMasterOf(location)) {
-            processBridgingRule(location.deviceId(), location.port(), hostMac, hostVlanId, false);
-            ips.forEach(ip ->
-                    processRoutingRule(location.deviceId(), location.port(), hostMac, hostVlanId, ip, false)
-            );
-        }
+        processBridgingRule(location.deviceId(), location.port(), hostMac, hostVlanId, false);
+        ips.forEach(ip ->
+                processRoutingRule(location.deviceId(), location.port(), hostMac, hostVlanId, ip, false)
+        );
 
         // Use the pair link temporarily before the second location of a dual-homed host shows up.
         // This do not affect single-homed hosts since the flow will be blocked in
         // processBridgingRule or processRoutingRule due to VLAN or IP mismatch respectively
         srManager.getPairDeviceId(location.deviceId()).ifPresent(pairDeviceId -> {
-            if (srManager.mastershipService.isLocalMaster(pairDeviceId) &&
-                    host.locations().stream().noneMatch(l -> l.deviceId().equals(pairDeviceId))) {
+            if (host.locations().stream().noneMatch(l -> l.deviceId().equals(pairDeviceId))) {
                 srManager.getPairLocalPorts(pairDeviceId).ifPresent(pairRemotePort -> {
                     // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
                     //       when the host is untagged
@@ -150,18 +136,15 @@
         log.info("Host {}/{} is removed from {}", hostMac, hostVlanId, locations);
 
         locations.forEach(location -> {
-            if (srManager.isMasterOf(location)) {
-                processBridgingRule(location.deviceId(), location.port(), hostMac, hostVlanId, true);
-                ips.forEach(ip ->
-                        processRoutingRule(location.deviceId(), location.port(), hostMac, hostVlanId, ip, true)
-                );
-            }
+            processBridgingRule(location.deviceId(), location.port(), hostMac, hostVlanId, true);
+            ips.forEach(ip ->
+                    processRoutingRule(location.deviceId(), location.port(), hostMac, hostVlanId, ip, true)
+            );
 
             // Also remove redirection flows on the pair device if exists.
             Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(location.deviceId());
             Optional<PortNumber> pairLocalPort = srManager.getPairLocalPorts(location.deviceId());
-            if (pairDeviceId.isPresent() && pairLocalPort.isPresent() &&
-                    srManager.mastershipService.isLocalMaster(pairDeviceId.get())) {
+            if (pairDeviceId.isPresent() && pairLocalPort.isPresent()) {
                 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
                 //       when the host is untagged
                 VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(location)).orElse(hostVlanId);
@@ -202,8 +185,7 @@
                 .collect(Collectors.toSet());
 
         // For each old location
-        Sets.difference(prevLocations, newLocations).stream().filter(srManager::isMasterOf)
-                .forEach(prevLocation -> {
+        Sets.difference(prevLocations, newLocations).forEach(prevLocation -> {
             // Remove routing rules for old IPs
             Sets.difference(prevIps, newIps).forEach(ip ->
                     processRoutingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId,
@@ -263,7 +245,7 @@
         });
 
         // For each new location, add all new IPs.
-        Sets.difference(newLocations, prevLocations).stream().filter(srManager::isMasterOf)
+        Sets.difference(newLocations, prevLocations).stream()
                 .forEach(newLocation -> {
             processBridgingRule(newLocation.deviceId(), newLocation.port(), hostMac, hostVlanId, false);
             newIps.forEach(ip ->
@@ -273,7 +255,7 @@
         });
 
         // For each unchanged location, add new IPs and remove old IPs.
-        Sets.intersection(newLocations, prevLocations).stream().filter(srManager::isMasterOf)
+        Sets.intersection(newLocations, prevLocations).stream()
                 .forEach(unchangedLocation -> {
             Sets.difference(prevIps, newIps).forEach(ip ->
                     processRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(), hostMac,
@@ -305,7 +287,7 @@
         Set<IpAddress> newIps = host.ipAddresses();
         log.info("Host {}/{} is updated", hostMac, hostVlanId);
 
-        locations.stream().filter(srManager::isMasterOf).forEach(location -> {
+        locations.forEach(location -> {
             Sets.difference(prevIps, newIps).forEach(ip ->
                     processRoutingRule(location.deviceId(), location.port(), hostMac, hostVlanId, ip, true)
             );
@@ -319,8 +301,7 @@
         // processBridgingRule or processRoutingRule due to VLAN or IP mismatch respectively
         locations.forEach(location ->
             srManager.getPairDeviceId(location.deviceId()).ifPresent(pairDeviceId -> {
-                if (srManager.mastershipService.isLocalMaster(pairDeviceId) &&
-                        locations.stream().noneMatch(l -> l.deviceId().equals(pairDeviceId))) {
+                if (locations.stream().noneMatch(l -> l.deviceId().equals(pairDeviceId))) {
                     Set<IpAddress> ipsToAdd = Sets.difference(newIps, prevIps);
                     Set<IpAddress> ipsToRemove = Sets.difference(prevIps, newIps);
 
@@ -408,81 +389,6 @@
     }
 
     /**
-     * Generates a forwarding objective builder for bridging rules.
-     * <p>
-     * The forwarding objective bridges packets destined to a given MAC to
-     * given port on given device.
-     *
-     * @param deviceId Device that host attaches to
-     * @param mac MAC address of the host
-     * @param hostVlanId VLAN ID of the host
-     * @param outport Port that host attaches to
-     * @param revoke true if forwarding objective is meant to revoke forwarding rule
-     * @return Forwarding objective builder
-     */
-    ForwardingObjective.Builder bridgingFwdObjBuilder(
-            DeviceId deviceId, MacAddress mac, VlanId hostVlanId,
-            PortNumber outport, boolean revoke) {
-        ConnectPoint connectPoint = new ConnectPoint(deviceId, outport);
-        VlanId untaggedVlan = srManager.getUntaggedVlanId(connectPoint);
-        Set<VlanId> taggedVlans = srManager.getTaggedVlanId(connectPoint);
-        VlanId nativeVlan = srManager.getNativeVlanId(connectPoint);
-
-        // Create host selector
-        TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
-        sbuilder.matchEthDst(mac);
-
-        // Create host treatment
-        TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
-        tbuilder.immediate().setOutput(outport);
-
-        // Create host meta
-        TrafficSelector.Builder mbuilder = DefaultTrafficSelector.builder();
-
-        // Adjust the selector, treatment and meta according to VLAN configuration
-        if (taggedVlans.contains(hostVlanId)) {
-            sbuilder.matchVlanId(hostVlanId);
-            mbuilder.matchVlanId(hostVlanId);
-        } else if (hostVlanId.equals(VlanId.NONE)) {
-            if (untaggedVlan != null) {
-                sbuilder.matchVlanId(untaggedVlan);
-                mbuilder.matchVlanId(untaggedVlan);
-                tbuilder.immediate().popVlan();
-            } else if (nativeVlan != null) {
-                sbuilder.matchVlanId(nativeVlan);
-                mbuilder.matchVlanId(nativeVlan);
-                tbuilder.immediate().popVlan();
-            } else {
-                log.warn("Untagged host {}/{} is not allowed on {} without untagged or native" +
-                        "vlan config", mac, hostVlanId, connectPoint);
-                return null;
-            }
-        } else {
-            log.warn("Tagged host {}/{} is not allowed on {} without VLAN listed in tagged vlan",
-                    mac, hostVlanId, connectPoint);
-            return null;
-        }
-
-        // All forwarding is via Groups. Drivers can re-purpose to flow-actions if needed.
-        // 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,
-                tbuilder.build(), mbuilder.build(), !revoke);
-        if (portNextObjId == -1) {
-            // Warning log will come from getPortNextObjective method
-            return null;
-        }
-
-        return DefaultForwardingObjective.builder()
-                .withFlag(ForwardingObjective.Flag.SPECIFIC)
-                .withSelector(sbuilder.build())
-                .nextStep(portNextObjId)
-                .withPriority(100)
-                .fromApp(srManager.appId)
-                .makePermanent();
-    }
-
-    /**
      * Populate or revoke a bridging rule on given deviceId that matches given mac, given vlan and
      * output to given port.
      *
@@ -494,21 +400,14 @@
      */
     private void processBridgingRule(DeviceId deviceId, PortNumber port, MacAddress mac,
                                      VlanId vlanId, boolean revoke) {
-        log.debug("{} bridging entry for host {}/{} at {}:{}", revoke ? "Revoking" : "Populating",
+        log.info("{} bridging entry for host {}/{} at {}:{}", revoke ? "Revoking" : "Populating",
                 mac, vlanId, deviceId, port);
 
-        ForwardingObjective.Builder fob = bridgingFwdObjBuilder(deviceId, mac, vlanId, port, revoke);
-        if (fob == null) {
-            log.warn("Fail to build fwd obj for host {}/{}. Abort.", mac, vlanId);
-            return;
+        if (!revoke) {
+            srManager.defaultRoutingHandler.populateBridging(deviceId, port, mac, vlanId);
+        } else {
+            srManager.defaultRoutingHandler.revokeBridging(deviceId, port, mac, vlanId);
         }
-
-        ObjectiveContext context = new DefaultObjectiveContext(
-                (objective) -> log.debug("Brigding rule for {}/{} {}", mac, vlanId,
-                        revoke ? "revoked" : "populated"),
-                (objective, error) -> log.warn("Failed to {} bridging rule for {}/{}: {}",
-                        revoke ? "revoked" : "populated", mac, vlanId, error));
-        flowObjectiveService.forward(deviceId, revoke ? fob.remove(context) : fob.add(context));
     }
 
     /**
@@ -538,59 +437,7 @@
         }
     }
 
-    /**
-     * Populate or revoke a bridging rule on given deviceId that matches given vlanId,
-     * and hostMAC connected to given port, and output to given port only when
-     * vlan information is valid.
-     *
-     * @param deviceId device ID that host attaches to
-     * @param portNum port number that host attaches to
-     * @param hostMac mac address of the host connected to the switch port
-     * @param vlanId Vlan ID configured on the switch port
-     * @param popVlan true to pop Vlan tag at TrafficTreatment, false otherwise
-     * @param install true to populate the objective, false to revoke
-     */
-    private void updateBridgingRule(DeviceId deviceId, PortNumber portNum, MacAddress hostMac,
-                            VlanId vlanId, boolean popVlan, boolean install) {
-        // Create host selector
-        TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
-        sbuilder.matchEthDst(hostMac);
 
-        // Create host meta
-        TrafficSelector.Builder mbuilder = DefaultTrafficSelector.builder();
-
-        sbuilder.matchVlanId(vlanId);
-        mbuilder.matchVlanId(vlanId);
-
-        // Create host treatment
-        TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
-        tbuilder.immediate().setOutput(portNum);
-
-        if (popVlan) {
-            tbuilder.immediate().popVlan();
-        }
-
-        int portNextObjId = srManager.getPortNextObjectiveId(deviceId, portNum,
-                                                             tbuilder.build(), mbuilder.build(), install);
-        if (portNextObjId != -1) {
-            ForwardingObjective.Builder fob = DefaultForwardingObjective.builder()
-                    .withFlag(ForwardingObjective.Flag.SPECIFIC)
-                    .withSelector(sbuilder.build())
-                    .nextStep(portNextObjId)
-                    .withPriority(100)
-                    .fromApp(srManager.appId)
-                    .makePermanent();
-
-            ObjectiveContext context = new DefaultObjectiveContext(
-                    (objective) -> log.debug("Brigding rule for {}/{} {}", hostMac, vlanId,
-                                             install ? "populated" : "revoked"),
-                    (objective, error) -> log.warn("Failed to {} bridging rule for {}/{}: {}",
-                                                   install ? "populate" : "revoke", hostMac, vlanId, error));
-            flowObjectiveService.forward(deviceId, install ? fob.add(context) : fob.remove(context));
-        } else {
-            log.warn("Failed to retrieve next objective for {}/{}", hostMac, vlanId);
-        }
-    }
 
     /**
      * Update forwarding objective for unicast bridging and unicast routing.
@@ -618,11 +465,11 @@
             // Check whether the host vlan is valid for new interface configuration
             if ((!popVlan && hostVlanId.equals(vlanId)) ||
                     (popVlan && hostVlanId.equals(VlanId.NONE))) {
-                updateBridgingRule(deviceId, portNum, mac, vlanId, popVlan, install);
+                srManager.defaultRoutingHandler.updateBridging(deviceId, portNum, mac, vlanId, popVlan, install);
                 // Update Forwarding objective and corresponding simple Next objective
                 // for each host and IP address connected to given port
                 host.ipAddresses().forEach(ipAddress ->
-                    srManager.routingRulePopulator.updateFwdObj(deviceId, portNum, ipAddress.toIpPrefix(),
+                    srManager.defaultRoutingHandler.updateFwdObj(deviceId, portNum, ipAddress.toIpPrefix(),
                                                                 mac, vlanId, popVlan, install)
                 );
             }
@@ -649,10 +496,10 @@
             host.ipAddresses().forEach(hostIpAddress -> {
                 ipPrefixSet.forEach(ipPrefix -> {
                     if (install && ipPrefix.contains(hostIpAddress)) {
-                            srManager.routingRulePopulator.populateRoute(cp.deviceId(), hostIpAddress.toIpPrefix(),
+                            srManager.defaultRoutingHandler.populateRoute(cp.deviceId(), hostIpAddress.toIpPrefix(),
                                                                          host.mac(), host.vlan(), cp.port());
                     } else if (!install && ipPrefix.contains(hostIpAddress)) {
-                            srManager.routingRulePopulator.revokeRoute(cp.deviceId(), hostIpAddress.toIpPrefix(),
+                            srManager.defaultRoutingHandler.revokeRoute(cp.deviceId(), hostIpAddress.toIpPrefix(),
                                                                        host.mac(), host.vlan(), cp.port());
                     }
                 });
diff --git a/app/src/main/java/org/onosproject/segmentrouting/RouteHandler.java b/app/src/main/java/org/onosproject/segmentrouting/RouteHandler.java
index a8de070..4b95bf3 100644
--- a/app/src/main/java/org/onosproject/segmentrouting/RouteHandler.java
+++ b/app/src/main/java/org/onosproject/segmentrouting/RouteHandler.java
@@ -252,8 +252,7 @@
             Set<HostLocation> newLocations = event.subject().locations();
 
             // For each old location
-            Sets.difference(prevLocations, newLocations).stream().filter(srManager::isMasterOf)
-                    .forEach(prevLocation -> {
+            Sets.difference(prevLocations, newLocations).forEach(prevLocation -> {
                 // Redirect the flows to pair link if configured
                 // Note: Do not continue removing any rule
                 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(prevLocation.deviceId());
@@ -276,8 +275,7 @@
             });
 
             // For each new location, add all new IPs.
-            Sets.difference(newLocations, prevLocations).stream().filter(srManager::isMasterOf)
-                    .forEach(newLocation -> {
+            Sets.difference(newLocations, prevLocations).forEach(newLocation -> {
                 log.debug("HostMoved. populateRoute {}, {}, {}, {}", newLocation, prefix, hostMac, hostVlanId);
                 srManager.defaultRoutingHandler.populateRoute(newLocation.deviceId(), prefix,
                         hostMac, hostVlanId, newLocation.port());
diff --git a/app/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java b/app/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
index 06f7ef0..bb9378e 100644
--- a/app/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
+++ b/app/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java
@@ -117,8 +117,182 @@
     }
 
     /**
-     * Populates IP rules for a route that has direct connection to the
-     * switch.
+     * Populate a bridging rule on given deviceId that matches given mac, given vlan and
+     * output to given port.
+     *
+     * @param deviceId device ID
+     * @param port port
+     * @param mac mac address
+     * @param vlanId VLAN ID
+     */
+    void populateBridging(DeviceId deviceId, PortNumber port, MacAddress mac, VlanId vlanId) {
+        ForwardingObjective.Builder fob = bridgingFwdObjBuilder(deviceId, mac, vlanId, port, false);
+        if (fob == null) {
+            log.warn("Fail to build fwd obj for host {}/{}. Abort.", mac, vlanId);
+            return;
+        }
+
+        ObjectiveContext context = new DefaultObjectiveContext(
+                (objective) -> log.debug("Brigding rule for {}/{} revoked", mac, vlanId),
+                (objective, error) -> log.warn("Failed to revoke bridging rule for {}/{}: {}", mac, vlanId, error));
+        srManager.flowObjectiveService.forward(deviceId, fob.add(context));
+    }
+
+    /**
+     * Revoke a bridging rule on given deviceId that matches given mac, given vlan and
+     * output to given port.
+     *
+     * @param deviceId device ID
+     * @param port port
+     * @param mac mac address
+     * @param vlanId VLAN ID
+     */
+    void revokeBridging(DeviceId deviceId, PortNumber port, MacAddress mac, VlanId vlanId) {
+        ForwardingObjective.Builder fob = bridgingFwdObjBuilder(deviceId, mac, vlanId, port, true);
+        if (fob == null) {
+            log.warn("Fail to build fwd obj for host {}/{}. Abort.", mac, vlanId);
+            return;
+        }
+
+        ObjectiveContext context = new DefaultObjectiveContext(
+                (objective) -> log.debug("Brigding rule for {}/{} populated", mac, vlanId),
+                (objective, error) -> log.warn("Failed to populate bridging rule for {}/{}: {}", mac, vlanId, error));
+        srManager.flowObjectiveService.forward(deviceId, fob.remove(context));
+    }
+
+    /**
+     * Generates a forwarding objective builder for bridging rules.
+     * <p>
+     * The forwarding objective bridges packets destined to a given MAC to
+     * given port on given device.
+     *
+     * @param deviceId Device that host attaches to
+     * @param mac MAC address of the host
+     * @param hostVlanId VLAN ID of the host
+     * @param outport Port that host attaches to
+     * @param revoke true if forwarding objective is meant to revoke forwarding rule
+     * @return Forwarding objective builder
+     */
+    private ForwardingObjective.Builder bridgingFwdObjBuilder(
+            DeviceId deviceId, MacAddress mac, VlanId hostVlanId, PortNumber outport, boolean revoke) {
+        ConnectPoint connectPoint = new ConnectPoint(deviceId, outport);
+        VlanId untaggedVlan = srManager.getUntaggedVlanId(connectPoint);
+        Set<VlanId> taggedVlans = srManager.getTaggedVlanId(connectPoint);
+        VlanId nativeVlan = srManager.getNativeVlanId(connectPoint);
+
+        // Create host selector
+        TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
+        sbuilder.matchEthDst(mac);
+
+        // Create host treatment
+        TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
+        tbuilder.immediate().setOutput(outport);
+
+        // Create host meta
+        TrafficSelector.Builder mbuilder = DefaultTrafficSelector.builder();
+
+        // Adjust the selector, treatment and meta according to VLAN configuration
+        if (taggedVlans.contains(hostVlanId)) {
+            sbuilder.matchVlanId(hostVlanId);
+            mbuilder.matchVlanId(hostVlanId);
+        } else if (hostVlanId.equals(VlanId.NONE)) {
+            if (untaggedVlan != null) {
+                sbuilder.matchVlanId(untaggedVlan);
+                mbuilder.matchVlanId(untaggedVlan);
+                tbuilder.immediate().popVlan();
+            } else if (nativeVlan != null) {
+                sbuilder.matchVlanId(nativeVlan);
+                mbuilder.matchVlanId(nativeVlan);
+                tbuilder.immediate().popVlan();
+            } else {
+                log.warn("Untagged host {}/{} is not allowed on {} without untagged or native" +
+                        "vlan config", mac, hostVlanId, connectPoint);
+                return null;
+            }
+        } else {
+            log.warn("Tagged host {}/{} is not allowed on {} without VLAN listed in tagged vlan",
+                    mac, hostVlanId, connectPoint);
+            return null;
+        }
+
+        // All forwarding is via Groups. Drivers can re-purpose to flow-actions if needed.
+        // 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,
+                tbuilder.build(), mbuilder.build(), !revoke);
+        if (portNextObjId == -1) {
+            // Warning log will come from getPortNextObjective method
+            return null;
+        }
+
+        return DefaultForwardingObjective.builder()
+                .withFlag(ForwardingObjective.Flag.SPECIFIC)
+                .withSelector(sbuilder.build())
+                .nextStep(portNextObjId)
+                .withPriority(100)
+                .fromApp(srManager.appId)
+                .makePermanent();
+    }
+
+    /**
+     * Populate or revoke a bridging rule on given deviceId that matches given vlanId,
+     * and hostMAC connected to given port, and output to given port only when
+     * vlan information is valid.
+     *
+     * @param deviceId device ID that host attaches to
+     * @param portNum port number that host attaches to
+     * @param hostMac mac address of the host connected to the switch port
+     * @param vlanId Vlan ID configured on the switch port
+     * @param popVlan true to pop Vlan tag at TrafficTreatment, false otherwise
+     * @param install true to populate the objective, false to revoke
+     */
+    // TODO Refactor. There are a lot of duplications between this method, populateBridging,
+    //      revokeBridging and bridgingFwdObjBuilder.
+    void updateBridging(DeviceId deviceId, PortNumber portNum, MacAddress hostMac,
+                        VlanId vlanId, boolean popVlan, boolean install) {
+        // Create host selector
+        TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
+        sbuilder.matchEthDst(hostMac);
+
+        // Create host meta
+        TrafficSelector.Builder mbuilder = DefaultTrafficSelector.builder();
+
+        sbuilder.matchVlanId(vlanId);
+        mbuilder.matchVlanId(vlanId);
+
+        // Create host treatment
+        TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
+        tbuilder.immediate().setOutput(portNum);
+
+        if (popVlan) {
+            tbuilder.immediate().popVlan();
+        }
+
+        int portNextObjId = srManager.getPortNextObjectiveId(deviceId, portNum,
+                tbuilder.build(), mbuilder.build(), install);
+        if (portNextObjId != -1) {
+            ForwardingObjective.Builder fob = DefaultForwardingObjective.builder()
+                    .withFlag(ForwardingObjective.Flag.SPECIFIC)
+                    .withSelector(sbuilder.build())
+                    .nextStep(portNextObjId)
+                    .withPriority(100)
+                    .fromApp(srManager.appId)
+                    .makePermanent();
+
+            ObjectiveContext context = new DefaultObjectiveContext(
+                    (objective) -> log.debug("Brigding rule for {}/{} {}", hostMac, vlanId,
+                            install ? "populated" : "revoked"),
+                    (objective, error) -> log.warn("Failed to {} bridging rule for {}/{}: {}",
+                            install ? "populate" : "revoke", hostMac, vlanId, error));
+            srManager.flowObjectiveService.forward(deviceId, install ? fob.add(context) : fob.remove(context));
+        } else {
+            log.warn("Failed to retrieve next objective for {}/{}", hostMac, vlanId);
+        }
+    }
+
+    /**
+     * Populates IP rules for a route that has direct connection to the switch.
+     * This method should not be invoked directly without going through DefaultRoutingHandler.
      *
      * @param deviceId device ID of the device that next hop attaches to
      * @param prefix IP prefix of the route
@@ -157,6 +331,7 @@
 
     /**
      * Removes IP rules for a route when the next hop is gone.
+     * This method should not be invoked directly without going through DefaultRoutingHandler.
      *
      * @param deviceId device ID of the device that next hop attaches to
      * @param prefix IP prefix of the route
@@ -293,14 +468,15 @@
     }
 
     /**
-     * Revokes IP flow rules for the subnets in each edge switch.
+     * Revokes IP flow rules for the subnets from given device.
      *
+     * @param targetSw target switch from which the subnets need to be removed
      * @param subnets subnet being removed
      * @return true if all rules are removed successfully, false otherwise
      */
-    boolean revokeIpRuleForSubnet(Set<IpPrefix> subnets) {
+    boolean revokeIpRuleForSubnet(DeviceId targetSw, Set<IpPrefix> subnets) {
         for (IpPrefix subnet : subnets) {
-            if (!revokeIpRuleForRouter(subnet)) {
+            if (!revokeIpRuleForRouter(targetSw, subnet)) {
                 return false;
             }
         }
@@ -419,12 +595,13 @@
     }
 
     /**
-     * Revokes IP flow rules for the router IP address.
+     * Revokes IP flow rules for the router IP address from given device.
      *
+     * @param targetSw target switch from which the ipPrefix need to be removed
      * @param ipPrefix the IP address of the destination router
      * @return true if all rules are removed successfully, false otherwise
      */
-    private boolean revokeIpRuleForRouter(IpPrefix ipPrefix) {
+    private boolean revokeIpRuleForRouter(DeviceId targetSw, IpPrefix ipPrefix) {
         TrafficSelector.Builder sbuilder = buildIpSelectorFromIpPrefix(ipPrefix);
         TrafficSelector selector = sbuilder.build();
         TrafficTreatment dummyTreatment = DefaultTrafficTreatment.builder().build();
@@ -438,17 +615,11 @@
                 .withPriority(getPriorityFromPrefix(ipPrefix))
                 .withFlag(ForwardingObjective.Flag.SPECIFIC);
 
-        srManager.deviceService.getAvailableDevices().forEach(device -> {
-            if (srManager.mastershipService.isLocalMaster(device.id())) {
-                ObjectiveContext context = new DefaultObjectiveContext(
-                        (objective) -> log.debug("IP rule for router {} revoked from {}", ipPrefix, device.id()),
-                        (objective, error) -> log.warn("Failed to revoke IP rule for router {} from {}: {}",
-                                ipPrefix, device.id(), error));
-                srManager.flowObjectiveService.forward(device.id(), fwdBuilder.remove(context));
-            } else {
-                log.debug("Not the master of {}. Abort route {} removal", device.id(), ipPrefix);
-            }
-        });
+        ObjectiveContext context = new DefaultObjectiveContext(
+                (objective) -> log.debug("IP rule for router {} revoked from {}", ipPrefix, targetSw),
+                (objective, error) -> log.warn("Failed to revoke IP rule for router {} from {}: {}",
+                        ipPrefix, targetSw, error));
+        srManager.flowObjectiveService.forward(targetSw, fwdBuilder.remove(context));
 
         return true;
     }
diff --git a/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
index fe01fd3..c17df68 100644
--- a/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
+++ b/app/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
@@ -1215,7 +1215,6 @@
 
         if (mastershipService.isLocalMaster(deviceId)) {
             defaultRoutingHandler.populatePortAddressingRules(deviceId);
-            hostHandler.init(deviceId);
             xConnectHandler.init(deviceId);
             DefaultGroupHandler groupHandler = groupHandlerMap.get(deviceId);
             groupHandler.createGroupsFromVlanConfig();
@@ -1223,6 +1222,7 @@
         }
 
         appCfgHandler.init(deviceId);
+        hostHandler.init(deviceId);
         routeHandler.init(deviceId);
     }
 
@@ -1556,12 +1556,6 @@
             DeviceId deviceId = intf.connectPoint().deviceId();
             PortNumber portNum = intf.connectPoint().port();
 
-            if (!mastershipService.isLocalMaster(deviceId)) {
-                log.debug("CONFIG_UPDATED event for interfaces should be " +
-                                  "handled by master node for device {}", deviceId);
-                return;
-            }
-
             removeSubnetConfig(prevIntf.connectPoint(),
                                Sets.difference(new HashSet<>(prevIntf.ipAddressesList()),
                                                new HashSet<>(intf.ipAddressesList())));
diff --git a/app/src/test/java/org/onosproject/segmentrouting/DefaultRoutingHandlerTest.java b/app/src/test/java/org/onosproject/segmentrouting/DefaultRoutingHandlerTest.java
new file mode 100644
index 0000000..05abcfc
--- /dev/null
+++ b/app/src/test/java/org/onosproject/segmentrouting/DefaultRoutingHandlerTest.java
@@ -0,0 +1,339 @@
+/*
+ * 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;
+
+import com.google.common.collect.Sets;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.DefaultControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.segmentrouting.config.DeviceConfiguration;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.TestConsistentMap;
+
+import java.util.Optional;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reset;
+import static org.junit.Assert.*;
+
+public class DefaultRoutingHandlerTest {
+    private SegmentRoutingManager srManager;
+    private DefaultRoutingHandler dfh;
+
+    private static final DeviceId DEV1A = DeviceId.deviceId("of:1a");
+    private static final DeviceId DEV1B = DeviceId.deviceId("of:1b");
+    private static final DeviceId DEV2 = DeviceId.deviceId("of:2");
+
+    private static final NodeId NODE1 = NodeId.nodeId("192.168.1.1");
+    private static final NodeId NODE2 = NodeId.nodeId("192.168.1.2");
+    private static final NodeId NODE3 = NodeId.nodeId("192.168.1.3");
+    private static final IpAddress IP1 = IpAddress.valueOf("192.168.1.1");
+    private static final IpAddress IP2 = IpAddress.valueOf("192.168.1.2");
+    private static final IpAddress IP3 = IpAddress.valueOf("192.168.1.3");
+
+    @Before
+    public void setUp() {
+        srManager = createMock(SegmentRoutingManager.class);
+        srManager.storageService = createMock(StorageService.class);
+        expect(srManager.storageService.consistentMapBuilder()).andReturn(new TestConsistentMap.Builder<>()).anyTimes();
+        replay(srManager.storageService);
+        srManager.routingRulePopulator = createMock(RoutingRulePopulator.class);
+        srManager.deviceService = createMock(DeviceService.class);
+        srManager.deviceConfiguration = createMock(DeviceConfiguration.class);
+        srManager.mastershipService = createMock(MastershipService.class);
+        srManager.clusterService = createMock(ClusterService.class);
+        dfh = new DefaultRoutingHandler(srManager);
+    }
+
+    // Node 1 is the master of switch 1A, 1B, and 2
+    @Test
+    public void testShouldHandleRoutingCase1() {
+        expect(srManager.mastershipService.getMasterFor(DEV1A)).andReturn(NODE1).anyTimes();
+        expect(srManager.mastershipService.getMasterFor(DEV1B)).andReturn(NODE1).anyTimes();
+        expect(srManager.mastershipService.getMasterFor(DEV2)).andReturn(NODE1).anyTimes();
+        replay(srManager.mastershipService);
+
+        expect(srManager.getPairDeviceId(DEV1A)).andReturn(Optional.of(DEV1B)).anyTimes();
+        expect(srManager.getPairDeviceId(DEV1B)).andReturn(Optional.of(DEV1A)).anyTimes();
+        expect(srManager.getPairDeviceId(DEV2)).andReturn(Optional.empty()).anyTimes();
+        replay(srManager);
+
+        // Node 1 should program every device
+        expect(srManager.clusterService.getLocalNode()).andReturn(new DefaultControllerNode(NODE1, IP1)).anyTimes();
+        replay(srManager.clusterService);
+        assertTrue(dfh.shouldProgram(DEV1A));
+        assertTrue(dfh.shouldProgram(DEV1B));
+        assertTrue(dfh.shouldProgram(DEV2));
+
+        reset(srManager.clusterService);
+
+        // Node 2 should program no device
+        expect(srManager.clusterService.getLocalNode()).andReturn(new DefaultControllerNode(NODE2, IP2)).anyTimes();
+        replay(srManager.clusterService);
+        assertFalse(dfh.shouldProgram(DEV1A));
+        assertFalse(dfh.shouldProgram(DEV1B));
+        assertFalse(dfh.shouldProgram(DEV2));
+
+        reset(srManager.clusterService);
+
+        // Node 3 should program no device
+        expect(srManager.clusterService.getLocalNode()).andReturn(new DefaultControllerNode(NODE3, IP3)).anyTimes();
+        replay(srManager.clusterService);
+        assertFalse(dfh.shouldProgram(DEV1A));
+        assertFalse(dfh.shouldProgram(DEV1B));
+        assertFalse(dfh.shouldProgram(DEV2));
+    }
+
+    // Node 1 is the master of switch 1A, 1B
+    // Node 2 is the master of switch 2
+    @Test
+    public void testShouldHandleRoutingCase2() {
+        expect(srManager.mastershipService.getMasterFor(DEV1A)).andReturn(NODE1).anyTimes();
+        expect(srManager.mastershipService.getMasterFor(DEV1B)).andReturn(NODE1).anyTimes();
+        expect(srManager.mastershipService.getMasterFor(DEV2)).andReturn(NODE2).anyTimes();
+        replay(srManager.mastershipService);
+
+        expect(srManager.getPairDeviceId(DEV1A)).andReturn(Optional.of(DEV1B)).anyTimes();
+        expect(srManager.getPairDeviceId(DEV1B)).andReturn(Optional.of(DEV1A)).anyTimes();
+        expect(srManager.getPairDeviceId(DEV2)).andReturn(Optional.empty()).anyTimes();
+        replay(srManager);
+
+        // Node 1 should program 1A, 1B
+        expect(srManager.clusterService.getLocalNode()).andReturn(new DefaultControllerNode(NODE1, IP1)).anyTimes();
+        replay(srManager.clusterService);
+        assertTrue(dfh.shouldProgram(DEV1A));
+        assertTrue(dfh.shouldProgram(DEV1B));
+        assertFalse(dfh.shouldProgram(DEV2));
+
+        reset(srManager.clusterService);
+
+        // Node 2 should program 2
+        expect(srManager.clusterService.getLocalNode()).andReturn(new DefaultControllerNode(NODE2, IP2)).anyTimes();
+        replay(srManager.clusterService);
+        assertFalse(dfh.shouldProgram(DEV1A));
+        assertFalse(dfh.shouldProgram(DEV1B));
+        assertTrue(dfh.shouldProgram(DEV2));
+
+        reset(srManager.clusterService);
+
+        // Node 3 should program no device
+        expect(srManager.clusterService.getLocalNode()).andReturn(new DefaultControllerNode(NODE3, IP3)).anyTimes();
+        replay(srManager.clusterService);
+        assertFalse(dfh.shouldProgram(DEV1A));
+        assertFalse(dfh.shouldProgram(DEV1B));
+        assertFalse(dfh.shouldProgram(DEV2));
+    }
+
+    // Node 1 is the master of switch 1A
+    // Node 2 is the master of switch 1B
+    // Node 3 is the master of switch 2
+    @Test
+    public void testShouldHandleRoutingCase3() {
+        expect(srManager.mastershipService.getMasterFor(DEV1A)).andReturn(NODE1).anyTimes();
+        expect(srManager.mastershipService.getMasterFor(DEV1B)).andReturn(NODE2).anyTimes();
+        expect(srManager.mastershipService.getMasterFor(DEV2)).andReturn(NODE3).anyTimes();
+        replay(srManager.mastershipService);
+
+        expect(srManager.getPairDeviceId(DEV1A)).andReturn(Optional.of(DEV1B)).anyTimes();
+        expect(srManager.getPairDeviceId(DEV1B)).andReturn(Optional.of(DEV1A)).anyTimes();
+        expect(srManager.getPairDeviceId(DEV2)).andReturn(Optional.empty()).anyTimes();
+        replay(srManager);
+
+        // Node 1 should program 1A, 1B
+        expect(srManager.clusterService.getLocalNode()).andReturn(new DefaultControllerNode(NODE1, IP1)).anyTimes();
+        replay(srManager.clusterService);
+        assertTrue(dfh.shouldProgram(DEV1A));
+        assertTrue(dfh.shouldProgram(DEV1B));
+        assertFalse(dfh.shouldProgram(DEV2));
+
+        reset(srManager.clusterService);
+
+        // Node 2 should program no device
+        expect(srManager.clusterService.getLocalNode()).andReturn(new DefaultControllerNode(NODE2, IP2)).anyTimes();
+        replay(srManager.clusterService);
+        assertFalse(dfh.shouldProgram(DEV1A));
+        assertFalse(dfh.shouldProgram(DEV1B));
+        assertFalse(dfh.shouldProgram(DEV2));
+
+        reset(srManager.clusterService);
+
+        // Node 3 should program 2
+        expect(srManager.clusterService.getLocalNode()).andReturn(new DefaultControllerNode(NODE3, IP3)).anyTimes();
+        replay(srManager.clusterService);
+        assertFalse(dfh.shouldProgram(DEV1A));
+        assertFalse(dfh.shouldProgram(DEV1B));
+        assertTrue(dfh.shouldProgram(DEV2));
+    }
+
+    // Node 3 is the master of switch 1A, 1B, 2
+    // Later on, node 1 becomes the master of 1A; Node 2 becomes the master of 1B.
+    @Test
+    public void testShouldHandleRoutingCase4() {
+        expect(srManager.mastershipService.getMasterFor(DEV1A)).andReturn(NODE3).anyTimes();
+        expect(srManager.mastershipService.getMasterFor(DEV1B)).andReturn(NODE3).anyTimes();
+        expect(srManager.mastershipService.getMasterFor(DEV2)).andReturn(NODE3).anyTimes();
+        replay(srManager.mastershipService);
+
+        expect(srManager.getPairDeviceId(DEV1A)).andReturn(Optional.of(DEV1B)).anyTimes();
+        expect(srManager.getPairDeviceId(DEV1B)).andReturn(Optional.of(DEV1A)).anyTimes();
+        expect(srManager.getPairDeviceId(DEV2)).andReturn(Optional.empty()).anyTimes();
+        replay(srManager);
+
+        // Node 1 should program no device
+        expect(srManager.clusterService.getLocalNode()).andReturn(new DefaultControllerNode(NODE1, IP1)).anyTimes();
+        replay(srManager.clusterService);
+        assertFalse(dfh.shouldProgram(DEV1A));
+        assertFalse(dfh.shouldProgram(DEV1B));
+        assertFalse(dfh.shouldProgram(DEV2));
+
+        reset(srManager.clusterService);
+
+        // Node 2 should program no device
+        expect(srManager.clusterService.getLocalNode()).andReturn(new DefaultControllerNode(NODE2, IP2)).anyTimes();
+        replay(srManager.clusterService);
+        assertFalse(dfh.shouldProgram(DEV1A));
+        assertFalse(dfh.shouldProgram(DEV1B));
+        assertFalse(dfh.shouldProgram(DEV2));
+
+        reset(srManager.clusterService);
+
+        // Node 3 should program 1A, 1B and 2
+        expect(srManager.clusterService.getLocalNode()).andReturn(new DefaultControllerNode(NODE3, IP3)).anyTimes();
+        replay(srManager.clusterService);
+        assertTrue(dfh.shouldProgram(DEV1A));
+        assertTrue(dfh.shouldProgram(DEV1B));
+        assertTrue(dfh.shouldProgram(DEV2));
+
+        // Mastership of switch 1A moves to Node 1
+        reset(srManager.mastershipService);
+        expect(srManager.mastershipService.getMasterFor(DEV1A)).andReturn(NODE1).anyTimes();
+        expect(srManager.mastershipService.getMasterFor(DEV1B)).andReturn(NODE2).anyTimes();
+        expect(srManager.mastershipService.getMasterFor(DEV2)).andReturn(NODE3).anyTimes();
+        replay(srManager.mastershipService);
+
+        reset(srManager.clusterService);
+
+        // Node 1 should program 1A, 1B
+        expect(srManager.clusterService.getLocalNode()).andReturn(new DefaultControllerNode(NODE1, IP1)).anyTimes();
+        replay(srManager.clusterService);
+        assertTrue(dfh.shouldProgram(DEV1A));
+        assertTrue(dfh.shouldProgram(DEV1B));
+        assertFalse(dfh.shouldProgram(DEV2));
+
+        reset(srManager.clusterService);
+
+        // Node 2 should program no device
+        expect(srManager.clusterService.getLocalNode()).andReturn(new DefaultControllerNode(NODE2, IP2)).anyTimes();
+        replay(srManager.clusterService);
+        assertFalse(dfh.shouldProgram(DEV1A));
+        assertFalse(dfh.shouldProgram(DEV1B));
+        assertFalse(dfh.shouldProgram(DEV2));
+
+        reset(srManager.clusterService);
+
+        // Node 3 should program 2
+        expect(srManager.clusterService.getLocalNode()).andReturn(new DefaultControllerNode(NODE3, IP3)).anyTimes();
+        replay(srManager.clusterService);
+        assertFalse(dfh.shouldProgram(DEV1A));
+        assertFalse(dfh.shouldProgram(DEV1B));
+        assertTrue(dfh.shouldProgram(DEV2));
+    }
+
+    // Node 1 is the master of 1A. 1B has no master
+    // Node 2 becomes the master of 1B later
+    @Test
+    public void testShouldHandleRoutingCase5() {
+        expect(srManager.mastershipService.getMasterFor(DEV1A)).andReturn(NODE1).anyTimes();
+        expect(srManager.mastershipService.getMasterFor(DEV1B)).andReturn(null).anyTimes();
+        replay(srManager.mastershipService);
+
+        expect(srManager.getPairDeviceId(DEV1A)).andReturn(Optional.of(DEV1B)).anyTimes();
+        expect(srManager.getPairDeviceId(DEV1B)).andReturn(Optional.of(DEV1A)).anyTimes();
+        replay(srManager);
+
+        // Node 1 should program both 1A and 1B
+        expect(srManager.clusterService.getLocalNode()).andReturn(new DefaultControllerNode(NODE1, IP1)).anyTimes();
+        replay(srManager.clusterService);
+        assertTrue(dfh.shouldProgram(DEV1A));
+        assertTrue(dfh.shouldProgram(DEV1B));
+
+        reset(srManager.clusterService);
+
+        // Node 2 should program no device
+        expect(srManager.clusterService.getLocalNode()).andReturn(new DefaultControllerNode(NODE2, IP2)).anyTimes();
+        replay(srManager.clusterService);
+        assertFalse(dfh.shouldProgram(DEV1A));
+        assertFalse(dfh.shouldProgram(DEV1B));
+
+        // Mastership of switch 1B moves to Node 2
+        reset(srManager.mastershipService);
+        expect(srManager.mastershipService.getMasterFor(DEV1A)).andReturn(NODE1).anyTimes();
+        expect(srManager.mastershipService.getMasterFor(DEV1B)).andReturn(NODE2).anyTimes();
+        replay(srManager.mastershipService);
+
+        reset(srManager.clusterService);
+
+        // Node 1 should program 1A, 1B
+        expect(srManager.clusterService.getLocalNode()).andReturn(new DefaultControllerNode(NODE1, IP1)).anyTimes();
+        replay(srManager.clusterService);
+        assertTrue(dfh.shouldProgram(DEV1A));
+        assertTrue(dfh.shouldProgram(DEV1B));
+
+        reset(srManager.clusterService);
+
+        // Node 2 should program no device
+        expect(srManager.clusterService.getLocalNode()).andReturn(new DefaultControllerNode(NODE2, IP2)).anyTimes();
+        replay(srManager.clusterService);
+        assertFalse(dfh.shouldProgram(DEV1A));
+        assertFalse(dfh.shouldProgram(DEV1B));
+    }
+
+    // Neither 1A or 1B has master
+    @Test
+    public void testShouldHandleRoutingCase6() {
+        expect(srManager.mastershipService.getMasterFor(DEV1A)).andReturn(null).anyTimes();
+        expect(srManager.mastershipService.getMasterFor(DEV1B)).andReturn(null).anyTimes();
+        replay(srManager.mastershipService);
+
+        expect(srManager.getPairDeviceId(DEV1A)).andReturn(Optional.of(DEV1B)).anyTimes();
+        expect(srManager.getPairDeviceId(DEV1B)).andReturn(Optional.of(DEV1A)).anyTimes();
+        replay(srManager);
+
+        // Node 1 should program no device
+        expect(srManager.clusterService.getLocalNode()).andReturn(new DefaultControllerNode(NODE1, IP1)).anyTimes();
+        replay(srManager.clusterService);
+        assertFalse(dfh.shouldProgram(DEV1A));
+        assertFalse(dfh.shouldProgram(DEV1B));
+
+        reset(srManager.clusterService);
+
+        // Node 2 should program no device
+        expect(srManager.clusterService.getLocalNode()).andReturn(new DefaultControllerNode(NODE2, IP2)).anyTimes();
+        replay(srManager.clusterService);
+        assertFalse(dfh.shouldProgram(DEV1A));
+        assertFalse(dfh.shouldProgram(DEV1B));
+
+        assertFalse(dfh.shouldProgram.containsKey(Sets.newHashSet(DEV1A, DEV1B)));
+    }
+}
\ No newline at end of file
diff --git a/app/src/test/java/org/onosproject/segmentrouting/HostHandlerTest.java b/app/src/test/java/org/onosproject/segmentrouting/HostHandlerTest.java
index b6ec070..3605727 100644
--- a/app/src/test/java/org/onosproject/segmentrouting/HostHandlerTest.java
+++ b/app/src/test/java/org/onosproject/segmentrouting/HostHandlerTest.java
@@ -44,10 +44,15 @@
 import org.onosproject.net.provider.ProviderId;
 import org.onosproject.segmentrouting.config.DeviceConfiguration;
 import org.onosproject.segmentrouting.config.SegmentRoutingDeviceConfig;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.TestConsistentMap;
 
 import java.util.Map;
 import java.util.Set;
 
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
 import static org.junit.Assert.*;
 
 /**r
@@ -201,6 +206,9 @@
 
         // Initialize Segment Routing Manager
         SegmentRoutingManager srManager = new MockSegmentRoutingManager(NEXT_TABLE);
+        srManager.storageService = createMock(StorageService.class);
+        expect(srManager.storageService.consistentMapBuilder()).andReturn(new TestConsistentMap.Builder<>()).anyTimes();
+        replay(srManager.storageService);
         srManager.cfgService = new NetworkConfigRegistryAdapter();
         srManager.deviceConfiguration = new DeviceConfiguration(srManager);
         srManager.flowObjectiveService = new MockFlowObjectiveService(BRIDGING_TABLE, NEXT_TABLE);
@@ -846,10 +854,4 @@
         assertNotNull(BRIDGING_TABLE.get(new MockBridgingTableKey(DEV1, HOST_MAC, INTF_VLAN_UNTAGGED)));
         assertNotNull(BRIDGING_TABLE.get(new MockBridgingTableKey(DEV2, HOST_MAC, INTF_VLAN_UNTAGGED)));
     }
-
-    @Test
-    public void testBridgingFwdObjBuilder() throws Exception {
-        assertNotNull(hostHandler.bridgingFwdObjBuilder(DEV2, HOST_MAC, HOST_VLAN_UNTAGGED, P1, false));
-        assertNull(hostHandler.bridgingFwdObjBuilder(DEV2, HOST_MAC, HOST_VLAN_UNTAGGED, P3, false));
-    }
 }
diff --git a/app/src/test/java/org/onosproject/segmentrouting/MockDefaultRoutingHandler.java b/app/src/test/java/org/onosproject/segmentrouting/MockDefaultRoutingHandler.java
index d31be2f..0002948 100644
--- a/app/src/test/java/org/onosproject/segmentrouting/MockDefaultRoutingHandler.java
+++ b/app/src/test/java/org/onosproject/segmentrouting/MockDefaultRoutingHandler.java
@@ -18,6 +18,7 @@
 
 import org.onlab.packet.IpPrefix;
 import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
 
 import java.util.Map;
 import java.util.Set;
@@ -62,4 +63,9 @@
         routingTable.entrySet().removeIf(e -> subnets.contains(e.getKey().ipPrefix));
         return true;
     }
+
+    @Override
+    protected boolean shouldProgram(DeviceId deviceId) {
+        return true;
+    }
 }
\ No newline at end of file
diff --git a/app/src/test/java/org/onosproject/segmentrouting/RouteHandlerTest.java b/app/src/test/java/org/onosproject/segmentrouting/RouteHandlerTest.java
index 36a9e79..a7388d1 100644
--- a/app/src/test/java/org/onosproject/segmentrouting/RouteHandlerTest.java
+++ b/app/src/test/java/org/onosproject/segmentrouting/RouteHandlerTest.java
@@ -45,10 +45,13 @@
 import org.onosproject.routeservice.RouteEvent;
 import org.onosproject.segmentrouting.config.DeviceConfiguration;
 import org.onosproject.segmentrouting.config.SegmentRoutingDeviceConfig;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.TestConsistentMap;
 
 import java.util.Map;
 import java.util.Set;
 
+import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.reset;
 import static org.junit.Assert.*;
 import static org.easymock.EasyMock.createMock;
@@ -141,6 +144,9 @@
 
         // Initialize Segment Routing Manager
         srManager = new MockSegmentRoutingManager(NEXT_TABLE);
+        srManager.storageService = createMock(StorageService.class);
+        expect(srManager.storageService.consistentMapBuilder()).andReturn(new TestConsistentMap.Builder<>()).anyTimes();
+        replay(srManager.storageService);
         srManager.cfgService = new NetworkConfigRegistryAdapter();
         srManager.deviceConfiguration = createMock(DeviceConfiguration.class);
         srManager.flowObjectiveService = new MockFlowObjectiveService(BRIDGING_TABLE, NEXT_TABLE);