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/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