Extend host structures to store multiple locations

Also update host location format in CLI and REST API

Change-Id: I0fbd655f642627dd3eb8a2925f83a3ee016fe4aa
diff --git a/core/api/src/main/java/org/onosproject/net/DefaultHost.java b/core/api/src/main/java/org/onosproject/net/DefaultHost.java
index d9cfa25..defb5a3 100644
--- a/core/api/src/main/java/org/onosproject/net/DefaultHost.java
+++ b/core/api/src/main/java/org/onosproject/net/DefaultHost.java
@@ -21,6 +21,7 @@
 import org.onlab.packet.VlanId;
 
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashSet;
 import java.util.Objects;
 import java.util.Set;
@@ -34,7 +35,7 @@
 
     private final MacAddress mac;
     private final VlanId vlan;
-    private final HostLocation location;
+    private final Set<HostLocation> locations;
     private final Set<IpAddress> ips;
     private final boolean configured;
 
@@ -70,10 +71,29 @@
     public DefaultHost(ProviderId providerId, HostId id, MacAddress mac,
                        VlanId vlan, HostLocation location, Set<IpAddress> ips,
                        boolean configured, Annotations... annotations) {
+        this(providerId, id, mac, vlan, Collections.singleton(location), ips,
+                configured, annotations);
+    }
+
+    /**
+     * Creates an end-station host using the supplied information.
+     *
+     * @param providerId  provider identity
+     * @param id          host identifier
+     * @param mac         host MAC address
+     * @param vlan        host VLAN identifier
+     * @param locations   set of host locations
+     * @param ips         host IP addresses
+     * @param configured  true if configured via NetworkConfiguration
+     * @param annotations optional key/value annotations
+     */
+    public DefaultHost(ProviderId providerId, HostId id, MacAddress mac,
+                       VlanId vlan, Set<HostLocation> locations, Set<IpAddress> ips,
+                       boolean configured, Annotations... annotations) {
         super(providerId, id, annotations);
         this.mac = mac;
         this.vlan = vlan;
-        this.location = location;
+        this.locations = new HashSet<>(locations);
         this.ips = new HashSet<>(ips);
         this.configured = configured;
     }
@@ -99,7 +119,14 @@
 
     @Override
     public HostLocation location() {
-        return location;
+        return locations.stream()
+                .sorted(Comparator.comparingLong(HostLocation::time).reversed())
+                .findFirst().orElse(null);
+    }
+
+    @Override
+    public Set<HostLocation> locations() {
+        return locations;
     }
 
     @Override
@@ -114,7 +141,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(id, mac, vlan, location);
+        return Objects.hash(id, mac, vlan, locations);
     }
 
     @Override
@@ -127,7 +154,7 @@
             return Objects.equals(this.id, other.id) &&
                     Objects.equals(this.mac, other.mac) &&
                     Objects.equals(this.vlan, other.vlan) &&
-                    Objects.equals(this.location, other.location) &&
+                    Objects.equals(this.locations, other.locations) &&
                     Objects.equals(this.ipAddresses(), other.ipAddresses()) &&
                     Objects.equals(this.annotations(), other.annotations());
         }
@@ -140,7 +167,7 @@
                 .add("id", id())
                 .add("mac", mac())
                 .add("vlan", vlan())
-                .add("location", location())
+                .add("locations", locations())
                 .add("ipAddresses", ipAddresses())
                 .add("annotations", annotations())
                 .add("configured", configured())
diff --git a/core/api/src/main/java/org/onosproject/net/Host.java b/core/api/src/main/java/org/onosproject/net/Host.java
index 5094727..86ef79f 100644
--- a/core/api/src/main/java/org/onosproject/net/Host.java
+++ b/core/api/src/main/java/org/onosproject/net/Host.java
@@ -59,11 +59,18 @@
      * Returns the most recent host location where the host attaches to the
      * network edge.
      *
-     * @return host location
+     * @return the most recent host location
      */
     HostLocation location();
 
     /**
+     * Returns all host locations where the host attaches to the network edge.
+     *
+     * @return all host locations
+     */
+    Set<HostLocation> locations();
+
+    /**
      * Returns true if configured by NetworkConfiguration.
      * @return configured/learnt dynamically
      */
@@ -73,4 +80,3 @@
     // TODO: explore capturing list of recent locations to aid in mobility
 
 }
