Implement host probing retry with major refactoring

- Implement probe retry
- Switch to typical core/provider design pattern for HostProbingService
  and as a result decoupling the dependency between SR and HostLocationProvider

Change-Id: I33a15af580677ea376b421ac3e26f9821dcca844
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 dc58dcc..40e67b5 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
@@ -54,7 +54,6 @@
 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;
@@ -93,12 +92,9 @@
 import java.util.Objects;
 import java.util.Optional;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
 import java.util.stream.Stream;
 import java.util.Set;
 
-import static java.util.concurrent.Executors.newScheduledThreadPool;
 import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
 import static org.onlab.util.Tools.groupedThreads;
 import static org.slf4j.LoggerFactory.getLogger;
@@ -109,7 +105,7 @@
  */
 @Component(immediate = true)
 @Service
-public class HostLocationProvider extends AbstractProvider implements HostProvider, HostLocationProbingService {
+public class HostLocationProvider extends AbstractProvider implements HostProvider {
     private final Logger log = getLogger(getClass());
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
@@ -136,8 +132,6 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected InterfaceService interfaceService;
 
-    private HostProviderService providerService;
-
     private final InternalHostProvider processor = new InternalHostProvider();
     private final DeviceListener deviceListener = new InternalDeviceListener();
 
@@ -173,11 +167,11 @@
             label = "Allow hosts to be multihomed")
     private boolean multihomingEnabled = false;
 
-    private int probeInitDelayMs = 1000;
+    private HostProviderService providerService;
 
-    ExecutorService eventHandler;
+    ExecutorService deviceEventHandler;
+    private ExecutorService probeEventHandler;
     private ExecutorService packetHandler;
-    private ScheduledExecutorService hostProber;
 
     /**
      * Creates an OpenFlow host provider.
@@ -190,10 +184,12 @@
     public void activate(ComponentContext context) {
         cfgService.registerProperties(getClass());
         appId = coreService.registerApplication("org.onosproject.provider.host");
-        eventHandler = newSingleThreadScheduledExecutor(groupedThreads("onos/host-loc-provider", "event-handler", log));
+        deviceEventHandler = newSingleThreadScheduledExecutor(groupedThreads("onos/host-loc-provider",
+                "device-event-handler", log));
+        probeEventHandler = newSingleThreadScheduledExecutor(groupedThreads("onos/host-loc-provider",
+                "probe-event-handler", log));
         packetHandler = newSingleThreadScheduledExecutor(groupedThreads("onos/host-loc-provider",
                 "packet-handler", log));
-        hostProber = newScheduledThreadPool(32, groupedThreads("onos/host-loc-probe", "%d", log));
         providerService = providerRegistry.register(this);
         packetService.addProcessor(processor, PacketProcessor.advisor(1));
         deviceService.addListener(deviceListener);
@@ -212,9 +208,9 @@
         providerRegistry.unregister(this);
         packetService.removeProcessor(processor);
         deviceService.removeListener(deviceListener);
-        eventHandler.shutdown();
+        deviceEventHandler.shutdown();
+        probeEventHandler.shutdown();
         packetHandler.shutdown();
-        hostProber.shutdown();
         providerService = null;
         log.info("Stopped");
     }
@@ -354,47 +350,6 @@
     }
 
     @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
-            hostProber.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.debug("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
     public void triggerProbe(Host host) {
         //log.info("Triggering probe on device {} ", host);
 
@@ -465,10 +420,8 @@
 
                     if (prevLocations.stream().noneMatch(loc -> loc.deviceId().equals(hloc.deviceId()))) {
                         // 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
+                        // Could be a dual-home host.
                         newLocations.addAll(prevLocations);
-                        prevLocations.forEach(prevLocation ->
-                                probeHostLocation(existingHost, prevLocation, ProbeMode.VERIFY));
                     } else {
                         // Move within the same switch
                         // Simply replace old location that is on the same device
@@ -559,10 +512,8 @@
             HostId hid = HostId.hostId(eth.getSourceMAC(), vlan);
             MacAddress destMac = eth.getDestinationMAC();
 
-            // Receives a location probe. Invalid entry from the cache
+            // Ignore location probes
             if (multihomingEnabled && destMac.isOnos() && !MacAddress.NONE.equals(destMac)) {
-                log.debug("Receives probe for {}/{} on {}", srcMac, vlan, heardOn);
-                providerService.removePendingHostLocation(destMac);
                 return;
             }
 
@@ -762,7 +713,7 @@
     private class InternalDeviceListener implements DeviceListener {
         @Override
         public void event(DeviceEvent event) {
-            eventHandler.execute(() -> handleEvent(event));
+            deviceEventHandler.execute(() -> handleEvent(event));
         }
 
         private void handleEvent(DeviceEvent event) {
diff --git a/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java b/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java
index e00617d..89a6ef8 100644
--- a/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java
+++ b/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java
@@ -236,7 +236,7 @@
 
         provider.activate(CTX_FOR_NO_REMOVE);
 
-        provider.eventHandler = MoreExecutors.newDirectExecutorService();
+        provider.deviceEventHandler = MoreExecutors.newDirectExecutorService();
     }
 
     @Test
diff --git a/providers/hostprobing/BUCK b/providers/hostprobing/BUCK
new file mode 100644
index 0000000..21bf493
--- /dev/null
+++ b/providers/hostprobing/BUCK
@@ -0,0 +1,22 @@
+COMPILE_DEPS = [
+    '//lib:CORE_DEPS',
+    '//incubator/api:onos-incubator-api',
+]
+
+TEST_DEPS = [
+    '//lib:TEST_ADAPTERS',
+    '//utils/osgi:onlab-osgi-tests',
+]
+
+osgi_jar_with_tests (
+    deps = COMPILE_DEPS,
+    test_deps = TEST_DEPS,
+)
+
+onos_app (
+    app_name = 'org.onosproject.hostprobingprovider',
+    title = 'Host Probing Provider',
+    category = 'Provider',
+    url = 'http://onosproject.org',
+    description = 'Provides host probing mechanism that discovers or verifies the existence of a host at specific location',
+)
diff --git a/providers/hostprobing/pom.xml b/providers/hostprobing/pom.xml
new file mode 100644
index 0000000..3f3c001
--- /dev/null
+++ b/providers/hostprobing/pom.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2014-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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.onosproject</groupId>
+        <artifactId>onos-providers</artifactId>
+        <version>1.12.1-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>onos-host-provider</artifactId>
+    <packaging>bundle</packaging>
+
+    <properties>
+        <onos.app.name>org.onosproject.hostprobingprovider</onos.app.name>
+        <onos.app.title>Host Probing Provider</onos.app.title>
+        <onos.app.origin>ONF</onos.app.origin>
+        <onos.app.category>Provider</onos.app.category>
+        <onos.app.url>http://onosproject.org</onos.app.url>
+        <onos.app.readme>Provides host probing mechanism that discovers or verifies the existence of a host at specific location</onos.app.readme>
+    </properties>
+
+    <description>ONOS host probing provider</description>
+    <dependencies>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-api</artifactId>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-osgi</artifactId>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.easymock</groupId>
+            <artifactId>easymock</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-incubator-api</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/providers/hostprobing/src/main/java/org/onosproject/provider/hostprobing/impl/DefaultHostProbingProvider.java b/providers/hostprobing/src/main/java/org/onosproject/provider/hostprobing/impl/DefaultHostProbingProvider.java
new file mode 100644
index 0000000..dfcbf0b
--- /dev/null
+++ b/providers/hostprobing/src/main/java/org/onosproject/provider/hostprobing/impl/DefaultHostProbingProvider.java
@@ -0,0 +1,252 @@
+/*
+ * 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.provider.hostprobing.impl;
+
+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.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.ARP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv6;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onlab.packet.ndp.NeighborSolicitation;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.host.HostProbe;
+import org.onosproject.net.host.HostProbingEvent;
+import org.onosproject.net.host.HostProbingProvider;
+import org.onosproject.net.host.HostProbingProviderRegistry;
+import org.onosproject.net.host.HostProbingProviderService;
+import org.onosproject.net.host.HostProvider;
+import org.onosproject.net.host.HostProviderRegistry;
+import org.onosproject.net.host.HostProviderService;
+import org.onosproject.net.host.ProbeMode;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.net.provider.AbstractProvider;
+import org.onosproject.net.provider.ProviderId;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.Optional;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import static java.util.concurrent.Executors.newScheduledThreadPool;
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provider which sends host location probes to discover or verify a host at specific location.
+ */
+@Component(immediate = true)
+@Service
+public class DefaultHostProbingProvider extends AbstractProvider implements HostProvider, HostProbingProvider {
+    private final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private HostProviderRegistry providerRegistry;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private HostProbingProviderRegistry hostProbingProviderRegistry;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private MastershipService mastershipService;
+
+    private HostProviderService providerService;
+    private HostProbingProviderService hostProbingProviderService;
+    private ExecutorService packetHandler;
+    private ExecutorService probeEventHandler;
+    private ScheduledExecutorService hostProber;
+
+    private final PacketProcessor packetProcessor = context ->
+        packetHandler.execute(() -> {
+            Ethernet eth = context.inPacket().parsed();
+            if (eth == null) {
+                return;
+            }
+            MacAddress srcMac = eth.getSourceMAC();
+            MacAddress destMac = eth.getDestinationMAC();
+            VlanId vlan = VlanId.vlanId(eth.getVlanID());
+            ConnectPoint heardOn = context.inPacket().receivedFrom();
+
+            // Receives a location probe. Invalid entry from the cache
+            if (destMac.isOnos() && !MacAddress.NONE.equals(destMac)) {
+                log.debug("Receives probe for {}/{} on {}", srcMac, vlan, heardOn);
+                hostProbingProviderService.removeProbingHost(destMac);
+            }
+        });
+
+    // TODO Make this configurable
+    private static final int PROBE_INIT_DELAY_MS = 1000;
+    private static final int DEFAULT_RETRY = 5;
+
+    /**
+     * Creates an OpenFlow host provider.
+     */
+    public DefaultHostProbingProvider() {
+        super(new ProviderId("hostprobing", "org.onosproject.provider.hostprobing"));
+    }
+
+    @Activate
+    public void activate(ComponentContext context) {
+        providerService = providerRegistry.register(this);
+        hostProbingProviderService = hostProbingProviderRegistry.register(this);
+
+        packetHandler = newSingleThreadScheduledExecutor(groupedThreads("onos/host-loc-provider",
+                "packet-handler", log));
+        probeEventHandler = newSingleThreadScheduledExecutor(groupedThreads("onos/host-loc-provider",
+                "probe-handler", log));
+        hostProber = newScheduledThreadPool(32, groupedThreads("onos/host-loc-probe", "%d", log));
+
+        packetService.addProcessor(packetProcessor, PacketProcessor.advisor(1));
+    }
+
+    @Deactivate
+    public void deactivate() {
+        providerRegistry.unregister(this);
+        hostProbingProviderRegistry.unregister(this);
+        providerService = null;
+
+        packetService.removeProcessor(packetProcessor);
+
+        packetHandler.shutdown();
+        probeEventHandler.shutdown();
+        hostProber.shutdown();
+    }
+
+    @Override
+    public void triggerProbe(Host host) {
+        // Not doing anything at this moment...
+    }
+
+    @Override
+    public void processEvent(HostProbingEvent event) {
+        probeEventHandler.execute(() -> {
+            log.debug("Receiving HostProbingEvent {}", event);
+            HostProbe hostProbe = event.subject();
+
+            switch (event.type()) {
+                case PROBE_REQUESTED:
+                    // Do nothing
+                    break;
+                case PROBE_TIMEOUT:
+                    // Retry probe until PROBE_FAIL
+                    // TODO Only retry DISCOVER probes
+                    probeHostInternal(hostProbe, hostProbe.connectPoint(),
+                            hostProbe.mode(), hostProbe.probeMac(), hostProbe.retry());
+                    break;
+                case PROBE_FAIL:
+                    // Remove this location if this is a verify probe.
+                    if (hostProbe.mode() == ProbeMode.VERIFY) {
+                        providerService.removeLocationFromHost(hostProbe.id(),
+                                (HostLocation) hostProbe.connectPoint());
+                    }
+                    break;
+                case PROBE_COMPLETED:
+                    // Add this location if this is a discover probe.
+                    if (hostProbe.mode() == ProbeMode.DISCOVER) {
+                        HostLocation newLocation = new HostLocation(hostProbe.connectPoint(),
+                                System.currentTimeMillis());
+                        providerService.addLocationToHost(hostProbe.id(), newLocation);
+                    }
+                    break;
+                default:
+                    log.warn("Unknown HostProbingEvent type: {}", event.type());
+            }
+        });
+    }
+
+    @Override
+    public void probeHost(Host host, ConnectPoint connectPoint, ProbeMode probeMode) {
+        probeHostInternal(host, connectPoint, probeMode, null, DEFAULT_RETRY);
+    }
+
+    // probeMac can be null if this is the very first probe and the mac is to-be-generated.
+    private void probeHostInternal(Host host, ConnectPoint connectPoint, ProbeMode probeMode,
+                                   MacAddress probeMac, int retry) {
+        if (!mastershipService.isLocalMaster(connectPoint.deviceId())) {
+            log.debug("Current node is not master of {}, abort probing {}", connectPoint.deviceId(), host);
+            return;
+        }
+
+        log.debug("probeHostInternal host={}, cp={}, mode={}, probeMac={}, retry={}", host, connectPoint,
+                probeMode, probeMac, retry);
+        Optional<IpAddress> ipOptional = host.ipAddresses().stream().findFirst();
+
+        if (ipOptional.isPresent()) {
+            probeMac = hostProbingProviderService.addProbingHost(host, connectPoint, probeMode, probeMac, retry);
+
+            IpAddress ip = ipOptional.get();
+            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
+            hostProber.schedule(() ->
+                    sendLocationProbe(probe, connectPoint), PROBE_INIT_DELAY_MS, TimeUnit.MILLISECONDS);
+        } else {
+            log.debug("Host {} has no IP address yet. Skip probing.", host);
+        }
+    }
+
+    /**
+     * 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.debug("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);
+    }
+}
\ No newline at end of file
diff --git a/providers/hostprobing/src/main/java/org/onosproject/provider/hostprobing/impl/package-info.java b/providers/hostprobing/src/main/java/org/onosproject/provider/hostprobing/impl/package-info.java
new file mode 100644
index 0000000..60ef748
--- /dev/null
+++ b/providers/hostprobing/src/main/java/org/onosproject/provider/hostprobing/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Provider that uses packet service as a means of host discovery and tracking.
+ */
+package org.onosproject.provider.hostprobing.impl;
diff --git a/providers/pom.xml b/providers/pom.xml
index 1e23b20..f2f5fff 100644
--- a/providers/pom.xml
+++ b/providers/pom.xml
@@ -33,6 +33,7 @@
     <modules>
         <module>openflow</module>
         <module>host</module>
+        <module>hostprobing</module>
         <module>netcfghost</module>
         <module>netconf</module>
         <module>null</module>