Refactored the HostStore to allow multiple MAC addresses bound to a single port

Change-Id: Icd3b2e483b15486251ac1cca107478a012d1a3e7
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/HostToInterfaceAdaptor.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/HostToInterfaceAdaptor.java
index f9d6951..604d12d 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/HostToInterfaceAdaptor.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/HostToInterfaceAdaptor.java
@@ -53,11 +53,13 @@
     public Interface getInterface(ConnectPoint connectPoint) {
         checkNotNull(connectPoint);
 
-        PortAddresses portAddresses =
+        Set<PortAddresses> portAddresses =
                 hostService.getAddressBindingsForPort(connectPoint);
 
-        if (!portAddresses.ipAddresses().isEmpty()) {
-            return new Interface(portAddresses);
+        for (PortAddresses addresses : portAddresses) {
+            if (addresses.connectPoint().equals(connectPoint)) {
+                return new Interface(addresses);
+            }
         }
 
         return null;
diff --git a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/HostToInterfaceAdaptorTest.java b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/HostToInterfaceAdaptorTest.java
index 2a65616..9e31389 100644
--- a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/HostToInterfaceAdaptorTest.java
+++ b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/HostToInterfaceAdaptorTest.java
@@ -23,6 +23,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
 
@@ -63,10 +64,6 @@
     private static final ConnectPoint NON_EXISTENT_CP = new ConnectPoint(
             DeviceId.deviceId("doesnotexist"), PortNumber.portNumber(1));
 
-    private static final PortAddresses DEFAULT_PA = new PortAddresses(
-            NON_EXISTENT_CP, null, null);
-
-
     @Before
     public void setUp() throws Exception {
         hostService = createMock(HostService.class);
@@ -123,7 +120,8 @@
             MacAddress mac) {
         PortAddresses pa = new PortAddresses(cp, ipAddresses, mac);
         portAddresses.add(pa);
-        expect(hostService.getAddressBindingsForPort(cp)).andReturn(pa).anyTimes();
+        expect(hostService.getAddressBindingsForPort(cp)).andReturn(
+                Collections.singleton(pa)).anyTimes();
 
         Interface intf = new Interface(cp, ipAddresses, mac);
         interfaces.put(cp, intf);
@@ -158,7 +156,7 @@
         // Try and get an interface for a connect point with no addresses
         reset(hostService);
         expect(hostService.getAddressBindingsForPort(NON_EXISTENT_CP))
-                .andReturn(DEFAULT_PA).anyTimes();
+                .andReturn(Collections.<PortAddresses>emptySet()).anyTimes();
         replay(hostService);
 
         assertNull(adaptor.getInterface(NON_EXISTENT_CP));
diff --git a/core/api/src/main/java/org/onlab/onos/net/host/HostAdminService.java b/core/api/src/main/java/org/onlab/onos/net/host/HostAdminService.java
index f421fd8..c51a847 100644
--- a/core/api/src/main/java/org/onlab/onos/net/host/HostAdminService.java
+++ b/core/api/src/main/java/org/onlab/onos/net/host/HostAdminService.java
@@ -34,11 +34,7 @@
      * Binds IP and MAC addresses to the given connection point.
      * <p>
      * The addresses are added to the set of addresses already bound to the
-     * connection point. If any of the fields in addresses is null, no change
-     * is made to the corresponding addresses in the store.
-     * {@link #unbindAddressesFromPort(PortAddresses)} must be use to unbind
-     * addresses that have previously been bound.
-     * </p>
+     * connection point.
      *
      * @param addresses address object containing addresses to add and the port
      * to add them to
diff --git a/core/api/src/main/java/org/onlab/onos/net/host/HostService.java b/core/api/src/main/java/org/onlab/onos/net/host/HostService.java
index aa31459..7f7be50 100644
--- a/core/api/src/main/java/org/onlab/onos/net/host/HostService.java
+++ b/core/api/src/main/java/org/onlab/onos/net/host/HostService.java
@@ -135,7 +135,7 @@
      * @param connectPoint the connection point to retrieve address bindings for
      * @return addresses bound to the port
      */
-    PortAddresses getAddressBindingsForPort(ConnectPoint connectPoint);
+    Set<PortAddresses> getAddressBindingsForPort(ConnectPoint connectPoint);
 
     /**
      * Adds the specified host listener.
diff --git a/core/api/src/main/java/org/onlab/onos/net/host/HostStore.java b/core/api/src/main/java/org/onlab/onos/net/host/HostStore.java
index a6bf96d..0316dcf 100644
--- a/core/api/src/main/java/org/onlab/onos/net/host/HostStore.java
+++ b/core/api/src/main/java/org/onlab/onos/net/host/HostStore.java
@@ -15,6 +15,8 @@
  */
 package org.onlab.onos.net.host;
 
+import java.util.Set;
+
 import org.onlab.onos.net.ConnectPoint;
 import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.net.Host;
@@ -25,8 +27,6 @@
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
 
-import java.util.Set;
-
 /**
  * Manages inventory of end-station hosts; not intended for direct use.
  */
@@ -153,5 +153,5 @@
      *                     for
      * @return address information for the connection point
      */
-    PortAddresses getAddressBindingsForPort(ConnectPoint connectPoint);
+    Set<PortAddresses> getAddressBindingsForPort(ConnectPoint connectPoint);
 }
diff --git a/core/api/src/test/java/org/onlab/onos/net/host/HostServiceAdapter.java b/core/api/src/test/java/org/onlab/onos/net/host/HostServiceAdapter.java
index a0a0e36..03a8a43 100644
--- a/core/api/src/test/java/org/onlab/onos/net/host/HostServiceAdapter.java
+++ b/core/api/src/test/java/org/onlab/onos/net/host/HostServiceAdapter.java
@@ -95,7 +95,7 @@
     }
 
     @Override
-    public PortAddresses getAddressBindingsForPort(ConnectPoint connectPoint) {
+    public Set<PortAddresses> getAddressBindingsForPort(ConnectPoint connectPoint) {
         return null;
     }
 
diff --git a/core/net/src/main/java/org/onlab/onos/net/host/impl/HostManager.java b/core/net/src/main/java/org/onlab/onos/net/host/impl/HostManager.java
index 762bae0..5b5e5b7 100644
--- a/core/net/src/main/java/org/onlab/onos/net/host/impl/HostManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/host/impl/HostManager.java
@@ -207,7 +207,7 @@
     }
 
     @Override
-    public PortAddresses getAddressBindingsForPort(ConnectPoint connectPoint) {
+    public Set<PortAddresses> getAddressBindingsForPort(ConnectPoint connectPoint) {
         return store.getAddressBindingsForPort(connectPoint);
     }
 
diff --git a/core/net/src/main/java/org/onlab/onos/net/host/impl/HostMonitor.java b/core/net/src/main/java/org/onlab/onos/net/host/impl/HostMonitor.java
index a6af018..d7299e8 100644
--- a/core/net/src/main/java/org/onlab/onos/net/host/impl/HostMonitor.java
+++ b/core/net/src/main/java/org/onlab/onos/net/host/impl/HostMonitor.java
@@ -175,13 +175,15 @@
         for (Device device : deviceService.getDevices()) {
             for (Port port : deviceService.getPorts(device.id())) {
                 ConnectPoint cp = new ConnectPoint(device.id(), port.number());
-                PortAddresses portAddresses =
+                Set<PortAddresses> portAddressSet =
                     hostManager.getAddressBindingsForPort(cp);
 
-                for (InterfaceIpAddress ia : portAddresses.ipAddresses()) {
-                    if (ia.subnetAddress().contains(targetIp)) {
-                        sendProbe(device.id(), port, targetIp,
-                                  ia.ipAddress(), portAddresses.mac());
+                for (PortAddresses portAddresses : portAddressSet) {
+                    for (InterfaceIpAddress ia : portAddresses.ipAddresses()) {
+                        if (ia.subnetAddress().contains(targetIp)) {
+                            sendProbe(device.id(), port, targetIp,
+                                      ia.ipAddress(), portAddresses.mac());
+                        }
                     }
                 }
             }
diff --git a/core/net/src/main/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManager.java b/core/net/src/main/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManager.java
index 0aae62d..49528d0 100644
--- a/core/net/src/main/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManager.java
@@ -134,14 +134,16 @@
             IpAddress target =
                 IpAddress.valueOf(IpAddress.Version.INET,
                                   arp.getTargetProtocolAddress());
-            PortAddresses addresses =
+            Set<PortAddresses> addressSet =
                 hostService.getAddressBindingsForPort(inPort);
 
-            for (InterfaceIpAddress ia : addresses.ipAddresses()) {
-                if (ia.ipAddress().equals(target)) {
-                    Ethernet arpReply =
-                        buildArpReply(ia.ipAddress(), addresses.mac(), eth);
-                    sendTo(arpReply, inPort);
+            for (PortAddresses addresses : addressSet) {
+                for (InterfaceIpAddress ia : addresses.ipAddresses()) {
+                    if (ia.ipAddress().equals(target)) {
+                        Ethernet arpReply =
+                            buildArpReply(ia.ipAddress(), addresses.mac(), eth);
+                        sendTo(arpReply, inPort);
+                    }
                 }
             }
             return;
@@ -244,7 +246,7 @@
         // TODO: Is this sufficient to identify outside-facing ports: just
         // having IP addresses on a port?
         //
-        return !hostService.getAddressBindingsForPort(port).ipAddresses().isEmpty();
+        return !hostService.getAddressBindingsForPort(port).isEmpty();
     }
 
     @Override
diff --git a/core/net/src/test/java/org/onlab/onos/net/host/impl/HostManagerTest.java b/core/net/src/test/java/org/onlab/onos/net/host/impl/HostManagerTest.java
index cbc9cf1..6a058ab 100644
--- a/core/net/src/test/java/org/onlab/onos/net/host/impl/HostManagerTest.java
+++ b/core/net/src/test/java/org/onlab/onos/net/host/impl/HostManagerTest.java
@@ -234,10 +234,10 @@
             new PortAddresses(CP1, Sets.newHashSet(IA1, IA2), MAC1);
 
         mgr.bindAddressesToPort(add1);
-        PortAddresses storedAddresses = mgr.getAddressBindingsForPort(CP1);
+        Set<PortAddresses> storedAddresses = mgr.getAddressBindingsForPort(CP1);
 
-        assertTrue(add1.ipAddresses().equals(storedAddresses.ipAddresses()));
-        assertTrue(add1.mac().equals(storedAddresses.mac()));
+        assertEquals(1, storedAddresses.size());
+        assertTrue(storedAddresses.contains(add1));
 
         // Add some more addresses and check that they're added correctly
         PortAddresses add2 =
@@ -246,18 +246,19 @@
         mgr.bindAddressesToPort(add2);
         storedAddresses = mgr.getAddressBindingsForPort(CP1);
 
-        assertTrue(storedAddresses.ipAddresses().equals(
-                Sets.newHashSet(IA1, IA2, IA3)));
-        assertTrue(storedAddresses.mac().equals(MAC1));
+        assertEquals(2, storedAddresses.size());
+        assertTrue(storedAddresses.contains(add1));
+        assertTrue(storedAddresses.contains(add2));
 
         PortAddresses add3 = new PortAddresses(CP1, null, MAC2);
 
         mgr.bindAddressesToPort(add3);
         storedAddresses = mgr.getAddressBindingsForPort(CP1);
 
-        assertTrue(storedAddresses.ipAddresses().equals(
-                Sets.newHashSet(IA1, IA2, IA3)));
-        assertTrue(storedAddresses.mac().equals(MAC2));
+        assertEquals(3, storedAddresses.size());
+        assertTrue(storedAddresses.contains(add1));
+        assertTrue(storedAddresses.contains(add2));
+        assertTrue(storedAddresses.contains(add3));
     }
 
     @Test
@@ -266,10 +267,10 @@
             new PortAddresses(CP1, Sets.newHashSet(IA1, IA2), MAC1);
 
         mgr.bindAddressesToPort(add1);
-        PortAddresses storedAddresses = mgr.getAddressBindingsForPort(CP1);
+        Set<PortAddresses> storedAddresses = mgr.getAddressBindingsForPort(CP1);
 
-        assertTrue(storedAddresses.ipAddresses().size() == 2);
-        assertNotNull(storedAddresses.mac());
+        assertEquals(1, storedAddresses.size());
+        assertTrue(storedAddresses.contains(add1));
 
         PortAddresses rem1 =
             new PortAddresses(CP1, Sets.newHashSet(IA1), null);
@@ -277,25 +278,15 @@
         mgr.unbindAddressesFromPort(rem1);
         storedAddresses = mgr.getAddressBindingsForPort(CP1);
 
-        assertTrue(storedAddresses.ipAddresses().equals(Sets.newHashSet(IA2)));
-        assertTrue(storedAddresses.mac().equals(MAC1));
+        // It shouldn't have been removed because it didn't match the originally
+        // submitted address object
+        assertEquals(1, storedAddresses.size());
+        assertTrue(storedAddresses.contains(add1));
 
-        PortAddresses rem2 = new PortAddresses(CP1, null, MAC1);
-
-        mgr.unbindAddressesFromPort(rem2);
+        mgr.unbindAddressesFromPort(add1);
         storedAddresses = mgr.getAddressBindingsForPort(CP1);
 
-        assertTrue(storedAddresses.ipAddresses().equals(Sets.newHashSet(IA2)));
-        assertNull(storedAddresses.mac());
-
-        PortAddresses rem3 =
-            new PortAddresses(CP1, Sets.newHashSet(IA2), MAC1);
-
-        mgr.unbindAddressesFromPort(rem3);
-        storedAddresses = mgr.getAddressBindingsForPort(CP1);
-
-        assertTrue(storedAddresses.ipAddresses().isEmpty());
-        assertNull(storedAddresses.mac());
+        assertTrue(storedAddresses.isEmpty());
     }
 
     @Test
@@ -304,16 +295,15 @@
             new PortAddresses(CP1, Sets.newHashSet(IA1, IA2), MAC1);
 
         mgr.bindAddressesToPort(add1);
-        PortAddresses storedAddresses = mgr.getAddressBindingsForPort(CP1);
+        Set<PortAddresses> storedAddresses = mgr.getAddressBindingsForPort(CP1);
 
-        assertTrue(storedAddresses.ipAddresses().size() == 2);
-        assertNotNull(storedAddresses.mac());
+        assertEquals(1, storedAddresses.size());
+        assertTrue(storedAddresses.contains(add1));
 
         mgr.clearAddresses(CP1);
         storedAddresses = mgr.getAddressBindingsForPort(CP1);
 
-        assertTrue(storedAddresses.ipAddresses().isEmpty());
-        assertNull(storedAddresses.mac());
+        assertTrue(storedAddresses.isEmpty());
     }
 
     @Test
@@ -322,12 +312,10 @@
             new PortAddresses(CP1, Sets.newHashSet(IA1, IA2), MAC1);
 
         mgr.bindAddressesToPort(add1);
-        PortAddresses storedAddresses = mgr.getAddressBindingsForPort(CP1);
+        Set<PortAddresses> storedAddresses = mgr.getAddressBindingsForPort(CP1);
 
-        assertTrue(storedAddresses.connectPoint().equals(CP1));
-        assertTrue(storedAddresses.ipAddresses().equals(
-                        Sets.newHashSet(IA1, IA2)));
-        assertTrue(storedAddresses.mac().equals(MAC1));
+        assertEquals(1, storedAddresses.size());
+        assertTrue(storedAddresses.contains(add1));
     }
 
     @Test
diff --git a/core/net/src/test/java/org/onlab/onos/net/host/impl/HostMonitorTest.java b/core/net/src/test/java/org/onlab/onos/net/host/impl/HostMonitorTest.java
index 654cab1..a8febd7 100644
--- a/core/net/src/test/java/org/onlab/onos/net/host/impl/HostMonitorTest.java
+++ b/core/net/src/test/java/org/onlab/onos/net/host/impl/HostMonitorTest.java
@@ -20,7 +20,9 @@
 import static org.easymock.EasyMock.expectLastCall;
 import static org.easymock.EasyMock.replay;
 import static org.easymock.EasyMock.verify;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -130,7 +132,7 @@
         expect(hostManager.getHostsByIp(TARGET_IP_ADDR))
                 .andReturn(Collections.<Host>emptySet()).anyTimes();
         expect(hostManager.getAddressBindingsForPort(cp))
-                .andReturn(pa).anyTimes();
+                .andReturn(Collections.singleton(pa)).anyTimes();
         replay(hostManager);
 
         TestPacketService packetService = new TestPacketService();
diff --git a/core/net/src/test/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManagerTest.java b/core/net/src/test/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManagerTest.java
index e82151e..8beac4a 100644
--- a/core/net/src/test/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManagerTest.java
+++ b/core/net/src/test/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManagerTest.java
@@ -19,7 +19,10 @@
 import static org.easymock.EasyMock.createMock;
 import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.replay;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -207,13 +210,18 @@
             IpAddress addr2 = IpAddress.valueOf("10.0." + (2 * i) + ".1");
             InterfaceIpAddress ia1 = new InterfaceIpAddress(addr1, prefix1);
             InterfaceIpAddress ia2 = new InterfaceIpAddress(addr2, prefix2);
-            PortAddresses pa =
-                new PortAddresses(cp, Sets.newHashSet(ia1, ia2),
-                                  MacAddress.valueOf(i));
-            addresses.add(pa);
+            PortAddresses pa1 =
+                new PortAddresses(cp, Sets.newHashSet(ia1),
+                                  MacAddress.valueOf(2 * i - 1));
+            PortAddresses pa2 =
+                    new PortAddresses(cp, Sets.newHashSet(ia2),
+                                      MacAddress.valueOf(2 * i));
+
+            addresses.add(pa1);
+            addresses.add(pa2);
 
             expect(hostService.getAddressBindingsForPort(cp))
-                    .andReturn(pa).anyTimes();
+                    .andReturn(Sets.newHashSet(pa1, pa2)).anyTimes();
         }
 
         expect(hostService.getAddressBindings()).andReturn(addresses).anyTimes();
@@ -222,7 +230,7 @@
             ConnectPoint cp = new ConnectPoint(getDeviceId(i + NUM_ADDRESS_PORTS),
                     P1);
             expect(hostService.getAddressBindingsForPort(cp))
-                    .andReturn(new PortAddresses(cp, null, null)).anyTimes();
+                    .andReturn(Collections.<PortAddresses>emptySet()).anyTimes();
         }
     }
 
