Introduced HOST_AUX_MOVED event

Additionally,
- Fixed an issue in DistributedHostStore that didn't copy innverVlan, tpid and suspend bit correctly in some cases
- Clarified javadoc of getConnectedHosts

Change-Id: I56c93eea878a24a6588ceecdbeac75c1747ae8cc
(cherry picked from commit f42939d9ea1c71e42f171d31aa489072191298a8)
diff --git a/core/api/src/main/java/org/onosproject/net/host/DefaultHostDescription.java b/core/api/src/main/java/org/onosproject/net/host/DefaultHostDescription.java
index c83c2c1..74fa9f4 100644
--- a/core/api/src/main/java/org/onosproject/net/host/DefaultHostDescription.java
+++ b/core/api/src/main/java/org/onosproject/net/host/DefaultHostDescription.java
@@ -18,6 +18,7 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashSet;
+import java.util.Objects;
 import java.util.Set;
 
 import org.onlab.packet.EthType;
@@ -31,7 +32,6 @@
 import com.google.common.collect.ImmutableSet;
 
 import static com.google.common.base.MoreObjects.toStringHelper;
-import com.google.common.base.Objects;
 
 /**
  * Default implementation of an immutable host description.
@@ -42,6 +42,7 @@
     private final MacAddress mac;
     private final VlanId vlan;
     private final Set<HostLocation> locations;
+    private final Set<HostLocation> auxLocations;
     private final Set<IpAddress> ip;
     private final VlanId innerVlan;
     private final EthType tpid;
@@ -157,10 +158,31 @@
     public DefaultHostDescription(MacAddress mac, VlanId vlan, Set<HostLocation> locations,
                                   Set<IpAddress> ip, VlanId innerVlan, EthType tpid,
                                   boolean configured, SparseAnnotations... annotations) {
+        this(mac, vlan, locations, null, ip, innerVlan, tpid, configured, annotations);
+    }
+
+    /**
+     * Creates a host description using the supplied information.
+     *
+     * @param mac          host MAC address
+     * @param vlan         host VLAN identifier
+     * @param locations    host locations
+     * @param auxLocations  set of auxiliary locations, or null if unspecified
+     * @param ip           host IP address
+     * @param innerVlan    host inner VLAN identifier
+     * @param tpid         outer TPID of a host
+     * @param configured   true if configured via NetworkConfiguration
+     * @param annotations  optional key/value annotations map
+     */
+    public DefaultHostDescription(MacAddress mac, VlanId vlan,
+                                  Set<HostLocation> locations, Set<HostLocation> auxLocations,
+                                  Set<IpAddress> ip, VlanId innerVlan, EthType tpid,
+                                  boolean configured, SparseAnnotations... annotations) {
         super(annotations);
         this.mac = mac;
         this.vlan = vlan;
         this.locations = new HashSet<>(locations);
+        this.auxLocations = (auxLocations != null) ? new HashSet<>(auxLocations) : null;
         this.ip = new HashSet<>(ip);
         this.innerVlan = innerVlan;
         this.tpid = tpid;
@@ -212,6 +234,11 @@
     }
 
     @Override
+    public Set<HostLocation> auxLocations() {
+        return auxLocations;
+    }
+
+    @Override
     public Set<IpAddress> ipAddress() {
         return ip;
     }
@@ -237,6 +264,7 @@
                 .add("mac", mac)
                 .add("vlan", vlan)
                 .add("locations", locations)
+                .add("auxLocations", auxLocations)
                 .add("ipAddress", ip)
                 .add("configured", configured)
                 .add("innerVlanId", innerVlan)
@@ -246,7 +274,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hashCode(super.hashCode(), mac, vlan, locations, ip);
+        return Objects.hash(super.hashCode(), mac, vlan, locations, auxLocations, ip);
     }
 
     @Override
@@ -256,12 +284,13 @@
                 return false;
             }
             DefaultHostDescription that = (DefaultHostDescription) object;
-            return Objects.equal(this.mac, that.mac)
-                    && Objects.equal(this.vlan, that.vlan)
-                    && Objects.equal(this.locations, that.locations)
-                    && Objects.equal(this.ip, that.ip)
-                    && Objects.equal(this.innerVlan, that.innerVlan)
-                    && Objects.equal(this.tpid, that.tpid);
+            return Objects.equals(this.mac, that.mac)
+                    && Objects.equals(this.vlan, that.vlan)
+                    && Objects.equals(this.locations, that.locations)
+                    && Objects.equals(this.auxLocations, that.auxLocations)
+                    && Objects.equals(this.ip, that.ip)
+                    && Objects.equals(this.innerVlan, that.innerVlan)
+                    && Objects.equals(this.tpid, that.tpid);
         }
         return false;
     }
