Dual-homing probing improvements

(1) Active probing mechanism in the following two scenarios
    (1-1) Probe all ports on the pair device within the same vlan (excluding the pair port) when the 1st location of a host is learnt
    (1-2) Probe again when a device/port goes down and comes up again
    * Introduce HostLocationProvingService
        - DISCOVER mode: discover potential new locations
        - VERIFY mode: verify old locations
    * Can be enabled/disabled via component config
    * Improve HostHandlerTest to test the probing behavior

(2) Fix an issue that redirection flow doesn't get installed after device re-connects

(3) Temporarily fix a race condition in HostHandler by adding a little bit delay

Change-Id: I33d3fe94a6ca491a88b8e06f65bef11447ead0bf
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/HostHandler.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/HostHandler.java
index 6ac25f3..1379e79 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/HostHandler.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/HostHandler.java
@@ -35,6 +35,7 @@
 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;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -43,6 +44,9 @@
 
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 import static com.google.common.base.Preconditions.checkArgument;
@@ -51,8 +55,9 @@
  * Handles host-related events.
  */
 public class HostHandler {
-
     private static final Logger log = LoggerFactory.getLogger(HostHandler.class);
+    static final int HOST_MOVED_DELAY_MS = 1000;
+
     protected final SegmentRoutingManager srManager;
     private HostService hostService;
     private FlowObjectiveService flowObjectiveService;
@@ -71,7 +76,8 @@
     protected void init(DeviceId devId) {
         hostService.getHosts().forEach(host ->
             host.locations().stream()
-                    .filter(location -> location.deviceId().equals(devId))
+                    .filter(location -> location.deviceId().equals(devId) ||
+                            location.deviceId().equals(srManager.getPairDeviceId(devId).orElse(null)))
                     .forEach(location -> processHostAddedAtLocation(host, location))
         );
     }
@@ -114,6 +120,10 @@
                     processBridgingRule(pairDeviceId, pairRemotePort, hostMac, vlanId, false);
                     ips.forEach(ip -> processRoutingRule(pairDeviceId, pairRemotePort, hostMac, vlanId,
                                     ip, false));
+
+                    if (srManager.activeProbing) {
+                        probe(host, location, pairDeviceId, pairRemotePort);
+                    }
                 });
             }
         });
@@ -162,8 +172,23 @@
         Set<IpAddress> prevIps = event.prevSubject().ipAddresses();
         Set<HostLocation> newLocations = event.subject().locations();
         Set<IpAddress> newIps = event.subject().ipAddresses();
-        log.info("Host {}/{} is moved from {} to {}", hostMac, hostVlanId, prevLocations, newLocations);
 