@@ -339,7 +347,8 @@
         IpAddress theirIp = IpAddress.valueOf("10.0.1.254");
         IpAddress ourFirstIp = IpAddress.valueOf("10.0.1.1");
         IpAddress ourSecondIp = IpAddress.valueOf("10.0.2.1");
-        MacAddress ourMac = MacAddress.valueOf(1L);
+        MacAddress firstMac = MacAddress.valueOf(1L);
+        MacAddress secondMac = MacAddress.valueOf(2L);
 
         Host requestor = new DefaultHost(PID, HID2, MAC2, VLAN1, LOC1,
                 Collections.singleton(theirIp));
@@ -352,7 +361,7 @@
         proxyArp.reply(arpRequest, LOC1);
 
         assertEquals(1, packetService.packets.size());
-        Ethernet arpReply = buildArp(ARP.OP_REPLY, ourMac, MAC2, ourFirstIp, theirIp);
+        Ethernet arpReply = buildArp(ARP.OP_REPLY, firstMac, MAC2, ourFirstIp, theirIp);
         verifyPacketOut(arpReply, LOC1, packetService.packets.get(0));
 
         // Test a request for the second address on that port
@@ -362,7 +371,7 @@
         proxyArp.reply(arpRequest, LOC1);
 
         assertEquals(1, packetService.packets.size());