diff --git a/core/api/src/main/java/org/onosproject/net/host/HostDescription.java b/core/api/src/main/java/org/onosproject/net/host/HostDescription.java
index 543836e..322b57d 100644
--- a/core/api/src/main/java/org/onosproject/net/host/HostDescription.java
+++ b/core/api/src/main/java/org/onosproject/net/host/HostDescription.java
@@ -76,6 +76,13 @@
     Set<HostLocation> locations();
 
     /**
+     * Returns host auxiliary locations, which could be useful for app operations in addition to the attach points.
+     *
+     * @return auxiliary locations, or null if unspecified
+     */
+    Set<HostLocation> auxLocations();
+
+    /**
      * Returns the IP address associated with this host's MAC.
      *
      * @return host IP address
diff --git a/core/api/src/main/java/org/onosproject/net/host/HostEvent.java b/core/api/src/main/java/org/onosproject/net/host/HostEvent.java
index 0fecf2b..132ac11 100644
--- a/core/api/src/main/java/org/onosproject/net/host/HostEvent.java
+++ b/core/api/src/main/java/org/onosproject/net/host/HostEvent.java
@@ -56,7 +56,11 @@
         /**
          * Signifies that host state in non offending state.
          */
-        HOST_UNSUSPENDED
+        HOST_UNSUSPENDED,
+        /**
+         * Signifies that host auxiliary location has changed.
+         */
+        HOST_AUX_MOVED
     }
 
     private Host prevSubject;