-
diff --git a/core/api/src/main/java/org/onosproject/net/HostLocation.java b/core/api/src/main/java/org/onosproject/net/HostLocation.java
index f7868c6..94409b6 100644
--- a/core/api/src/main/java/org/onosproject/net/HostLocation.java
+++ b/core/api/src/main/java/org/onosproject/net/HostLocation.java
@@ -64,4 +64,8 @@
         return time;
     }
 
+    @Override
+    public String toString() {
+        return deviceId() + "/" + port();
+    }
 }
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 5f9cfd8..c2531f6 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
@@ -16,6 +16,8 @@
 package org.onosproject.net.host;
 
 import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
 import java.util.Set;
 
 import org.onosproject.net.AbstractDescription;
@@ -38,7 +40,7 @@
 
     private final MacAddress mac;
     private final VlanId vlan;
-    private final HostLocation location;
+    private final Set<HostLocation> locations;
     private final Set<IpAddress> ip;
     private final boolean configured;
 
@@ -53,8 +55,7 @@
     public DefaultHostDescription(MacAddress mac, VlanId vlan,
                                   HostLocation location,
                                   SparseAnnotations... annotations) {
-        this(mac, vlan, location, Collections.emptySet(),
-             annotations);
+        this(mac, vlan, location, Collections.emptySet(), annotations);
     }
 
     /**
@@ -100,8 +101,7 @@
                                   HostLocation location,
                                   boolean configured,
                                   SparseAnnotations... annotations) {
-        this(mac, vlan, location, Collections.<IpAddress>emptySet(),
-             configured, annotations);
+        this(mac, vlan, location, Collections.emptySet(), configured, annotations);
     }
 
     /**
@@ -118,11 +118,28 @@
                                   HostLocation location, Set<IpAddress> ip,
                                   boolean configured,
                                   SparseAnnotations... annotations) {
+        this(mac, vlan, Collections.singleton(location), ip, 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 ip           host IP address
+     * @param configured   true if configured via NetworkConfiguration
+     * @param annotations  optional key/value annotations map
+     */
+    public DefaultHostDescription(MacAddress mac, VlanId vlan,
+                                  Set<HostLocation> locations,
+                                  Set<IpAddress> ip, boolean configured,
+                                  SparseAnnotations... annotations) {
         super(annotations);
         this.mac = mac;
         this.vlan = vlan;
-        this.location = location;
-        this.ip = ImmutableSet.copyOf(ip);
+        this.locations = new HashSet<>(locations);
+        this.ip = new HashSet<>(ip);
         this.configured = configured;
     }
 
@@ -138,7 +155,14 @@
 
     @Override
     public HostLocation location() {
-        return location;
+        return locations.stream()
+                .sorted(Comparator.comparingLong(HostLocation::time).reversed())
+                .findFirst().orElse(null);
+    }
+
+    @Override
+    public Set<HostLocation> locations() {
+        return locations;
     }
 
     @Override
@@ -156,7 +180,7 @@
         return toStringHelper(this)
                 .add("mac", mac)
                 .add("vlan", vlan)
-                .add("location", location)
+                .add("locations", locations)
                 .add("ipAddress", ip)
                 .add("configured", configured)
                 .toString();
@@ -164,7 +188,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hashCode(super.hashCode(), mac, vlan, location, ip);
+        return Objects.hashCode(super.hashCode(), mac, vlan, locations, ip);
     }
 
     @Override
@@ -176,7 +200,7 @@
             DefaultHostDescription that = (DefaultHostDescription) object;
             return Objects.equal(this.mac, that.mac)
                     && Objects.equal(this.vlan, that.vlan)
-                    && Objects.equal(this.location, that.location)
+                    && Objects.equal(this.locations, that.locations)
                     && Objects.equal(this.ip, that.ip);
         }
         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 d7687ac..bba9dc9 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
@@ -43,13 +43,20 @@
     VlanId vlan();
 
     /**
-     * Returns the location of the host on the network edge.
+     * Returns the most recent location of the host on the network edge.
      *
-     * @return the network location
+     * @return the most recent host location
      */
     HostLocation location();
 
     /**
+     * Returns all locations of the host on the network edge.
+     *
+     * @return all host locations
+     */
+    Set<HostLocation> locations();
+
+    /**
      * Returns the IP address associated with this host's MAC.
      *
      * @return host IP address