-        arpReply = buildArp(ARP.OP_REPLY, ourMac, MAC2, ourSecondIp, theirIp);
+        arpReply = buildArp(ARP.OP_REPLY, secondMac, MAC2, ourSecondIp, theirIp);
         verifyPacketOut(arpReply, LOC1, packetService.packets.get(0));
     }
 
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/GossipHostStore.java b/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/GossipHostStore.java
index 8029e27..30a73e0 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/GossipHostStore.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/host/impl/GossipHostStore.java
@@ -15,12 +15,25 @@
  */
 package org.onlab.onos.store.host.impl;
 
-import com.google.common.collect.FluentIterable;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.onos.cluster.ControllerNodeToNodeId.toNodeId;
+import static org.onlab.onos.net.host.HostEvent.Type.HOST_ADDED;
+import static org.onlab.onos.net.host.HostEvent.Type.HOST_MOVED;
+import static org.onlab.onos.net.host.HostEvent.Type.HOST_REMOVED;
+import static org.onlab.onos.net.host.HostEvent.Type.HOST_UPDATED;
+import static org.onlab.util.Tools.namedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
 
 import org.apache.commons.lang3.RandomUtils;
 import org.apache.felix.scr.annotations.Activate;
@@ -45,7 +58,6 @@
 import org.onlab.onos.net.host.HostEvent;
 import org.onlab.onos.net.host.HostStore;
 import org.onlab.onos.net.host.HostStoreDelegate;
