Remove host location when port down or device down

Also refactor Host Location Provider

Change-Id: I57d682ee51e80ddd7e141883521a12da705a336d
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 65472dd..97b49a4 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
@@ -46,6 +46,7 @@
 import org.onosproject.core.CoreService;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
 import org.onosproject.net.Host;
 import org.onosproject.net.HostId;
 import org.onosproject.net.HostLocation;
@@ -77,7 +78,6 @@
 
 import java.nio.ByteBuffer;
 import java.util.Dictionary;
-import java.util.Set;
 import java.util.concurrent.ExecutorService;
 
 import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
@@ -365,9 +365,7 @@
         packetService.emit(outboundPacket);
     }
 
-    /*
-     * This method is using source ip as 0.0.0.0 , to receive the reply even from the sub net hosts.
-     */
+    // This method is using source ip as 0.0.0.0 , to receive the reply even from the sub net hosts.
     private Ethernet buildArpRequest(IpAddress targetIp, Host host) {
 
         ARP arp = new ARP();
@@ -393,36 +391,19 @@
 
     private class InternalHostProvider implements PacketProcessor {
         /**
-         * Updates host location only.
+         * Create or update host information.
+         * Will not update IP if IP is null, all zero or self-assigned.
          *
          * @param hid  host ID
          * @param mac  source Mac address
          * @param vlan VLAN ID
          * @param hloc host location
+         * @param ip   source IP address or null if not updating
          */
-        private void updateLocation(HostId hid, MacAddress mac,
-                                    VlanId vlan, HostLocation hloc) {
-            HostDescription desc = new DefaultHostDescription(mac, vlan, hloc);
-            try {
-                providerService.hostDetected(hid, desc, false);
-            } catch (IllegalStateException e) {
-                log.debug("Host {} suppressed", hid);
-            }
-        }
-
-        /**
-         * Updates host location and IP address.
-         *
-         * @param hid  host ID
-         * @param mac  source Mac address
-         * @param vlan VLAN ID
-         * @param hloc host location
-         * @param ip   source IP address
-         */
-        private void updateLocationIP(HostId hid, MacAddress mac,
-                                      VlanId vlan, HostLocation hloc,
-                                      IpAddress ip) {
-            HostDescription desc = ip.isZero() || ip.isSelfAssigned() ?
+        private void createOrUpdateHost(HostId hid, MacAddress mac,
+                                        VlanId vlan, HostLocation hloc,
+                                        IpAddress ip) {
+            HostDescription desc = ip == null || ip.isZero() || ip.isSelfAssigned() ?
                     new DefaultHostDescription(mac, vlan, hloc) :
                     new DefaultHostDescription(mac, vlan, hloc, ip);
             try {
@@ -433,20 +414,20 @@
         }
 
         /**
-         * Updates host IP address for an existing host.
+         * Updates IP address for an existing host.
          *
          * @param hid host ID
          * @param ip IP address
          */
-        private void updateIp(HostId hid, IpAddress ip) {
+        private void updateHostIp(HostId hid, IpAddress ip) {
             Host host = hostService.getHost(hid);
             if (host == null) {
                 log.debug("Fail to update IP for {}. Host does not exist");
                 return;
             }
 
-            HostDescription desc =
-                    new DefaultHostDescription(hid.mac(), hid.vlanId(), host.location(), ip);
+            HostDescription desc = new DefaultHostDescription(hid.mac(), hid.vlanId(),
+                    host.location(), ip);
             try {
                 providerService.hostDetected(hid, desc, false);
             } catch (IllegalStateException e) {
@@ -492,7 +473,7 @@
                 ARP arp = (ARP) eth.getPayload();
                 IpAddress ip = IpAddress.valueOf(IpAddress.Version.INET,
                                                  arp.getSenderProtocolAddress());
-                updateLocationIP(hid, srcMac, vlan, hloc, ip);
+                createOrUpdateHost(hid, srcMac, vlan, hloc, ip);
 
             // IPv4: update location only
             // DHCP ACK: additionally update IP of DHCP client
@@ -512,12 +493,12 @@
                                 MacAddress hostMac = MacAddress.valueOf(dhcp.getClientHardwareAddress());
                                 VlanId hostVlan = VlanId.vlanId(eth.getVlanID());
                                 HostId hostId = HostId.hostId(hostMac, hostVlan);
-                                updateIp(hostId, IpAddress.valueOf(dhcp.getYourIPAddress()));
+                                updateHostIp(hostId, IpAddress.valueOf(dhcp.getYourIPAddress()));
                             }
                         }
                     }
                 }
-                updateLocation(hid, srcMac, vlan, hloc);
+                createOrUpdateHost(hid, srcMac, vlan, hloc, null);
 
             //
             // NeighborAdvertisement and NeighborSolicitation: possible
@@ -552,7 +533,7 @@
                             return;
                         }
                         // NeighborSolicitation, NeighborAdvertisement
-                        updateLocationIP(hid, srcMac, vlan, hloc, ip);
+                        createOrUpdateHost(hid, srcMac, vlan, hloc, ip);
                         return;
                     }
                 }