+        // FIXME: Delay event handling a little bit to wait for the previous redirection flows to be completed
+        //        The permanent solution would be introducing CompletableFuture and wait for it
+        if (prevLocations.size() == 1 && newLocations.size() == 2) {
+            log.debug("Delay event handling when host {}/{} moves from 1 to 2 locations", hostMac, hostVlanId);
+            ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
+            executorService.schedule(() ->
+                    processHostMoved(hostMac, hostVlanId, prevLocations, prevIps, newLocations, newIps),
+                    HOST_MOVED_DELAY_MS, TimeUnit.MILLISECONDS);
+        } else {
+            processHostMoved(hostMac, hostVlanId, prevLocations, prevIps, newLocations, newIps);
+        }
+    }
+
+    private void processHostMoved(MacAddress hostMac, VlanId hostVlanId, Set<HostLocation> prevLocations,
+                                  Set<IpAddress> prevIps, Set<HostLocation> newLocations, Set<IpAddress> newIps) {
+        log.info("Host {}/{} is moved from {} to {}", hostMac, hostVlanId, prevLocations, newLocations);
         Set<DeviceId> newDeviceIds = newLocations.stream().map(HostLocation::deviceId)
                 .collect(Collectors.toSet());
 
@@ -254,11 +279,12 @@
     }
 
     void processHostUpdatedEvent(HostEvent event) {
-        MacAddress hostMac = event.subject().mac();
-        VlanId hostVlanId = event.subject().vlan();
-        Set<HostLocation> locations = event.subject().locations();
+        Host host = event.subject();
+        MacAddress hostMac = host.mac();
+        VlanId hostVlanId = host.vlan();
+        Set<HostLocation> locations = host.locations();
         Set<IpAddress> prevIps = event.prevSubject().ipAddresses();
-        Set<IpAddress> newIps = event.subject().ipAddresses();
+        Set<IpAddress> newIps = host.ipAddresses();
         log.info("Host {}/{} is updated", hostMac, hostVlanId);
 
         locations.stream().filter(srManager::isMasterOf).forEach(location -> {
@@ -273,25 +299,94 @@
         // 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
-        locations.forEach(location -> {
+        locations.forEach(location ->
             srManager.getPairDeviceId(location.deviceId()).ifPresent(pairDeviceId -> {
                 if (srManager.mastershipService.isLocalMaster(pairDeviceId) &&
                         locations.stream().noneMatch(l -> l.deviceId().equals(pairDeviceId))) {
+                    Set<IpAddress> ipsToAdd = Sets.difference(newIps, prevIps);
+                    Set<IpAddress> ipsToRemove = Sets.difference(prevIps, newIps);
+
                     srManager.getPairLocalPorts(pairDeviceId).ifPresent(pairRemotePort -> {
                         // 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);
 
-                        Sets.difference(prevIps, newIps).forEach(ip ->
+                        ipsToRemove.forEach(ip ->
                                 processRoutingRule(pairDeviceId, pairRemotePort, hostMac, vlanId, ip, true)
                         );
-                        Sets.difference(newIps, prevIps).forEach(ip ->
+                        ipsToAdd.forEach(ip ->
                                 processRoutingRule(pairDeviceId, pairRemotePort, hostMac, vlanId, ip, false)
                         );
+
+                        if (srManager.activeProbing) {
+                            probe(host, location, pairDeviceId, pairRemotePort);
+                        }
                     });
                 }
-            });
-        });
+            })
+        );
+    }
+
+    /**
+     * When a non-pair port comes up, probe each host on the pair device if
+     * (1) the host is tagged and the tagged vlan of current port contains host vlan; or
+     * (2) the host is untagged and the internal vlan is the same on the host port and current port.
+     *
+     * @param cp connect point
+     */
+    void processPortUp(ConnectPoint cp) {
+        if (cp.port().equals(srManager.getPairLocalPorts(cp.deviceId()).orElse(null))) {
+            return;
+        }
+        if (srManager.activeProbing) {
+            srManager.getPairDeviceId(cp.deviceId())
+                    .ifPresent(pairDeviceId -> srManager.hostService.getConnectedHosts(pairDeviceId).stream()
+                            .filter(host -> isHostInVlanOfPort(host, pairDeviceId, cp))
+                            .forEach(host -> srManager.probingService.probeHostLocation(host, cp, ProbeMode.DISCOVER))
+                    );
+        }
+    }
+
+    /**
+     * Checks if given host located on given device id matches VLAN config of current port.
+     *
+     * @param host host to check
+     * @param deviceId device id to check
+     * @param cp current connect point
+     * @return true if the host located at deviceId matches the VLAN config on cp
+     */
+    private boolean isHostInVlanOfPort(Host host, DeviceId deviceId, ConnectPoint cp) {
+        VlanId internalVlan = srManager.getInternalVlanId(cp);
+        Set<VlanId> taggedVlan = srManager.getTaggedVlanId(cp);
+
+        return taggedVlan.contains(host.vlan()) ||
+                (internalVlan != null && host.locations().stream()
+                        .filter(l -> l.deviceId().equals(deviceId))
+                        .map(srManager::getInternalVlanId)
+                        .anyMatch(internalVlan::equals));
+    }
+
+    /**
+     * Send a probe on all locations with the same VLAN on pair device, excluding pair port.
+     *
+     * @param host host to probe
+     * @param location newly discovered host location
+     * @param pairDeviceId pair device id
+     * @param pairRemotePort pair remote port
+     */
+    private void probe(Host host, ConnectPoint location, DeviceId pairDeviceId, PortNumber pairRemotePort) {
+        VlanId vlanToProbe = host.vlan().equals(VlanId.NONE) ?
+                srManager.getInternalVlanId(location) : host.vlan();
+        srManager.interfaceService.getInterfaces().stream()
+                .filter(i -> i.vlanTagged().contains(vlanToProbe) ||
+                        i.vlanUntagged().equals(vlanToProbe) ||
+                        i.vlanNative().equals(vlanToProbe))
+                .filter(i -> i.connectPoint().deviceId().equals(pairDeviceId))
+                .filter(i -> !i.connectPoint().port().equals(pairRemotePort))
+                .forEach(i -> {
+                    log.debug("Probing host {} on pair device {}", host.id(), i.connectPoint());
+                    srManager.probingService.probeHostLocation(host, i.connectPoint(), ProbeMode.DISCOVER);
+                });
     }
 
     /**
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
index d57a681..7a17d39 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java
@@ -15,6 +15,7 @@
  */
 package org.onosproject.segmentrouting;
 
+
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.collect.HashMultimap;
 import com.google.common.collect.ImmutableMap;
@@ -24,6 +25,8 @@
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Property;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
@@ -35,6 +38,7 @@
 import org.onlab.packet.IpPrefix;
 import org.onlab.packet.VlanId;
 import org.onlab.util.KryoNamespace;
+import org.onlab.util.Tools;
 import org.onosproject.cfg.ComponentConfigService;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
@@ -64,6 +68,7 @@
 import org.onosproject.net.flowobjective.FlowObjectiveService;
 import org.onosproject.net.host.HostEvent;
 import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostLocationProbingService;
 import org.onosproject.net.host.HostService;
 import org.onosproject.net.host.InterfaceIpAddress;
 import org.onosproject.net.intf.Interface;
@@ -106,10 +111,12 @@
 import org.onosproject.store.service.EventuallyConsistentMapBuilder;
 import org.onosproject.store.service.StorageService;
 import org.onosproject.store.service.WallClockTimestamp;
+import org.osgi.service.component.ComponentContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.util.Collections;
+import java.util.Dictionary;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -158,6 +165,9 @@
     HostService hostService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    HostLocationProbingService probingService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     DeviceService deviceService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
@@ -187,6 +197,10 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     public InterfaceService interfaceService;
 
+    @Property(name = "activeProbing", boolValue = true,
+            label = "Enable active probing to discover dual-homed hosts.")
+    boolean activeProbing = true;
+
     ArpHandler arpHandler = null;
     IcmpHandler icmpHandler = null;
     IpHandler ipHandler = null;
@@ -226,19 +240,19 @@
      * Per device next objective ID store with (device id + destination set) as key.
      * Used to keep track on MPLS group information.
      */
-    EventuallyConsistentMap<DestinationSetNextObjectiveStoreKey, NextNeighbors>
+    private EventuallyConsistentMap<DestinationSetNextObjectiveStoreKey, NextNeighbors>
             dsNextObjStore = null;
     /**
      * Per device next objective ID store with (device id + vlanid) as key.
      * Used to keep track on L2 flood group information.
      */
-    EventuallyConsistentMap<VlanNextObjectiveStoreKey, Integer>
+    private EventuallyConsistentMap<VlanNextObjectiveStoreKey, Integer>
             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.
      */
-    EventuallyConsistentMap<PortNextObjectiveStoreKey, Integer>
+    private EventuallyConsistentMap<PortNextObjectiveStoreKey, Integer>
             portNextObjStore = null;
 
     // Local store for all links seen and their present status, used for
@@ -323,7 +337,7 @@
     public static final VlanId INTERNAL_VLAN = VlanId.vlanId((short) 4094);
 
     @Activate
-    protected void activate() {
+    protected void activate(ComponentContext context) {
         appId = coreService.registerApplication(APP_NAME);
 
         log.debug("Creating EC map nsnextobjectivestore");
@@ -392,6 +406,8 @@
                                       "staleLinkAge", "15000");
         compCfgService.preSetProperty("org.onosproject.net.host.impl.HostManager",
                                       "allowDuplicateIps", "false");
+        compCfgService.registerProperties(getClass());
+        modified(context);
 
         processor = new InternalPacketProcessor();
         linkListener = new InternalLinkListener();
@@ -449,6 +465,7 @@
         cfgService.unregisterConfigFactory(xConnectConfigFactory);
         cfgService.unregisterConfigFactory(mcastConfigFactory);
         cfgService.unregisterConfigFactory(pwaasConfigFactory);
+        compCfgService.unregisterProperties(getClass(), false);
 
         hostService.removeListener(hostListener);
         packetService.removeProcessor(processor);
@@ -472,6 +489,22 @@
         log.info("Stopped");
     }
 
+    @Modified
+    private void modified(ComponentContext context) {
+        Dictionary<?, ?> properties = context.getProperties();
+        if (properties == null) {
+            return;
+        }
+
+        String strActiveProving = Tools.get(properties, "activeProbing");
+        boolean expectActiveProbing = Boolean.parseBoolean(strActiveProving);
+
+        if (expectActiveProbing != activeProbing) {
+            activeProbing = expectActiveProbing;
+            log.info("{} active probing", activeProbing ? "Enabling" : "Disabling");
+        }
+    }
+
     @Override
     public List<Tunnel> getTunnels() {
         return tunnelHandler.getTunnels();
@@ -922,7 +955,7 @@
      *         true if the seen-link is up;
      *         false if the seen-link is down
      */
-    Boolean isSeenLinkUp(Link link) {
+    private Boolean isSeenLinkUp(Link link) {
         return seenLinks.get(link);
     }
 
@@ -970,7 +1003,7 @@
      * @return true if another unidirectional link exists in the reverse direction,
      *              has been seen-before and is up
      */
-    public boolean isBidirectional(Link link) {
+    boolean isBidirectional(Link link) {
         Link reverseLink = linkService.getLink(link.dst(), link.src());
         if (reverseLink == null) {
             return false;
@@ -989,7 +1022,7 @@
      * @param link the infrastructure link being queried
      * @return true if link should be avoided
      */
-    public boolean avoidLink(Link link) {
+    boolean avoidLink(Link link) {
         // XXX currently only avoids all pair-links. In the future can be
         // extended to avoid any generic link
         DeviceId src = link.src().deviceId();
@@ -1012,12 +1045,9 @@
             return false;
         }
 
-        if (srcPort.equals(pairLocalPort) &&
+        return srcPort.equals(pairLocalPort) &&
                 link.dst().deviceId().equals(pairDev) &&
-                link.dst().port().equals(pairRemotePort)) {
-            return true;
-        }
-        return false;
+                link.dst().port().equals(pairRemotePort);
     }
 
     private class InternalPacketProcessor implements PacketProcessor {
@@ -1374,9 +1404,7 @@
     private void processDeviceRemoved(Device device) {
         dsNextObjStore.entrySet().stream()
                 .filter(entry -> entry.getKey().deviceId().equals(device.id()))
-                .forEach(entry -> {
-                    dsNextObjStore.remove(entry.getKey());
-                });
+                .forEach(entry -> dsNextObjStore.remove(entry.getKey()));
         vlanNextObjStore.entrySet().stream()
                 .filter(entry -> entry.getKey().deviceId().equals(device.id()))
                 .forEach(entry -> vlanNextObjStore.remove(entry.getKey()));
@@ -1456,6 +1484,7 @@
         if (portUp) {
             log.info("Device:EdgePort {}:{} is enabled in vlan: {}", device.id(),
                      port.number(), vlanId);
+            hostHandler.processPortUp(new ConnectPoint(device.id(), port.number()));
         } else {
             log.info("Device:EdgePort {}:{} is disabled in vlan: {}", device.id(),
                      port.number(), vlanId);
@@ -1509,7 +1538,7 @@
         /**
          * Reads network config and initializes related data structure accordingly.
          */
-        public void configureNetwork() {
+        void configureNetwork() {
             createOrUpdateDeviceConfiguration();
 
             arpHandler = new ArpHandler(srManager);
diff --git a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java
index b7d32a1..7ea430d 100644
--- a/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java
+++ b/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/DeviceConfiguration.java
@@ -501,6 +501,20 @@
     }
 
     /**
+     * Returns all ports that has a subnet that contains any of the given IP addresses.
+     *
+     * @param ips a set of IP addresses
+     * @return a set of connect point that has a subnet that contains any of the given IP addresses
+     */
+    public Set<ConnectPoint> getPortByIps(Set<IpAddress> ips) {
+        return srManager.interfaceService.getInterfaces().stream()
+                .filter(intf -> intf.ipAddressesList().stream().anyMatch(intfAddress ->
+                            ips.stream().anyMatch(ip -> intfAddress.subnetAddress().contains(ip))))
+                .map(Interface::connectPoint)
+                .collect(Collectors.toSet());
+    }
+
+    /**
      * Returns the router ip address of segment router that has the
      * specified ip address in its subnets.
      *
diff --git a/apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/HostHandlerTest.java b/apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/HostHandlerTest.java
index 3b6d00a..cce5a31 100644
--- a/apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/HostHandlerTest.java
+++ b/apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/HostHandlerTest.java
@@ -28,6 +28,7 @@
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
 import org.onosproject.net.config.ConfigApplyDelegate;
+import org.onosproject.net.host.HostLocationProbingService;
 import org.onosproject.net.intf.Interface;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DefaultHost;
@@ -176,6 +177,8 @@
     private static final Set<Interface> INTERFACES = Sets.newHashSet(INTF11, INTF12, INTF13, INTF21,
             INTF22, INTF31, INTF32, INTF39, INTF41, INTF49);
 
+    private MockLocationProbingService mockLocationProbingService;
+
     @Before
     public void setUp() throws Exception {
         // Initialize pairDevice and pairLocalPort config
@@ -207,6 +210,8 @@
         srManager.mastershipService = new MockMastershipService(LOCAL_DEVICES);
         srManager.hostService = new MockHostService(HOSTS);
         srManager.cfgService = mockNetworkConfigRegistry;
+        mockLocationProbingService = new MockLocationProbingService();
+        srManager.probingService = mockLocationProbingService;
 
         hostHandler = new HostHandler(srManager);
 
@@ -336,15 +341,22 @@
         assertEquals(2, BRIDGING_TABLE.size());
         assertEquals(P1, BRIDGING_TABLE.get(new MockBridgingTableKey(DEV3, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
         assertEquals(P9, BRIDGING_TABLE.get(new MockBridgingTableKey(DEV4, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+        // Expect probe to be sent out on pair device
+        assertTrue(mockLocationProbingService.verifyProbe(host1, CP41, HostLocationProbingService.ProbeMode.DISCOVER));
 
         // Add the second location of dual-homed host
         // Expect: no longer use the pair link
         hostHandler.processHostMovedEvent(new HostEvent(HostEvent.Type.HOST_MOVED, host2, host1));
         assertEquals(2, ROUTING_TABLE.size());
         assertEquals(P1, ROUTING_TABLE.get(new MockRoutingTableKey(DEV3, HOST_IP11.toIpPrefix())).portNumber);
-        assertEquals(P1, ROUTING_TABLE.get(new MockRoutingTableKey(DEV4, HOST_IP11.toIpPrefix())).portNumber);
+        assertEquals(P9, ROUTING_TABLE.get(new MockRoutingTableKey(DEV4, HOST_IP11.toIpPrefix())).portNumber);
         assertEquals(2, BRIDGING_TABLE.size());
         assertEquals(P1, BRIDGING_TABLE.get(new MockBridgingTableKey(DEV3, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+        assertEquals(P9, BRIDGING_TABLE.get(new MockBridgingTableKey(DEV4, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+        // FIXME: Delay event handling a little bit to wait for the previous redirection flows to be completed
+        //        The permanent solution would be introducing CompletableFuture and wait for it
+        Thread.sleep(HostHandler.HOST_MOVED_DELAY_MS + 50);
+        assertEquals(P1, ROUTING_TABLE.get(new MockRoutingTableKey(DEV4, HOST_IP11.toIpPrefix())).portNumber);
         assertEquals(P1, BRIDGING_TABLE.get(new MockBridgingTableKey(DEV4, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
     }
 
@@ -612,10 +624,16 @@
         assertEquals(4, ROUTING_TABLE.size());
         assertEquals(P1, ROUTING_TABLE.get(new MockRoutingTableKey(DEV3, HOST_IP11.toIpPrefix())).portNumber);
         assertEquals(P1, ROUTING_TABLE.get(new MockRoutingTableKey(DEV3, HOST_IP12.toIpPrefix())).portNumber);
-        assertEquals(P1, ROUTING_TABLE.get(new MockRoutingTableKey(DEV4, HOST_IP11.toIpPrefix())).portNumber);
-        assertEquals(P1, ROUTING_TABLE.get(new MockRoutingTableKey(DEV4, HOST_IP12.toIpPrefix())).portNumber);
+        assertEquals(P9, ROUTING_TABLE.get(new MockRoutingTableKey(DEV4, HOST_IP11.toIpPrefix())).portNumber);
+        assertEquals(P9, ROUTING_TABLE.get(new MockRoutingTableKey(DEV4, HOST_IP12.toIpPrefix())).portNumber);
         assertEquals(2, BRIDGING_TABLE.size());
         assertEquals(P1, BRIDGING_TABLE.get(new MockBridgingTableKey(DEV3, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+        assertEquals(P9, BRIDGING_TABLE.get(new MockBridgingTableKey(DEV4, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+        // FIXME: Delay event handling a little bit to wait for the previous redirection flows to be completed
+        //        The permanent solution would be introducing CompletableFuture and wait for it
+        Thread.sleep(HostHandler.HOST_MOVED_DELAY_MS + 50);
+        assertEquals(P1, ROUTING_TABLE.get(new MockRoutingTableKey(DEV4, HOST_IP11.toIpPrefix())).portNumber);
+        assertEquals(P1, ROUTING_TABLE.get(new MockRoutingTableKey(DEV4, HOST_IP12.toIpPrefix())).portNumber);
         assertEquals(P1, BRIDGING_TABLE.get(new MockBridgingTableKey(DEV4, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
     }
 
@@ -714,22 +732,29 @@
 
         // Discover IP
         // Expect: routing redirection should also work
-        hostHandler.processHostAddedEvent(new HostEvent(HostEvent.Type.HOST_UPDATED, host2, host1));
+        hostHandler.processHostUpdatedEvent(new HostEvent(HostEvent.Type.HOST_UPDATED, host2, host1));
         assertEquals(2, ROUTING_TABLE.size());
         assertEquals(P1, ROUTING_TABLE.get(new MockRoutingTableKey(DEV3, HOST_IP11.toIpPrefix())).portNumber);
         assertEquals(P9, ROUTING_TABLE.get(new MockRoutingTableKey(DEV4, HOST_IP11.toIpPrefix())).portNumber);
         assertEquals(2, BRIDGING_TABLE.size());
         assertEquals(P1, BRIDGING_TABLE.get(new MockBridgingTableKey(DEV3, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
         assertEquals(P9, BRIDGING_TABLE.get(new MockBridgingTableKey(DEV4, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+        // Expect probe to be sent out on pair device
+        assertTrue(mockLocationProbingService.verifyProbe(host2, CP41, HostLocationProbingService.ProbeMode.DISCOVER));
 
         // Discover location
         // Expect: cancel all redirections
-        hostHandler.processHostAddedEvent(new HostEvent(HostEvent.Type.HOST_MOVED, host3, host2));
+        hostHandler.processHostMovedEvent(new HostEvent(HostEvent.Type.HOST_MOVED, host3, host2));
         assertEquals(2, ROUTING_TABLE.size());
         assertEquals(P1, ROUTING_TABLE.get(new MockRoutingTableKey(DEV3, HOST_IP11.toIpPrefix())).portNumber);
-        assertEquals(P1, ROUTING_TABLE.get(new MockRoutingTableKey(DEV4, HOST_IP11.toIpPrefix())).portNumber);
+        assertEquals(P9, ROUTING_TABLE.get(new MockRoutingTableKey(DEV4, HOST_IP11.toIpPrefix())).portNumber);
         assertEquals(2, BRIDGING_TABLE.size());
         assertEquals(P1, BRIDGING_TABLE.get(new MockBridgingTableKey(DEV3, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+        assertEquals(P9, BRIDGING_TABLE.get(new MockBridgingTableKey(DEV4, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
+        // FIXME: Delay event handling a little bit to wait for the previous redirection flows to be completed
+        //        The permanent solution would be introducing CompletableFuture and wait for it
+        Thread.sleep(HostHandler.HOST_MOVED_DELAY_MS + 50);
+        assertEquals(P1, ROUTING_TABLE.get(new MockRoutingTableKey(DEV4, HOST_IP11.toIpPrefix())).portNumber);
         assertEquals(P1, BRIDGING_TABLE.get(new MockBridgingTableKey(DEV4, HOST_MAC, INTF_VLAN_UNTAGGED)).portNumber);
     }
 
diff --git a/apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/MockInterfaceService.java b/apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/MockInterfaceService.java
index 2367b57..f8d04ca 100644
--- a/apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/MockInterfaceService.java
+++ b/apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/MockInterfaceService.java
@@ -39,4 +39,9 @@
         return interfaces.stream().filter(intf -> cp.equals(intf.connectPoint()))
                 .collect(Collectors.toSet());
     }
+
+    @Override
+    public Set<Interface> getInterfaces() {
+        return interfaces;
+    }
 }
diff --git a/apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/MockLocationProbingService.java b/apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/MockLocationProbingService.java
new file mode 100644
index 0000000..b0cdf43
--- /dev/null
+++ b/apps/segmentrouting/src/test/java/org/onosproject/segmentrouting/MockLocationProbingService.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2017-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.Lists;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Host;
+import org.onosproject.net.host.HostLocationProbingService;
+
+import java.util.List;
+import java.util.Objects;
+
+public class MockLocationProbingService implements HostLocationProbingService {
+    List<Probe> probes;
+
+    private class Probe {
+        private Host host;
+        private ConnectPoint connectPoint;
+        private ProbeMode probeMode;
+
+        Probe(Host host, ConnectPoint connectPoint, ProbeMode probeMode) {
+            this.host = host;
+            this.connectPoint = connectPoint;
+            this.probeMode = probeMode;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (!(o instanceof Probe)) {
+                return false;
+            }
+            Probe that = (Probe) o;
+            return (Objects.equals(this.host, that.host) &&
+                    Objects.equals(this.connectPoint, that.connectPoint) &&
+                    Objects.equals(this.probeMode, that.probeMode));
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(host, connectPoint, probeMode);
+        }
+    }
+
+    MockLocationProbingService() {
+        probes = Lists.newArrayList();
+    }
+
+    boolean verifyProbe(Host host, ConnectPoint connectPoint, ProbeMode probeMode) {
+        Probe probe = new Probe(host, connectPoint, probeMode);
+        return probes.contains(probe);
+    }
+
+    @Override
+    public void probeHostLocation(Host host, ConnectPoint connectPoint, ProbeMode probeMode) {
+        probes.add(new Probe(host, connectPoint, probeMode));
+    }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/host/HostLocationProbingService.java b/core/api/src/main/java/org/onosproject/net/host/HostLocationProbingService.java
new file mode 100644
index 0000000..56d056c
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/host/HostLocationProbingService.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017-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.net.host;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Host;
+
+public interface HostLocationProbingService {
+    /**
+     * Mode of host location probing.
+     */
+    enum ProbeMode {
+        /**
+         * Append probed host location if reply is received before timeout. Otherwise, do nothing.
+         * Typically used to discover secondary locations.
+         */
+        DISCOVER,
+
+        /**
+         * Remove probed host location if reply is received after timeout. Otherwise, do nothing.
+         * Typically used to verify previous locations.
+         */
+        VERIFY
+    }
+
+    /**
+     * Probes given host on given location.
+     *
+     * @param host the host to be probed
+     * @param connectPoint the location of host to be probed
+     * @param probeMode probe mode
+     */
+    void probeHostLocation(Host host, ConnectPoint connectPoint, ProbeMode probeMode);
+
+}
diff --git a/core/api/src/main/java/org/onosproject/net/host/HostProviderService.java b/core/api/src/main/java/org/onosproject/net/host/HostProviderService.java
index b324ad0..d9aeff5 100644
--- a/core/api/src/main/java/org/onosproject/net/host/HostProviderService.java
+++ b/core/api/src/main/java/org/onosproject/net/host/HostProviderService.java
@@ -17,8 +17,10 @@
 
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
+import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.HostId;
 import org.onosproject.net.HostLocation;
+import org.onosproject.net.host.HostLocationProbingService.ProbeMode;
 import org.onosproject.net.provider.ProviderService;
 
 /**
@@ -64,10 +66,11 @@
      * retrieves the unique MAC address for the probe.
      *
      * @param hostId ID of the host
-     * @param hostLocation the host location that is under verification
+     * @param connectPoint the connect point that is under verification
+     * @param probeMode probe mode
      * @return probeMac, the source MAC address ONOS uses to probe the host
      */
-    default MacAddress addPendingHostLocation(HostId hostId, HostLocation hostLocation) {
+    default MacAddress addPendingHostLocation(HostId hostId, ConnectPoint connectPoint, ProbeMode probeMode) {
         return MacAddress.NONE;
     }
 
diff --git a/core/api/src/main/java/org/onosproject/net/host/HostStore.java b/core/api/src/main/java/org/onosproject/net/host/HostStore.java
index 6e98d95..96d3c31 100644
--- a/core/api/src/main/java/org/onosproject/net/host/HostStore.java
+++ b/core/api/src/main/java/org/onosproject/net/host/HostStore.java
@@ -23,6 +23,7 @@
 import org.onosproject.net.Host;
 import org.onosproject.net.HostId;
 import org.onosproject.net.HostLocation;
+import org.onosproject.net.host.HostLocationProbingService.ProbeMode;
 import org.onosproject.net.provider.ProviderId;
 import org.onosproject.store.Store;
 
@@ -65,6 +66,14 @@
     HostEvent removeIp(HostId hostId, IpAddress ipAddress);
 
     /**
+     * Append the specified location to the host entry.
+     *
+     * @param hostId host identification
+     * @param location location to be added
+     */
+    void appendLocation(HostId hostId, HostLocation location);
+
+    /**
      * Removes the specified location from the host entry.
      *
      * @param hostId host identification
@@ -139,10 +148,11 @@
      * retrieves the unique MAC address for the probe.
      *
      * @param hostId ID of the host
-     * @param hostLocation the host location that is under verification
+     * @param connectPoint the connect point that is under verification
+     * @param probeMode probe mode
      * @return probeMac, the source MAC address ONOS uses to probe the host
      */
-    default MacAddress addPendingHostLocation(HostId hostId, HostLocation hostLocation) {
+    default MacAddress addPendingHostLocation(HostId hostId, ConnectPoint connectPoint, ProbeMode probeMode) {
         return MacAddress.NONE;
     }
 
diff --git a/core/api/src/test/java/org/onosproject/net/host/HostStoreAdapter.java b/core/api/src/test/java/org/onosproject/net/host/HostStoreAdapter.java
index 8254de1..b79776d 100644
--- a/core/api/src/test/java/org/onosproject/net/host/HostStoreAdapter.java
+++ b/core/api/src/test/java/org/onosproject/net/host/HostStoreAdapter.java
@@ -65,6 +65,11 @@
     }
 
     @Override
+    public void appendLocation(HostId hostId, HostLocation location) {
+
+    }
+
+    @Override
     public void removeLocation(HostId hostId, HostLocation location) {
 
     }
diff --git a/core/common/src/test/java/org/onosproject/store/trivial/SimpleHostStore.java b/core/common/src/test/java/org/onosproject/store/trivial/SimpleHostStore.java
index e1fea28..4af7bea 100644
--- a/core/common/src/test/java/org/onosproject/store/trivial/SimpleHostStore.java
+++ b/core/common/src/test/java/org/onosproject/store/trivial/SimpleHostStore.java
@@ -174,6 +174,11 @@
     }
 
     @Override
+    public void appendLocation(HostId hostId, HostLocation location) {
+        hosts.get(hostId).locations().add(location);
+    }
+
+    @Override
     public void removeLocation(HostId hostId, HostLocation location) {
         hosts.get(hostId).locations().remove(location);
     }
diff --git a/core/net/src/main/java/org/onosproject/net/host/impl/HostManager.java b/core/net/src/main/java/org/onosproject/net/host/impl/HostManager.java
index b7a38e6..998f7df 100644
--- a/core/net/src/main/java/org/onosproject/net/host/impl/HostManager.java
+++ b/core/net/src/main/java/org/onosproject/net/host/impl/HostManager.java
@@ -29,6 +29,7 @@
 import org.onlab.packet.VlanId;
 import org.onlab.util.Tools;
 import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.net.host.HostLocationProbingService.ProbeMode;
 import org.onosproject.net.intf.Interface;
 import org.onosproject.net.intf.InterfaceService;
 import org.onosproject.net.HostLocation;
@@ -455,8 +456,8 @@
         }
 
         @Override
-        public MacAddress addPendingHostLocation(HostId hostId, HostLocation hostLocation) {
-            return store.addPendingHostLocation(hostId, hostLocation);
+        public MacAddress addPendingHostLocation(HostId hostId, ConnectPoint connectPoint, ProbeMode probeMode) {
+            return store.addPendingHostLocation(hostId, connectPoint, probeMode);
         }
 
         @Override
diff --git a/core/store/dist/src/main/java/org/onosproject/store/host/impl/DistributedHostStore.java b/core/store/dist/src/main/java/org/onosproject/store/host/impl/DistributedHostStore.java
index 436ec9b..dcf58e6 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/host/impl/DistributedHostStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/host/impl/DistributedHostStore.java
@@ -43,6 +43,7 @@
 import org.onosproject.net.host.HostEvent;
 import org.onosproject.net.host.HostStore;
 import org.onosproject.net.host.HostStoreDelegate;
+import org.onosproject.net.host.HostLocationProbingService.ProbeMode;
 import org.onosproject.net.provider.ProviderId;
 import org.onosproject.store.AbstractStore;
 import org.onosproject.store.serializers.KryoNamespaces;
@@ -60,6 +61,7 @@
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
@@ -146,7 +148,8 @@
 
         KryoNamespace.Builder pendingHostSerializer = KryoNamespace.newBuilder()
                 .register(KryoNamespaces.API)
-                .register(PendingHostLocation.class);
+                .register(PendingHostLocation.class)
+                .register(ProbeMode.class);
         pendingHostsConsistentMap = storageService.<MacAddress, PendingHostLocation>consistentMapBuilder()
                 .withName("onos-hosts-pending")
                 .withRelaxedReadConsistency()
@@ -311,7 +314,30 @@
     }
 
     @Override
+    public void appendLocation(HostId hostId, HostLocation location) {
+        log.debug("Appending location {} to host {}", location, hostId);
+        hosts.compute(hostId, (id, existingHost) -> {
+            if (existingHost != null) {
+                checkState(Objects.equals(hostId.mac(), existingHost.mac()),
+                        "Existing and new MAC addresses differ.");
+                checkState(Objects.equals(hostId.vlanId(), existingHost.vlan()),
+                        "Existing and new VLANs differ.");
+
+                Set<HostLocation> locations = new HashSet<>(existingHost.locations());
+                locations.add(location);
+
+                return new DefaultHost(existingHost.providerId(),
+                                hostId, existingHost.mac(), existingHost.vlan(),
+                                locations, existingHost.ipAddresses(),
+                                existingHost.configured(), existingHost.annotations());
+            }
+            return null;
+        });
+    }
+
+    @Override
     public void removeLocation(HostId hostId, HostLocation location) {
+        log.debug("Removing location {} from host {}", location, hostId);
         hosts.compute(hostId, (id, existingHost) -> {
             if (existingHost != null) {
                 checkState(Objects.equals(hostId.mac(), existingHost.mac()),
@@ -384,11 +410,11 @@
     }
 
     @Override
-    public MacAddress addPendingHostLocation(HostId hostId, HostLocation hostLocation) {
+    public MacAddress addPendingHostLocation(HostId hostId, ConnectPoint connectPoint, ProbeMode probeMode) {
         // Use ONLab OUI (3 bytes) + atomic counter (3 bytes) as the source MAC of the probe
         long nextIndex = storageService.getAtomicCounter("onos-hosts-probe-index").getAndIncrement();
         MacAddress probeMac = MacAddress.valueOf(MacAddress.NONE.toLong() + nextIndex);
-        PendingHostLocation phl = new PendingHostLocation(hostId, hostLocation);
+        PendingHostLocation phl = new PendingHostLocation(hostId, connectPoint, probeMode);
 
         pendingHostsCache.put(probeMac, phl);
         pendingHosts.put(probeMac, phl);
@@ -398,6 +424,14 @@
 
     @Override
     public void removePendingHostLocation(MacAddress probeMac) {
+        // Add the host location if probe replied in-time in DISCOVER mode
+        Optional.ofNullable(pendingHosts.get(probeMac)).ifPresent(phl -> {
+            if (phl.probeMode() == ProbeMode.DISCOVER) {
+                HostLocation newLocation = new HostLocation(phl.connectPoint(), System.currentTimeMillis());
+                appendLocation(phl.hostId(), newLocation);
+            }
+        });
+
         pendingHostsCache.invalidate(probeMac);
         pendingHosts.remove(probeMac);
     }
@@ -502,11 +536,13 @@
                 case INSERT:
                     break;
                 case UPDATE:
-                    if (newValue.value().expired()) {
+                    // Remove the host location if probe timeout in VERIFY mode
+                    if (newValue.value().expired() && newValue.value().probeMode() == ProbeMode.VERIFY) {
                         Executor locationRemover = Executors.newSingleThreadScheduledExecutor();
                         locationRemover.execute(() -> {
                             pendingHosts.remove(event.key());
-                            removeLocation(newValue.value().hostId(), newValue.value().location());
+                            removeLocation(newValue.value().hostId(),
+                                    new HostLocation(newValue.value().connectPoint(), 0L));
                         });
                     }
                     break;
diff --git a/core/store/dist/src/main/java/org/onosproject/store/host/impl/PendingHostLocation.java b/core/store/dist/src/main/java/org/onosproject/store/host/impl/PendingHostLocation.java
index de538f5..dc10507 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/host/impl/PendingHostLocation.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/host/impl/PendingHostLocation.java
@@ -16,8 +16,9 @@
 
 package org.onosproject.store.host.impl;
 
+import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.HostId;
-import org.onosproject.net.HostLocation;
+import org.onosproject.net.host.HostLocationProbingService.ProbeMode;
 
 import java.util.Objects;
 
@@ -28,19 +29,22 @@
  */
 class PendingHostLocation {
     private HostId hostId;
-    private HostLocation location;
+    private ConnectPoint connectPoint;
     private boolean expired;
+    private ProbeMode probeMode;
 
     /**
      * Constructs PendingHostLocation.
      *
      * @param hostId Host ID
-     * @param location location to be verified
+     * @param connectPoint location to be verified
+     * @param probeMode probe mode
      */
-    PendingHostLocation(HostId hostId, HostLocation location) {
+    PendingHostLocation(HostId hostId, ConnectPoint connectPoint, ProbeMode probeMode) {
         this.hostId = hostId;
-        this.location = location;
+        this.connectPoint = connectPoint;
         this.expired = false;
+        this.probeMode = probeMode;
     }
 
     /**
@@ -53,12 +57,12 @@
     }
 
     /**
-     * Gets HostLocation of this entry.
+     * Gets connect point of this entry.
      *
-     * @return host location
+     * @return connect point
      */
-    HostLocation location() {
-        return location;
+    ConnectPoint connectPoint() {
+        return connectPoint;
     }
 
     /**
@@ -79,6 +83,15 @@
         this.expired = expired;
     }
 
+    /**
+     * Gets probe mode of this entry.
+     *
+     * @return probe mode
+     */
+    ProbeMode probeMode() {
+        return probeMode;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {
@@ -89,20 +102,22 @@
         }
         PendingHostLocation that = (PendingHostLocation) o;
         return (Objects.equals(this.hostId, that.hostId) &&
-                Objects.equals(this.location, that.location) &&
-                Objects.equals(this.expired, that.expired));
+                Objects.equals(this.connectPoint, that.connectPoint) &&
+                Objects.equals(this.expired, that.expired) &&
+                Objects.equals(this.probeMode, that.probeMode));
     }
     @Override
     public int hashCode() {
-        return Objects.hash(hostId, location, expired);
+        return Objects.hash(hostId, connectPoint, expired, probeMode);
     }
 
     @Override
     public String toString() {
         return toStringHelper(getClass())
                 .add("hostId", hostId)
-                .add("location", location)
+                .add("location", connectPoint)
                 .add("expired", expired)
+                .add("probeMode", probeMode)
                 .toString();
     }
 }
diff --git a/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java b/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java
index 7e2feca..eadb4cf 100644
--- a/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java
+++ b/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java
@@ -23,6 +23,7 @@
 import org.apache.felix.scr.annotations.Property;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
 import org.onlab.packet.ARP;
 import org.onlab.packet.BasePacket;
 import org.onlab.packet.DHCP;
@@ -52,6 +53,7 @@
 import org.onosproject.cfg.ComponentConfigService;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
+import org.onosproject.net.host.HostLocationProbingService;
 import org.onosproject.net.intf.InterfaceService;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.Device;
@@ -105,7 +107,8 @@
  * hosts.
  */
 @Component(immediate = true)
-public class HostLocationProvider extends AbstractProvider implements HostProvider {
+@Service
+public class HostLocationProvider extends AbstractProvider implements HostProvider, HostLocationProbingService {
     private final Logger log = getLogger(getClass());
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
@@ -169,6 +172,8 @@
             label = "Allow hosts to be multihomed")
     private boolean multihomingEnabled = false;
 
+    private int probeInitDelayMs = 1000;
+
     protected ExecutorService eventHandler;
 
     private int probeDelayMs = 1000;
@@ -341,7 +346,48 @@
             log.info("Configured. Multihoming is {}",
                     multihomingEnabled ? "enabled" : "disabled");
         }
+    }
 
+    @Override
+    public void probeHostLocation(Host host, ConnectPoint connectPoint, ProbeMode probeMode) {
+        host.ipAddresses().stream().findFirst().ifPresent(ip -> {
+            MacAddress probeMac = providerService.addPendingHostLocation(host.id(), connectPoint, probeMode);
+            log.debug("Constructing {} probe for host {} with {}", probeMode, host.id(), ip);
+            Ethernet probe;
+            if (ip.isIp4()) {
+                probe = ARP.buildArpRequest(probeMac.toBytes(), Ip4Address.ZERO.toOctets(),
+                        host.id().mac().toBytes(), ip.toOctets(),
+                        host.id().mac().toBytes(), host.id().vlanId().toShort());
+            } else {
+                probe = NeighborSolicitation.buildNdpSolicit(
+                        ip.getIp6Address(),
+                        Ip6Address.valueOf(IPv6.getLinkLocalAddress(probeMac.toBytes())),
+                        ip.getIp6Address(),
+                        probeMac,
+                        host.id().mac(),
+                        host.id().vlanId());
+            }
+
+            // NOTE: delay the probe a little bit to wait for the store synchronization is done
+            ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
+            executorService.schedule(() ->
+                    sendLocationProbe(probe, connectPoint), probeInitDelayMs, TimeUnit.MILLISECONDS);
+        });
+    }
+
+    /**
+     * Send the probe packet on given port.
+     *
+     * @param probe the probe packet
+     * @param connectPoint the port we want to probe
+     */
+    private void sendLocationProbe(Ethernet probe, ConnectPoint connectPoint) {
+        log.info("Sending probe for host {} on location {} with probeMac {}",
+                probe.getDestinationMAC(), connectPoint, probe.getSourceMAC());
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder().setOutput(connectPoint.port()).build();
+        OutboundPacket outboundPacket = new DefaultOutboundPacket(connectPoint.deviceId(),
+                treatment, ByteBuffer.wrap(probe.serialize()));
+        packetService.emit(outboundPacket);
     }
 
     @Override
@@ -414,7 +460,8 @@
                         // New location is on a device that we haven't seen before
                         // Could be a dual-home host. Append new location and send out the probe
                         newLocations.addAll(prevLocations);
-                        probeLocations(existingHost);
+                        prevLocations.forEach(prevLocation ->
+                                probeHostLocation(existingHost, prevLocation, ProbeMode.VERIFY));
                     } else {
                         // Move within the same switch
                         // Simply replace old location that is on the same device
@@ -435,54 +482,6 @@
         }
 
         /**
-         * Start verification procedure of all previous locations by sending probes.
-         *
-         * @param host Host to be probed
-         */
-        private void probeLocations(Host host) {
-            host.locations().forEach(location -> {
-                MacAddress probeMac = providerService.addPendingHostLocation(host.id(), location);
-
-                host.ipAddresses().stream().findFirst().ifPresent(ip -> {
-                    log.debug("Probing host {} with {}", host.id(), ip);
-                    Ethernet probe;
-                    if (ip.isIp4()) {
-                        probe = ARP.buildArpRequest(probeMac.toBytes(), Ip4Address.ZERO.toOctets(),
-                                host.id().mac().toBytes(), ip.toOctets(),
-                                host.id().mac().toBytes(), host.id().vlanId().toShort());
-                    } else {
-                        probe = NeighborSolicitation.buildNdpSolicit(
-                                ip.getIp6Address(),
-                                Ip6Address.valueOf(IPv6.getLinkLocalAddress(probeMac.toBytes())),
-                                ip.getIp6Address(),
-                                probeMac,
-                                host.id().mac(),
-                                host.id().vlanId());
-                    }
-
-                    // NOTE: delay the probe a little bit to wait for the store synchronization is done
-                    ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
-                    executorService.schedule(() -> sendProbe(probe, location), probeDelayMs, TimeUnit.MILLISECONDS);
-                });
-            });
-        }
-
-        /**
-         * Send the probe packet on given port.
-         *
-         * @param probe the probe packet
-         * @param connectPoint the port we want to probe
-         */
-        private void sendProbe(Ethernet probe, ConnectPoint connectPoint) {
-            log.info("Probing host {} on location {} with probeMac {}",
-                    probe.getDestinationMAC(), connectPoint, probe.getSourceMAC());
-            TrafficTreatment treatment = DefaultTrafficTreatment.builder().setOutput(connectPoint.port()).build();
-            OutboundPacket outboundPacket = new DefaultOutboundPacket(connectPoint.deviceId(),
-                    treatment, ByteBuffer.wrap(probe.serialize()));
-            packetService.emit(outboundPacket);
-        }
-
-        /**
          * Updates IP address for an existing host.
          *
          * @param hid host ID