-import org.onlab.onos.net.host.InterfaceIpAddress;
 import org.onlab.onos.net.host.PortAddresses;
 import org.onlab.onos.net.provider.ProviderId;
 import org.onlab.onos.store.AbstractStore;
@@ -63,21 +75,13 @@
 import org.onlab.util.KryoNamespace;
 import org.slf4j.Logger;
 
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.Map.Entry;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
-import static org.onlab.onos.cluster.ControllerNodeToNodeId.toNodeId;
-import static org.onlab.onos.net.host.HostEvent.Type.*;
-import static org.onlab.util.Tools.namedThreads;
-import static org.slf4j.LoggerFactory.getLogger;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
 
 //TODO: multi-provider, annotation not supported.
 /**
@@ -100,8 +104,9 @@
     // Hosts tracked by their location
     private final Multimap<ConnectPoint, Host> locations = HashMultimap.create();
 
-    private final Map<ConnectPoint, PortAddresses> portAddresses =
-            new ConcurrentHashMap<>();
+    private final SetMultimap<ConnectPoint, PortAddresses> portAddresses =
+            Multimaps.synchronizedSetMultimap(
+                    HashMultimap.<ConnectPoint, PortAddresses>create());
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected HostClockService hostClockService;
@@ -343,77 +348,37 @@
 
     @Override
     public void updateAddressBindings(PortAddresses addresses) {
-        synchronized (portAddresses) {
-            PortAddresses existing = portAddresses.get(addresses.connectPoint());
-            if (existing == null) {
-                portAddresses.put(addresses.connectPoint(), addresses);
-            } else {
-                Set<InterfaceIpAddress> union =
-                    Sets.union(existing.ipAddresses(),
-                               addresses.ipAddresses()).immutableCopy();
-
-                MacAddress newMac = (addresses.mac() == null) ? existing.mac()
-                        : addresses.mac();
-
-                PortAddresses newAddresses =
-                        new PortAddresses(addresses.connectPoint(), union, newMac);
-
-                portAddresses.put(newAddresses.connectPoint(), newAddresses);
-            }
-        }
+        portAddresses.put(addresses.connectPoint(), addresses);
     }
 
     @Override
     public void removeAddressBindings(PortAddresses addresses) {
-        synchronized (portAddresses) {
-            PortAddresses existing = portAddresses.get(addresses.connectPoint());
-            if (existing != null) {
-                Set<InterfaceIpAddress> difference =
-                    Sets.difference(existing.ipAddresses(),
-                                    addresses.ipAddresses()).immutableCopy();
-
-                // If they removed the existing mac, set the new mac to null.
-                // Otherwise, keep the existing mac.
-                MacAddress newMac = existing.mac();
-                if (addresses.mac() != null && addresses.mac().equals(existing.mac())) {
-                    newMac = null;
-                }
-
-                PortAddresses newAddresses =
-                        new PortAddresses(addresses.connectPoint(), difference, newMac);
-
-                portAddresses.put(newAddresses.connectPoint(), newAddresses);
-            }
-        }
+        portAddresses.remove(addresses.connectPoint(), addresses);
     }
 
     @Override
     public void clearAddressBindings(ConnectPoint connectPoint) {
-        synchronized (portAddresses) {
-            portAddresses.remove(connectPoint);
-        }
+        portAddresses.removeAll(connectPoint);
     }
 
     @Override
     public Set<PortAddresses> getAddressBindings() {
         synchronized (portAddresses) {
-            return new HashSet<>(portAddresses.values());
+            return ImmutableSet.copyOf(portAddresses.values());
         }
     }
 
     @Override
-    public PortAddresses getAddressBindingsForPort(ConnectPoint connectPoint) {
-        PortAddresses addresses;
-
+    public Set<PortAddresses> getAddressBindingsForPort(ConnectPoint connectPoint) {
         synchronized (portAddresses) {
-            addresses = portAddresses.get(connectPoint);
-        }
+            Set<PortAddresses> addresses = portAddresses.get(connectPoint);
 
-        if (addresses == null) {
-            addresses = new PortAddresses(connectPoint, null, null);
+            if (addresses == null) {
+                return Collections.emptySet();
+            } else {
+                return ImmutableSet.copyOf(addresses);
+            }
         }
-
-        return addresses;
     }
 
     // Auxiliary extension to allow location to mutate.
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleHostStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleHostStore.java
index 6c27ea8..2f8fbd2 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleHostStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleHostStore.java
@@ -15,10 +15,18 @@
  */
 package org.onlab.onos.store.trivial.impl;
 
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
+import static org.onlab.onos.net.host.HostEvent.Type.HOST_ADDED;
+import static org.onlab.onos.net.host.HostEvent.Type.HOST_MOVED;
+import static org.onlab.onos.net.host.HostEvent.Type.HOST_REMOVED;
+import static org.onlab.onos.net.host.HostEvent.Type.HOST_UPDATED;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -34,7 +42,6 @@
 import org.onlab.onos.net.host.HostEvent;
 import org.onlab.onos.net.host.HostStore;
 import org.onlab.onos.net.host.HostStoreDelegate;