@@ -563,7 +544,7 @@
                 }
 
                 // normal IPv6 packets
-                updateLocation(hid, srcMac, vlan, hloc);
+                createOrUpdateHost(hid, srcMac, vlan, hloc, null);
             }
         }
     }
@@ -581,9 +562,8 @@
                 case DEVICE_ADDED:
                     break;
                 case DEVICE_AVAILABILITY_CHANGED:
-                    if (hostRemovalEnabled &&
-                            !deviceService.isAvailable(device.id())) {
-                        removeHosts(hostService.getConnectedHosts(device.id()));
+                    if (hostRemovalEnabled && !deviceService.isAvailable(device.id())) {
+                        processDeviceDown(device.id());
                     }
                     break;
                 case DEVICE_SUSPENDED:
@@ -592,16 +572,14 @@
                     break;
                 case DEVICE_REMOVED:
                     if (hostRemovalEnabled) {
-                        removeHosts(hostService.getConnectedHosts(device.id()));
+                        processDeviceDown(device.id());
                     }
                     break;
                 case PORT_ADDED:
                     break;
                 case PORT_UPDATED:
-                    if (hostRemovalEnabled) {
-                        ConnectPoint point =
-                                new ConnectPoint(device.id(), event.port().number());
-                        removeHosts(hostService.getConnectedHosts(point));
+                    if (hostRemovalEnabled && !event.port().isEnabled()) {
+                        processPortDown(new ConnectPoint(device.id(), event.port().number()));
                     }
                     break;
                 case PORT_REMOVED:
@@ -613,11 +591,28 @@
         }
     }
 
-    // Signals host vanish for all specified hosts.
-    private void removeHosts(Set<Host> hosts) {
-        for (Host host : hosts) {
-            providerService.hostVanished(host.id());
-        }
+    /**
+     * When a device goes down, update the location of affected hosts.
+     *
+     * @param deviceId the device that goes down
+     */
+    private void processDeviceDown(DeviceId deviceId) {
+        hostService.getConnectedHosts(deviceId).forEach(affectedHost -> affectedHost.locations().stream()
+                .filter(hostLocation -> hostLocation.deviceId().equals(deviceId))
+                .forEach(affectedLocation ->
+                        providerService.removeLocationFromHost(affectedHost.id(), affectedLocation))
+        );
+    }
+
+    /**
+     * When a port goes down, update the location of affected hosts.
+     *
+     * @param connectPoint the port that goes down
+     */
+    private void processPortDown(ConnectPoint connectPoint) {
+        hostService.getConnectedHosts(connectPoint).forEach(affectedHost ->
+                providerService.removeLocationFromHost(affectedHost.id(), new HostLocation(connectPoint, 0L))
+        );
     }
 
 }