@@ -94,7 +98,7 @@
      */
     public HostEvent(Type type, Host host, Host prevSubject) {
         super(type, host);
-        if (type == Type.HOST_MOVED || type == Type.HOST_UPDATED) {
+        if (type == Type.HOST_MOVED || type == Type.HOST_UPDATED || type == Type.HOST_AUX_MOVED) {
             this.prevSubject = prevSubject;
         }
     }
diff --git a/core/api/src/main/java/org/onosproject/net/host/HostService.java b/core/api/src/main/java/org/onosproject/net/host/HostService.java
index 55532bc..349f6db 100644
--- a/core/api/src/main/java/org/onosproject/net/host/HostService.java
+++ b/core/api/src/main/java/org/onosproject/net/host/HostService.java
@@ -81,8 +81,7 @@
     // TODO: consider adding Host getHostByIp(IpAddress ip, VlanId vlan);
 
     /**
-     * Returns the set of hosts whose most recent location is the specified
-     * connection point.
+     * Returns the set of hosts that attach to the specified connection point.
      *
      * @param connectPoint connection point
      * @return set of hosts connected to the connection point
@@ -90,8 +89,7 @@
     Set<Host> getConnectedHosts(ConnectPoint connectPoint);
 
     /**
-     * Returns the set of hosts whose most recent location is the specified
-     * infrastructure device.
+     * Returns the set of hosts that attach to the specified device.
      *
      * @param deviceId device identifier
      * @return set of hosts connected to the device
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 6d6eb11..b6b27b5 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
@@ -127,7 +127,7 @@
     Set<Host> getHosts(IpAddress ip);
 
     /**
-     * Returns the set of hosts whose location falls on the given connection point.
+     * Returns the set of hosts that attach to the specified connection point.
      *
      * @param connectPoint connection point
      * @return set of hosts
@@ -135,7 +135,7 @@
     Set<Host> getConnectedHosts(ConnectPoint connectPoint);
 
     /**
-     * Returns the set of hosts whose location falls on the given device.
+     * Returns the set of hosts that attach to the specified device.
      *
      * @param deviceId infrastructure device identifier
      * @return set of hosts
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 c4e1b68..c68cee3 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
@@ -155,7 +155,8 @@
                 !Objects.equals(existingHost.vlan(), hostDescription.vlan()) ||
                 !Objects.equals(existingHost.innerVlan(), hostDescription.innerVlan()) ||
                 !Objects.equals(existingHost.tpid(), hostDescription.tpid()) ||
-                !Objects.equals(existingHost.locations(), hostDescription.locations())) {
+                !Objects.equals(existingHost.locations(), hostDescription.locations()) ||
+                !Objects.equals(existingHost.auxLocations(), hostDescription.auxLocations())) {
             return true;
         }
 
@@ -211,10 +212,12 @@
                             hostDescription.hwAddress(),
                             hostDescription.vlan(),
                             hostDescription.locations(),
+                            hostDescription.auxLocations(),
                             addresses,
                             hostDescription.innerVlan(),
                             hostDescription.tpid(),
                             hostDescription.configured(),
+                            false,
                             annotations);
                 });
         return null;
@@ -247,6 +250,7 @@
                             existingHost.mac(),
                             existingHost.vlan(),
                             existingHost.locations(),
+                            existingHost.auxLocations(),
                             ImmutableSet.copyOf(addresses),
                             existingHost.innerVlan(),
                             existingHost.tpid(),
@@ -280,7 +284,7 @@
 
                 return new DefaultHost(existingHost.providerId(),
                         hostId, existingHost.mac(), existingHost.vlan(),
-                        newLocations, existingHost.ipAddresses(),
+                        newLocations, existingHost.auxLocations(), existingHost.ipAddresses(),
                         existingHost.innerVlan(), existingHost.tpid(),
                         existingHost.configured(), existingHost.suspended(), existingHost.annotations());
             }
@@ -305,7 +309,7 @@
                 return locations.isEmpty() ? null :
                         new DefaultHost(existingHost.providerId(),
                                 hostId, existingHost.mac(), existingHost.vlan(),
-                                locations, existingHost.ipAddresses(),
+                                locations, existingHost.auxLocations(), existingHost.ipAddresses(),
                                 existingHost.innerVlan(), existingHost.tpid(),
                                 existingHost.configured(), existingHost.suspended(), existingHost.annotations());
             }
@@ -373,6 +377,7 @@
                             existingHost.mac(),
                             existingHost.vlan(),
                             existingHost.locations(),
+                            existingHost.auxLocations(),
                             existingHost.ipAddresses(),
                             existingHost.innerVlan(),
                             existingHost.tpid(),
@@ -396,6 +401,7 @@
                             existingHost.mac(),
                             existingHost.vlan(),
                             existingHost.locations(),
+                            existingHost.auxLocations(),
                             existingHost.ipAddresses(),
                             existingHost.innerVlan(),
                             existingHost.tpid(),
@@ -474,6 +480,8 @@
                         notifyDelegate(new HostEvent(HOST_UNSUSPENDED, host, prevHost));
                     } else if (!Objects.equals(prevHost.locations(), host.locations())) {
                         notifyDelegate(new HostEvent(HOST_MOVED, host, prevHost));
+                    } else if (!Objects.equals(prevHost.auxLocations(), host.auxLocations())) {
+                        notifyDelegate(new HostEvent(HOST_AUX_MOVED, host, prevHost));
                     } else if (!Objects.equals(prevHost, host)) {
                         notifyDelegate(new HostEvent(HOST_UPDATED, host, prevHost));
                     }
diff --git a/core/store/dist/src/test/java/org/onosproject/store/host/impl/DistributedHostStoreTest.java b/core/store/dist/src/test/java/org/onosproject/store/host/impl/DistributedHostStoreTest.java
index a8caf2d..18e8a26 100644
--- a/core/store/dist/src/test/java/org/onosproject/store/host/impl/DistributedHostStoreTest.java
+++ b/core/store/dist/src/test/java/org/onosproject/store/host/impl/DistributedHostStoreTest.java
@@ -19,8 +19,10 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.onlab.packet.EthType;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DefaultHost;
 import org.onosproject.net.DeviceId;
@@ -30,6 +32,8 @@
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.host.DefaultHostDescription;
 import org.onosproject.net.host.HostDescription;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostStoreDelegate;
 import org.onosproject.net.provider.ProviderId;
 import org.onosproject.store.service.MapEvent;
 import org.onosproject.store.service.TestStorageService;
@@ -50,6 +54,7 @@
 public class DistributedHostStoreTest {
 
     private DistributedHostStore ecXHostStore;
+    private TestStoreDelegate delegate;
 
     private static final HostId HOSTID = HostId.hostId(MacAddress.valueOf("1a:1a:1a:1a:1a:1a"));
     private static final HostId HOSTID1 = HostId.hostId(MacAddress.valueOf("1a:1a:1a:1a:1a:1b"));
@@ -97,11 +102,30 @@
                                                                 HOST_LEARNT_WITH_ADDRESSES.annotations());
     private static final MapEvent<HostId, DefaultHost> HOST_EVENT =
             new MapEvent<>("foobar", HOSTID, new Versioned<>(NEW_HOST, 0), new Versioned<>(OLD_HOST, 0));
+    private static final DefaultHost HOST1 = new DefaultHost(PID, HOSTID, HOSTID.mac(), HOSTID.vlanId(),
+            Set.<HostLocation>of(HOST_LOC11), null,
+            Set.<IpAddress>of(), VlanId.NONE,
+            EthType.EtherType.UNKNOWN.ethType(), false, false);
+    private static final DefaultHost HOST2 = new DefaultHost(PID, HOSTID, HOSTID.mac(), HOSTID.vlanId(),
+            Set.<HostLocation>of(HOST_LOC11), null,
+            Set.<IpAddress>of(IP1), VlanId.NONE,
+            EthType.EtherType.UNKNOWN.ethType(), false, false);
+    private static final DefaultHost HOST3 = new DefaultHost(PID, HOSTID, HOSTID.mac(), HOSTID.vlanId(),
+            Set.<HostLocation>of(HOST_LOC11, HOST_LOC12), null,
+            Set.<IpAddress>of(IP1), VlanId.NONE,
+            EthType.EtherType.UNKNOWN.ethType(), false, false);
+    private static final DefaultHost HOST4 = new DefaultHost(PID, HOSTID, HOSTID.mac(), HOSTID.vlanId(),
+            Set.<HostLocation>of(HOST_LOC11), Set.<HostLocation>of(HOST_LOC12),
+            Set.<IpAddress>of(IP1), VlanId.NONE,
+            EthType.EtherType.UNKNOWN.ethType(), false, false);
 
     @Before
     public void setUp() {
         ecXHostStore = new DistributedHostStore();
 
+        delegate = new TestStoreDelegate();
+        ecXHostStore.setDelegate(delegate);
+
         ecXHostStore.storageService = new TestStorageService();
         ecXHostStore.activate();
     }
@@ -299,6 +323,74 @@
         assertEquals(HOST_ADDRESS, hostInHostsByIp.ipAddresses());
     }
 
+    @Test
+    public void testHostAdded() {
+        // Host is first discovered at only one location
+        MapEvent<HostId, DefaultHost> event = new MapEvent<>("event", HOSTID,
+                new Versioned<>(HOST1, 0), null);
+        // Expect: HOST_ADDED
+        ecXHostStore.hostLocationTracker.event(event);
+        assertEquals(HostEvent.Type.HOST_ADDED, delegate.lastEvent.type());
+        assertEquals(HOST1, delegate.lastEvent.subject());
+        assertNull(delegate.lastEvent.prevSubject());
+    }
+
+    @Test
+    public void testHostUpdated() {
+        // Host is updated with an IP
+        MapEvent<HostId, DefaultHost> event = new MapEvent<>("event", HOSTID,
+                new Versioned<>(HOST2, 1), new Versioned<>(HOST1, 0));
+        // Expect: HOST_UPDATED
+        ecXHostStore.hostLocationTracker.event(event);
+        assertEquals(HostEvent.Type.HOST_UPDATED, delegate.lastEvent.type());
+        assertEquals(HOST2, delegate.lastEvent.subject());
+        assertEquals(HOST1, delegate.lastEvent.prevSubject());
+    }
+
+    @Test
+    public void testHostMoved() {
+        // Host is updated with a second location
+        MapEvent<HostId, DefaultHost> event = new MapEvent<>("event", HOSTID,
+                new Versioned<>(HOST3, 1), new Versioned<>(HOST2, 0));
+        // Expect: HOST_MOVED
+        ecXHostStore.hostLocationTracker.event(event);
+        assertEquals(HostEvent.Type.HOST_MOVED, delegate.lastEvent.type());
+        assertEquals(HOST3, delegate.lastEvent.subject());
+        assertEquals(HOST2, delegate.lastEvent.prevSubject());
+    }
+
+    @Test
+    public void testHostAuxMoved() {
+        // Host aux location changed
+        MapEvent<HostId, DefaultHost> event = new MapEvent<>("event", HOSTID,
+                new Versioned<>(HOST4, 1), new Versioned<>(HOST1, 0));
+        // Expect: HOST_AUX_MOVED
+        ecXHostStore.hostLocationTracker.event(event);
+        assertEquals(HostEvent.Type.HOST_AUX_MOVED, delegate.lastEvent.type());
+        assertEquals(HOST4, delegate.lastEvent.subject());
+        assertEquals(HOST1, delegate.lastEvent.prevSubject());
+    }
+
+    @Test
+    public void testHostRemoved() {
+        // Host is removed
+        MapEvent<HostId, DefaultHost> event = new MapEvent<>("event", HOSTID,
+                null, new Versioned<>(HOST3, 0));
+        // Expect: HOST_REMOVED
+        ecXHostStore.hostLocationTracker.event(event);
+        assertEquals(HostEvent.Type.HOST_REMOVED, delegate.lastEvent.type());
+        assertEquals(HOST3, delegate.lastEvent.subject());
+        assertNull(delegate.lastEvent.prevSubject());
+    }
+
+    private class TestStoreDelegate implements HostStoreDelegate {
+        public HostEvent lastEvent;
+
+        @Override
+        public void notify(HostEvent event) {
+            lastEvent = event;
+        }
+    }
 
     private static HostDescription createHostDesc(HostId hostId, Set<IpAddress> ips) {
         return createHostDesc(hostId, ips, false, Collections.emptySet());