-import org.onlab.onos.net.host.InterfaceIpAddress;
 import org.onlab.onos.net.host.PortAddresses;
 import org.onlab.onos.net.provider.ProviderId;
 import org.onlab.onos.store.AbstractStore;
@@ -43,13 +50,11 @@
 import org.onlab.packet.VlanId;
 import org.slf4j.Logger;
 
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-import static org.onlab.onos.net.host.HostEvent.Type.*;
-import static org.slf4j.LoggerFactory.getLogger;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
 
 // TODO: multi-provider, annotation not supported.
 /**
@@ -70,8 +75,9 @@
     // Hosts tracked by their location
     private final Multimap<ConnectPoint, Host> locations = HashMultimap.create();
 
-    private final Map<ConnectPoint, PortAddresses> portAddresses =
-            new ConcurrentHashMap<>();
+    private final SetMultimap<ConnectPoint, PortAddresses> portAddresses =
+            Multimaps.synchronizedSetMultimap(
+                    HashMultimap.<ConnectPoint, PortAddresses>create());
 
     @Activate
     public void activate() {
@@ -213,77 +219,37 @@
 
     @Override
     public void updateAddressBindings(PortAddresses addresses) {
-        synchronized (portAddresses) {
-            PortAddresses existing = portAddresses.get(addresses.connectPoint());
-            if (existing == null) {
-                portAddresses.put(addresses.connectPoint(), addresses);
-            } else {
-                Set<InterfaceIpAddress> union =
-                    Sets.union(existing.ipAddresses(),
-                               addresses.ipAddresses()).immutableCopy();
-
-                MacAddress newMac = (addresses.mac() == null) ? existing.mac()
-                        : addresses.mac();
-
-                PortAddresses newAddresses =
-                        new PortAddresses(addresses.connectPoint(), union, newMac);
-
-                portAddresses.put(newAddresses.connectPoint(), newAddresses);
-            }
-        }
+        portAddresses.put(addresses.connectPoint(), addresses);
     }
 
     @Override
     public void removeAddressBindings(PortAddresses addresses) {
-        synchronized (portAddresses) {
-            PortAddresses existing = portAddresses.get(addresses.connectPoint());
-            if (existing != null) {
-                Set<InterfaceIpAddress> difference =
-                        Sets.difference(existing.ipAddresses(),
-                                        addresses.ipAddresses()).immutableCopy();
-
-                // If they removed the existing mac, set the new mac to null.
-                // Otherwise, keep the existing mac.
-                MacAddress newMac = existing.mac();
-                if (addresses.mac() != null && addresses.mac().equals(existing.mac())) {
-                    newMac = null;
-                }
-
-                PortAddresses newAddresses =
-                        new PortAddresses(addresses.connectPoint(), difference, newMac);
-
-                portAddresses.put(newAddresses.connectPoint(), newAddresses);
-            }
-        }
+        portAddresses.remove(addresses.connectPoint(), addresses);
     }
 
     @Override
     public void clearAddressBindings(ConnectPoint connectPoint) {
-        synchronized (portAddresses) {
-            portAddresses.remove(connectPoint);
-        }
+        portAddresses.removeAll(connectPoint);
     }
 
     @Override
     public Set<PortAddresses> getAddressBindings() {
         synchronized (portAddresses) {
-            return new HashSet<>(portAddresses.values());
+            return ImmutableSet.copyOf(portAddresses.values());
         }
     }
 
     @Override
-    public PortAddresses getAddressBindingsForPort(ConnectPoint connectPoint) {
-        PortAddresses addresses;
-
+    public Set<PortAddresses> getAddressBindingsForPort(ConnectPoint connectPoint) {
         synchronized (portAddresses) {
-            addresses = portAddresses.get(connectPoint);
-        }
+            Set<PortAddresses> addresses = portAddresses.get(connectPoint);
 
-        if (addresses == null) {
-            addresses = new PortAddresses(connectPoint, null, null);
+            if (addresses == null) {
+                return Collections.emptySet();
+            } else {
+                return ImmutableSet.copyOf(addresses);
+            }
         }
-
-        return addresses;
     }
 
     // Auxiliary extension to allow location to mutate.