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