Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next
diff --git a/core/api/src/main/java/org/onlab/onos/cluster/MastershipService.java b/core/api/src/main/java/org/onlab/onos/cluster/MastershipService.java
index be91609..d417516 100644
--- a/core/api/src/main/java/org/onlab/onos/cluster/MastershipService.java
+++ b/core/api/src/main/java/org/onlab/onos/cluster/MastershipService.java
@@ -56,7 +56,8 @@
     Set<DeviceId> getDevicesOf(NodeId nodeId);
 
     /**
-     * Returns the mastership term service for getting term information.
+     * Returns the mastership term service for getting read-only
+     * term information.
      *
      * @return the MastershipTermService for this mastership manager
      */
diff --git a/core/api/src/main/java/org/onlab/onos/cluster/MastershipStore.java b/core/api/src/main/java/org/onlab/onos/cluster/MastershipStore.java
index be5d873..bedc5e9 100644
--- a/core/api/src/main/java/org/onlab/onos/cluster/MastershipStore.java
+++ b/core/api/src/main/java/org/onlab/onos/cluster/MastershipStore.java
@@ -64,4 +64,14 @@
      * @return the current master's ID and the term value for device, or null
      */
     MastershipTerm getTermFor(DeviceId deviceId);
+
+    /**
+     * Revokes a controller instance's mastership over a device and hands
+     * over mastership to another controller instance.
+     *
+     * @param nodeId   the controller instance identifier
+     * @param deviceId device to revoke mastership for
+     * @return a mastership event
+     */
+    MastershipEvent unsetMaster(NodeId nodeId, DeviceId deviceId);
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/device/DeviceStore.java b/core/api/src/main/java/org/onlab/onos/net/device/DeviceStore.java
index c84aac8..fdb9f6d 100644
--- a/core/api/src/main/java/org/onlab/onos/net/device/DeviceStore.java
+++ b/core/api/src/main/java/org/onlab/onos/net/device/DeviceStore.java
@@ -48,6 +48,7 @@
     DeviceEvent createOrUpdateDevice(ProviderId providerId, DeviceId deviceId,
                                      DeviceDescription deviceDescription);
 
+    // TODO: We may need to enforce that ancillary cannot interfere this state
     /**
      * Removes the specified infrastructure device.
      *
@@ -60,22 +61,24 @@
      * Updates the ports of the specified infrastructure device using the given
      * list of port descriptions. The list is assumed to be comprehensive.
      *
+     * @param providerId        provider identifier
      * @param deviceId         device identifier
      * @param portDescriptions list of port descriptions
      * @return ready to send events describing what occurred; empty list if no change
      */
-    List<DeviceEvent> updatePorts(DeviceId deviceId,
+    List<DeviceEvent> updatePorts(ProviderId providerId, DeviceId deviceId,
                                   List<PortDescription> portDescriptions);
 
     /**
      * Updates the port status of the specified infrastructure device using the
      * given port description.
      *
+     * @param providerId        provider identifier
      * @param deviceId        device identifier
      * @param portDescription port description
      * @return ready to send event describing what occurred; null if no change
      */
-    DeviceEvent updatePortStatus(DeviceId deviceId,
+    DeviceEvent updatePortStatus(ProviderId providerId, DeviceId deviceId,
                                  PortDescription portDescription);
 
     /**
diff --git a/core/api/src/main/java/org/onlab/onos/net/provider/AbstractProviderRegistry.java b/core/api/src/main/java/org/onlab/onos/net/provider/AbstractProviderRegistry.java
index a4095ea..d59bfd2 100644
--- a/core/api/src/main/java/org/onlab/onos/net/provider/AbstractProviderRegistry.java
+++ b/core/api/src/main/java/org/onlab/onos/net/provider/AbstractProviderRegistry.java
@@ -35,10 +35,22 @@
     public synchronized S register(P provider) {
         checkNotNull(provider, "Provider cannot be null");
         checkState(!services.containsKey(provider.id()), "Provider %s already registered", provider.id());
+
+        // If the provider is a primary one, check for a conflict.
+        ProviderId pid = provider.id();
+        checkState(pid.isAncillary() || !providersByScheme.containsKey(pid.scheme()),
+                   "A primary provider with id %s is already registered",
+                   providersByScheme.get(pid.scheme()));
+
         S service = createProviderService(provider);
         services.put(provider.id(), service);
         providers.put(provider.id(), provider);
-        // FIXME populate scheme look-up
+
+        // Register the provider by URI scheme only if it is not ancillary.
+        if (!pid.isAncillary()) {
+            providersByScheme.put(pid.scheme(), provider);
+        }
+
         return service;
     }
 
diff --git a/core/api/src/main/java/org/onlab/onos/net/provider/ProviderId.java b/core/api/src/main/java/org/onlab/onos/net/provider/ProviderId.java
index 725748a..7d314e8 100644
--- a/core/api/src/main/java/org/onlab/onos/net/provider/ProviderId.java
+++ b/core/api/src/main/java/org/onlab/onos/net/provider/ProviderId.java
@@ -11,6 +11,7 @@
 
     private final String scheme;
     private final String id;
+    private final boolean ancillary;
 
     /**
      * Creates a new provider identifier from the specified string.
@@ -21,8 +22,22 @@
      * @param id     string identifier
      */
     public ProviderId(String scheme, String id) {
+        this(scheme, id, false);
+    }
+
+    /**
+     * Creates a new provider identifier from the specified string.
+     * The providers are expected to follow the reverse DNS convention, e.g.
+     * {@code org.onlab.onos.provider.of.device}
+     *
+     * @param scheme device URI scheme to which this provider is bound, e.g. "of", "snmp"
+     * @param id     string identifier
+     * @param ancillary ancillary provider indicator
+     */
+    public ProviderId(String scheme, String id, boolean ancillary) {
         this.scheme = scheme;
         this.id = id;
+        this.ancillary = ancillary;
     }
 
     /**
@@ -35,6 +50,15 @@
     }
 
     /**
+     * Indicates whether the provider id belongs to an ancillary provider.
+     *
+     * @return true for ancillary; false for primary provider
+     */
+    public boolean isAncillary() {
+        return ancillary;
+    }
+
+    /**
      * Returns the device URI scheme specific id portion.
      *
      * @return id
@@ -56,14 +80,16 @@
         if (obj instanceof ProviderId) {
             final ProviderId other = (ProviderId) obj;
             return Objects.equals(this.scheme, other.scheme) &&
-                    Objects.equals(this.id, other.id);
+                    Objects.equals(this.id, other.id) &&
+                    this.ancillary == other.ancillary;
         }
         return false;
     }
 
     @Override
     public String toString() {
-        return toStringHelper(this).add("scheme", scheme).add("id", id).toString();
+        return toStringHelper(this).add("scheme", scheme).add("id", id)
+                .add("ancillary", ancillary).toString();
     }
 
 }
diff --git a/core/api/src/test/java/org/onlab/onos/cluster/MastershipTermTest.java b/core/api/src/test/java/org/onlab/onos/cluster/MastershipTermTest.java
new file mode 100644
index 0000000..139c695
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/cluster/MastershipTermTest.java
@@ -0,0 +1,32 @@
+package org.onlab.onos.cluster;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+import com.google.common.testing.EqualsTester;
+
+public class MastershipTermTest {
+
+    private static final NodeId N1 = new NodeId("foo");
+    private static final NodeId N2 = new NodeId("bar");
+
+    private static final MastershipTerm TERM1 = MastershipTerm.of(N1, 0);
+    private static final MastershipTerm TERM2 = MastershipTerm.of(N2, 1);
+    private static final MastershipTerm TERM3 = MastershipTerm.of(N2, 1);
+    private static final MastershipTerm TERM4 = MastershipTerm.of(N1, 1);
+
+    @Test
+    public void basics() {
+        assertEquals("incorrect term number", 0, TERM1.termNumber());
+        assertEquals("incorrect master", new NodeId("foo"), TERM1.master());
+    }
+
+    @Test
+    public void testEquality() {
+        new EqualsTester().addEqualityGroup(MastershipTerm.of(N1, 0), TERM1)
+        .addEqualityGroup(TERM2, TERM3)
+        .addEqualityGroup(TERM4);
+    }
+
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/provider/AbstractProviderRegistryTest.java b/core/api/src/test/java/org/onlab/onos/net/provider/AbstractProviderRegistryTest.java
index 37bee71..3ecd90d 100644
--- a/core/api/src/test/java/org/onlab/onos/net/provider/AbstractProviderRegistryTest.java
+++ b/core/api/src/test/java/org/onlab/onos/net/provider/AbstractProviderRegistryTest.java
@@ -35,7 +35,7 @@
         assertThat("provider not found", registry.getProviders().contains(fooId));
         assertEquals("incorrect provider", psFoo.provider(), pFoo);
 
-        ProviderId barId = new ProviderId("of", "bar");
+        ProviderId barId = new ProviderId("snmp", "bar");
         TestProvider pBar = new TestProvider(barId);
         TestProviderService psBar = registry.register(pBar);
         assertEquals("incorrect provider count", 2, registry.getProviders().size());
@@ -49,6 +49,16 @@
         assertThat("provider not found", registry.getProviders().contains(barId));
     }
 
+    @Test
+    public void ancillaryProviders() {
+        TestProviderRegistry registry = new TestProviderRegistry();
+        TestProvider pFoo = new TestProvider(new ProviderId("of", "foo"));
+        TestProvider pBar = new TestProvider(new ProviderId("of", "bar", true));
+        registry.register(pFoo);
+        registry.register(pBar);
+        assertEquals("incorrect provider count", 2, registry.getProviders().size());
+    }
+
     @Test(expected = IllegalStateException.class)
     public void duplicateRegistration() {
         TestProviderRegistry registry = new TestProviderRegistry();
@@ -57,6 +67,15 @@
         registry.register(pFoo);
     }
 
+    @Test(expected = IllegalStateException.class)
+    public void duplicateSchemeRegistration() {
+        TestProviderRegistry registry = new TestProviderRegistry();
+        TestProvider pFoo = new TestProvider(new ProviderId("of", "foo"));
+        TestProvider pBar = new TestProvider(new ProviderId("of", "bar"));
+        registry.register(pFoo);
+        registry.register(pBar);
+    }
+
     @Test
     public void voidUnregistration() {
         TestProviderRegistry registry = new TestProviderRegistry();
diff --git a/core/net/src/main/java/org/onlab/onos/cluster/impl/MastershipManager.java b/core/net/src/main/java/org/onlab/onos/cluster/impl/MastershipManager.java
index 1a0c408..a0da87c 100644
--- a/core/net/src/main/java/org/onlab/onos/cluster/impl/MastershipManager.java
+++ b/core/net/src/main/java/org/onlab/onos/cluster/impl/MastershipManager.java
@@ -11,6 +11,8 @@
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
+import org.onlab.onos.cluster.ClusterEvent;
+import org.onlab.onos.cluster.ClusterEventListener;
 import org.onlab.onos.cluster.ClusterService;
 import org.onlab.onos.cluster.MastershipAdminService;
 import org.onlab.onos.cluster.MastershipEvent;
@@ -52,9 +54,12 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ClusterService clusterService;
 
+    private ClusterEventListener clusterListener = new InternalClusterEventListener();
+
     @Activate
     public void activate() {
         eventDispatcher.addSink(MastershipEvent.class, listenerRegistry);
+        clusterService.addListener(clusterListener);
         store.setDelegate(delegate);
         log.info("Started");
     }
@@ -62,6 +67,7 @@
     @Deactivate
     public void deactivate() {
         eventDispatcher.removeSink(MastershipEvent.class);
+        clusterService.removeListener(clusterListener);
         store.unsetDelegate(delegate);
         log.info("Stopped");
     }
@@ -71,12 +77,16 @@
         checkNotNull(nodeId, NODE_ID_NULL);
         checkNotNull(deviceId, DEVICE_ID_NULL);
         checkNotNull(role, ROLE_NULL);
-        //TODO figure out appropriate action for non-MASTER roles, if we even set those
+
+        MastershipEvent event = null;
         if (role.equals(MastershipRole.MASTER)) {
-            MastershipEvent event = store.setMaster(nodeId, deviceId);
-            if (event != null) {
-                post(event);
-            }
+            event = store.setMaster(nodeId, deviceId);
+        } else {
+            event = store.unsetMaster(nodeId, deviceId);
+        }
+
+        if (event != null) {
+            post(event);
         }
     }
 
@@ -88,8 +98,16 @@
 
     @Override
     public void relinquishMastership(DeviceId deviceId) {
-        checkNotNull(deviceId, DEVICE_ID_NULL);
-        // FIXME: add method to store to give up mastership and trigger new master selection process
+        MastershipRole role = getLocalRole(deviceId);
+        if (!role.equals(MastershipRole.MASTER)) {
+            return;
+        }
+
+        MastershipEvent event = store.unsetMaster(
+                clusterService.getLocalNode().id(), deviceId);
+        if (event != null) {
+            post(event);
+        }
     }
 
     @Override
@@ -146,6 +164,26 @@
 
     }
 
+    //callback for reacting to cluster events
+    private class InternalClusterEventListener implements ClusterEventListener {
+
+        @Override
+        public void event(ClusterEvent event) {
+            switch (event.type()) {
+                //FIXME: worry about addition when the time comes
+                case INSTANCE_ADDED:
+                case INSTANCE_ACTIVATED:
+                     break;
+                case INSTANCE_REMOVED:
+                case INSTANCE_DEACTIVATED:
+                    break;
+                default:
+                    log.warn("unknown cluster event {}", event);
+            }
+        }
+
+    }
+
     public class InternalDelegate implements MastershipStoreDelegate {
 
         @Override
diff --git a/core/net/src/main/java/org/onlab/onos/net/device/impl/DeviceManager.java b/core/net/src/main/java/org/onlab/onos/net/device/impl/DeviceManager.java
index e7f2697..b61afd2 100644
--- a/core/net/src/main/java/org/onlab/onos/net/device/impl/DeviceManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/device/impl/DeviceManager.java
@@ -16,6 +16,7 @@
 import org.onlab.onos.cluster.MastershipEvent;
 import org.onlab.onos.cluster.MastershipListener;
 import org.onlab.onos.cluster.MastershipService;
+import org.onlab.onos.cluster.MastershipTermService;
 import org.onlab.onos.cluster.MastershipTerm;
 import org.onlab.onos.event.AbstractListenerRegistry;
 import org.onlab.onos.event.EventDeliveryService;
@@ -76,6 +77,8 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected MastershipService mastershipService;
 
+    protected MastershipTermService termService;
+
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ClockService clockService;
 
@@ -84,6 +87,7 @@
         store.setDelegate(delegate);
         eventDispatcher.addSink(DeviceEvent.class, listenerRegistry);
         mastershipService.addListener(mastershipListener);
+        termService = mastershipService.requestTermService();
         log.info("Started");
     }
 
@@ -198,7 +202,7 @@
                 log.info("Device {} connected", deviceId);
                 mastershipService.requestRoleFor(deviceId);
                 provider().roleChanged(event.subject(),
-                        mastershipService.getLocalRole(deviceId));
+                        mastershipService.requestRoleFor(deviceId));
                 post(event);
             }
         }
@@ -208,8 +212,11 @@
             checkNotNull(deviceId, DEVICE_ID_NULL);
             checkValidity();
             DeviceEvent event = store.markOffline(deviceId);
+
+            //we're no longer capable of mastership.
             if (event != null) {
                 log.info("Device {} disconnected", deviceId);
+                mastershipService.relinquishMastership(deviceId);
                 post(event);
             }
         }
@@ -221,8 +228,9 @@
             checkNotNull(portDescriptions,
                     "Port descriptions list cannot be null");
             checkValidity();
-            List<DeviceEvent> events = store.updatePorts(deviceId,
-                    portDescriptions);
+            this.provider().id();
+            List<DeviceEvent> events = store.updatePorts(this.provider().id(),
+                    deviceId, portDescriptions);
             for (DeviceEvent event : events) {
                 post(event);
             }
@@ -234,8 +242,8 @@
             checkNotNull(deviceId, DEVICE_ID_NULL);
             checkNotNull(portDescription, PORT_DESCRIPTION_NULL);
             checkValidity();
-            DeviceEvent event = store.updatePortStatus(deviceId,
-                    portDescription);
+            DeviceEvent event = store.updatePortStatus(this.provider().id(),
+                    deviceId, portDescription);
             if (event != null) {
                 log.info("Device {} port {} status changed", deviceId, event
                         .port().number());
diff --git a/core/net/src/main/java/org/onlab/onos/net/topology/impl/DefaultTopologyProvider.java b/core/net/src/main/java/org/onlab/onos/net/topology/impl/DefaultTopologyProvider.java
index 770f368..7ee6ddd 100644
--- a/core/net/src/main/java/org/onlab/onos/net/topology/impl/DefaultTopologyProvider.java
+++ b/core/net/src/main/java/org/onlab/onos/net/topology/impl/DefaultTopologyProvider.java
@@ -65,8 +65,8 @@
     private volatile boolean isStarted = false;
 
     private TopologyProviderService providerService;
-    private DeviceListener deviceListener = new InnerDeviceListener();
-    private LinkListener linkListener = new InnerLinkListener();
+    private DeviceListener deviceListener = new InternalDeviceListener();
+    private LinkListener linkListener = new InternalLinkListener();
 
     private EventAccumulator accumulator;
     private ExecutorService executor;
@@ -132,7 +132,7 @@
     }
 
     // Callback for device events
-    private class InnerDeviceListener implements DeviceListener {
+    private class InternalDeviceListener implements DeviceListener {
         @Override
         public void event(DeviceEvent event) {
             DeviceEvent.Type type = event.type();
@@ -144,7 +144,7 @@
     }
 
     // Callback for link events
-    private class InnerLinkListener implements LinkListener {
+    private class InternalLinkListener implements LinkListener {
         @Override
         public void event(LinkEvent event) {
             accumulator.add(event);
diff --git a/core/net/src/test/java/org/onlab/onos/cluster/impl/MastershipManagerTest.java b/core/net/src/test/java/org/onlab/onos/cluster/impl/MastershipManagerTest.java
index d4a13ab..29b4ddf 100644
--- a/core/net/src/test/java/org/onlab/onos/cluster/impl/MastershipManagerTest.java
+++ b/core/net/src/test/java/org/onlab/onos/cluster/impl/MastershipManagerTest.java
@@ -15,10 +15,11 @@
 import org.onlab.onos.cluster.NodeId;
 import org.onlab.onos.event.impl.TestEventDispatcher;
 import org.onlab.onos.net.DeviceId;
-import org.onlab.onos.net.trivial.impl.SimpleMastershipStore;
+import org.onlab.onos.store.trivial.impl.SimpleMastershipStore;
 import org.onlab.packet.IpPrefix;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.onlab.onos.net.MastershipRole.*;
 
 /**
@@ -65,7 +66,24 @@
 
     @Test
     public void relinquishMastership() {
-        //TODO
+        //no backups - should turn to standby and no master for device
+        mgr.setRole(NID_LOCAL, DEV_MASTER, MASTER);
+        assertEquals("wrong role:", MASTER, mgr.getLocalRole(DEV_MASTER));
+        mgr.relinquishMastership(DEV_MASTER);
+        assertNull("wrong master:", mgr.getMasterFor(DEV_OTHER));
+        assertEquals("wrong role:", STANDBY, mgr.getLocalRole(DEV_MASTER));
+
+        //not master, nothing should happen
+        mgr.setRole(NID_LOCAL, DEV_OTHER, STANDBY);
+        mgr.relinquishMastership(DEV_OTHER);
+        assertNull("wrong role:", mgr.getMasterFor(DEV_OTHER));
+
+        //provide NID_OTHER as backup and relinquish
+        mgr.setRole(NID_LOCAL, DEV_MASTER, MASTER);
+        assertEquals("wrong master:", NID_LOCAL, mgr.getMasterFor(DEV_MASTER));
+        mgr.setRole(NID_OTHER, DEV_MASTER, STANDBY);
+        mgr.relinquishMastership(DEV_MASTER);
+        assertEquals("wrong master:", NID_OTHER, mgr.getMasterFor(DEV_MASTER));
     }
 
     @Test
@@ -95,7 +113,6 @@
         mgr.setRole(NID_LOCAL, DEV_MASTER, MASTER);
         mgr.setRole(NID_LOCAL, DEV_OTHER, STANDBY);
         assertEquals("should be one device:", 1, mgr.getDevicesOf(NID_LOCAL).size());
-
         //hand both devices to NID_LOCAL
         mgr.setRole(NID_LOCAL, DEV_OTHER, MASTER);
         assertEquals("should be two devices:", 2, mgr.getDevicesOf(NID_LOCAL).size());
diff --git a/core/net/src/test/java/org/onlab/onos/net/device/impl/DeviceManagerTest.java b/core/net/src/test/java/org/onlab/onos/net/device/impl/DeviceManagerTest.java
index 9ff34cc..7d09cca 100644
--- a/core/net/src/test/java/org/onlab/onos/net/device/impl/DeviceManagerTest.java
+++ b/core/net/src/test/java/org/onlab/onos/net/device/impl/DeviceManagerTest.java
@@ -27,7 +27,7 @@
 import org.onlab.onos.net.device.PortDescription;
 import org.onlab.onos.net.provider.AbstractProvider;
 import org.onlab.onos.net.provider.ProviderId;
-import org.onlab.onos.net.trivial.impl.SimpleDeviceStore;
+import org.onlab.onos.store.trivial.impl.SimpleDeviceStore;
 
 import java.util.ArrayList;
 import java.util.Iterator;
diff --git a/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java b/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java
index 54abb84..5ff72a2 100644
--- a/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java
+++ b/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java
@@ -40,7 +40,7 @@
 import org.onlab.onos.net.flow.instructions.Instruction;
 import org.onlab.onos.net.provider.AbstractProvider;
 import org.onlab.onos.net.provider.ProviderId;
-import org.onlab.onos.net.trivial.impl.SimpleFlowRuleStore;
+import org.onlab.onos.store.trivial.impl.SimpleFlowRuleStore;
 
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
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 49ac5b8..0b07380 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
@@ -34,7 +34,7 @@
 import org.onlab.onos.net.host.PortAddresses;
 import org.onlab.onos.net.provider.AbstractProvider;
 import org.onlab.onos.net.provider.ProviderId;
-import org.onlab.onos.net.trivial.impl.SimpleHostStore;
+import org.onlab.onos.store.trivial.impl.SimpleHostStore;
 import org.onlab.packet.IpPrefix;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
diff --git a/core/net/src/test/java/org/onlab/onos/net/link/impl/LinkManagerTest.java b/core/net/src/test/java/org/onlab/onos/net/link/impl/LinkManagerTest.java
index b78d954..1d52ba3 100644
--- a/core/net/src/test/java/org/onlab/onos/net/link/impl/LinkManagerTest.java
+++ b/core/net/src/test/java/org/onlab/onos/net/link/impl/LinkManagerTest.java
@@ -23,7 +23,7 @@
 import org.onlab.onos.net.provider.ProviderId;
 import org.onlab.onos.event.impl.TestEventDispatcher;
 import org.onlab.onos.net.device.impl.DeviceManager;
-import org.onlab.onos.net.trivial.impl.SimpleLinkStore;
+import org.onlab.onos.store.trivial.impl.SimpleLinkStore;
 
 import java.util.ArrayList;
 import java.util.Iterator;
diff --git a/core/net/src/test/java/org/onlab/onos/net/topology/impl/TopologyManagerTest.java b/core/net/src/test/java/org/onlab/onos/net/topology/impl/TopologyManagerTest.java
index 1a19f96..15b7eca 100644
--- a/core/net/src/test/java/org/onlab/onos/net/topology/impl/TopologyManagerTest.java
+++ b/core/net/src/test/java/org/onlab/onos/net/topology/impl/TopologyManagerTest.java
@@ -24,7 +24,7 @@
 import org.onlab.onos.net.topology.TopologyProviderRegistry;
 import org.onlab.onos.net.topology.TopologyProviderService;
 import org.onlab.onos.net.topology.TopologyService;
-import org.onlab.onos.net.trivial.impl.SimpleTopologyStore;
+import org.onlab.onos.store.trivial.impl.SimpleTopologyStore;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/package-info.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/package-info.java
index f4b9710..8d273ac 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/package-info.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/package-info.java
@@ -1,4 +1,4 @@
 /**
  * Distributed cluster store and messaging subsystem implementation.
  */
-package org.onlab.onos.store.cluster.impl;
\ No newline at end of file
+package org.onlab.onos.store.cluster.impl;
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/AntiEntropyAdvertisement.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/AntiEntropyAdvertisement.java
new file mode 100644
index 0000000..f9e0d63
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/AntiEntropyAdvertisement.java
@@ -0,0 +1,49 @@
+package org.onlab.onos.store.cluster.messaging;
+
+import static org.onlab.onos.store.cluster.messaging.MessageSubject.AE_ADVERTISEMENT;
+import java.util.Map;
+
+import org.onlab.onos.cluster.NodeId;
+import org.onlab.onos.store.Timestamp;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Anti-Entropy advertisement message.
+ * <p>
+ * Message to advertise the information this node holds.
+ *
+ * @param <ID> ID type
+ */
+public class AntiEntropyAdvertisement<ID> extends ClusterMessage {
+
+    private final NodeId sender;
+    private final ImmutableMap<ID, Timestamp> advertisement;
+
+    /**
+     * Creates anti-entropy advertisement message.
+     *
+     * @param sender sender of this message
+     * @param advertisement timestamp information of the data sender holds
+     */
+    public AntiEntropyAdvertisement(NodeId sender, Map<ID, Timestamp> advertisement) {
+        super(AE_ADVERTISEMENT);
+        this.sender = sender;
+        this.advertisement = ImmutableMap.copyOf(advertisement);
+    }
+
+    public NodeId sender() {
+        return sender;
+    }
+
+    public ImmutableMap<ID, Timestamp> advertisement() {
+        return advertisement;
+    }
+
+    // Default constructor for serializer
+    protected AntiEntropyAdvertisement() {
+        super(AE_ADVERTISEMENT);
+        this.sender = null;
+        this.advertisement = null;
+    }
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/AntiEntropyReply.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/AntiEntropyReply.java
new file mode 100644
index 0000000..9bc095e
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/AntiEntropyReply.java
@@ -0,0 +1,82 @@
+package org.onlab.onos.store.cluster.messaging;
+
+import static org.onlab.onos.store.cluster.messaging.MessageSubject.AE_REPLY;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.onlab.onos.cluster.NodeId;
+import org.onlab.onos.store.device.impl.VersionedValue;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Anti-Entropy reply message.
+ * <p>
+ * Message to send in reply to advertisement or another reply.
+ * Suggest to the sender about the more up-to-date data this node has,
+ * and request for more recent data that the receiver has.
+ */
+public class AntiEntropyReply<ID, V extends VersionedValue<?>> extends ClusterMessage {
+
+    private final NodeId sender;
+    private final ImmutableMap<ID, V> suggestion;
+    private final ImmutableSet<ID> request;
+
+    /**
+     * Creates a reply to anti-entropy message.
+     *
+     * @param sender sender of this message
+     * @param suggestion collection of more recent values, sender had
+     * @param request Collection of identifiers
+     */
+    public AntiEntropyReply(NodeId sender,
+                            Map<ID, V> suggestion,
+                            Set<ID> request) {
+        super(AE_REPLY);
+        this.sender = sender;
+        this.suggestion = ImmutableMap.copyOf(suggestion);
+        this.request = ImmutableSet.copyOf(request);
+    }
+
+    public NodeId sender() {
+        return sender;
+    }
+
+    /**
+     * Returns collection of values, which the recipient of this reply is likely
+     * to be missing or has outdated version.
+     *
+     * @return
+     */
+    public ImmutableMap<ID, V> suggestion() {
+        return suggestion;
+    }
+
+    /**
+     * Returns collection of identifier to request.
+     *
+     * @return collection of identifier to request
+     */
+    public ImmutableSet<ID> request() {
+        return request;
+    }
+
+    /**
+     * Checks if reply contains any suggestion or request.
+     *
+     * @return true if nothing is suggested and requested
+     */
+    public boolean isEmpty() {
+        return suggestion.isEmpty() && request.isEmpty();
+    }
+
+    // Default constructor for serializer
+    protected AntiEntropyReply() {
+        super(AE_REPLY);
+        this.sender = null;
+        this.suggestion = null;
+        this.request = null;
+    }
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/MessageSubject.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/MessageSubject.java
index c7badf2..97cbf1d 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/MessageSubject.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/MessageSubject.java
@@ -15,6 +15,12 @@
     LEAVING_MEMBER,
 
     /** Signifies a heart-beat message. */
-    ECHO
+    ECHO,
+
+    /** Anti-Entropy advertisement message. */
+    AE_ADVERTISEMENT,
+
+    /** Anti-Entropy reply message. */
+    AE_REPLY,
 
 }
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/package-info.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/package-info.java
index 5276b0b..326dbe8 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/package-info.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/package-info.java
@@ -1,4 +1,4 @@
 /**
  * Cluster messaging APIs for the use by the various distributed stores.
  */
-package org.onlab.onos.store.cluster.messaging;
\ No newline at end of file
+package org.onlab.onos.store.cluster.messaging;
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/DeviceAntiEntropyAdvertisement.java b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/DeviceAntiEntropyAdvertisement.java
new file mode 100644
index 0000000..301884c
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/DeviceAntiEntropyAdvertisement.java
@@ -0,0 +1,39 @@
+package org.onlab.onos.store.device.impl;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.onlab.onos.cluster.NodeId;
+import org.onlab.onos.net.Device;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.store.Timestamp;
+import org.onlab.onos.store.cluster.messaging.AntiEntropyAdvertisement;
+
+// TODO DeviceID needs to be changed to something like (ProviderID, DeviceID)
+// TODO: Handle Port as part of these messages, or separate messages for Ports?
+
+public class DeviceAntiEntropyAdvertisement
+    extends AntiEntropyAdvertisement<DeviceId> {
+
+
+    public DeviceAntiEntropyAdvertisement(NodeId sender,
+            Map<DeviceId, Timestamp> advertisement) {
+        super(sender, advertisement);
+    }
+
+    // May need to add ProviderID, etc.
+    public static DeviceAntiEntropyAdvertisement create(
+            NodeId self,
+            Collection<VersionedValue<Device>> localValues) {
+
+        Map<DeviceId, Timestamp> ads = new HashMap<>(localValues.size());
+        for (VersionedValue<Device> e : localValues) {
+            ads.put(e.entity().id(), e.timestamp());
+        }
+        return new DeviceAntiEntropyAdvertisement(self, ads);
+    }
+
+    // For serializer
+    protected DeviceAntiEntropyAdvertisement() {}
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/DeviceAntiEntropyReply.java b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/DeviceAntiEntropyReply.java
new file mode 100644
index 0000000..011713e
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/DeviceAntiEntropyReply.java
@@ -0,0 +1,102 @@
+package org.onlab.onos.store.device.impl;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.onlab.onos.cluster.NodeId;
+import org.onlab.onos.net.Device;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.store.Timestamp;
+import org.onlab.onos.store.cluster.messaging.AntiEntropyReply;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+public class DeviceAntiEntropyReply
+    extends AntiEntropyReply<DeviceId, VersionedValue<Device>> {
+
+
+    public DeviceAntiEntropyReply(NodeId sender,
+            Map<DeviceId, VersionedValue<Device>> suggestion,
+            Set<DeviceId> request) {
+        super(sender, suggestion, request);
+    }
+
+    /**
+     * Creates a reply to Anti-Entropy advertisement.
+     *
+     * @param advertisement to respond to
+     * @param self node identifier representing local node
+     * @param localValues local values held on this node
+     * @return reply message
+     */
+    public static DeviceAntiEntropyReply reply(
+            DeviceAntiEntropyAdvertisement advertisement,
+            NodeId self,
+            Collection<VersionedValue<Device>> localValues
+            ) {
+
+        ImmutableMap<DeviceId, Timestamp> ads = advertisement.advertisement();
+
+        ImmutableMap.Builder<DeviceId, VersionedValue<Device>>
+            sug = ImmutableMap.builder();
+
+        Set<DeviceId> req = new HashSet<>(ads.keySet());
+
+        for (VersionedValue<Device> e : localValues) {
+            final DeviceId id = e.entity().id();
+            final Timestamp local = e.timestamp();
+            final Timestamp theirs = ads.get(id);
+            if (theirs == null) {
+                // they don't have it, suggest
+                sug.put(id, e);
+                // don't need theirs
+                req.remove(id);
+            } else if (local.compareTo(theirs) < 0) {
+                // they got older one, suggest
+                sug.put(id, e);
+                // don't need theirs
+                req.remove(id);
+            } else if (local.equals(theirs)) {
+                // same, don't need theirs
+                req.remove(id);
+            }
+        }
+
+        return new DeviceAntiEntropyReply(self, sug.build(), req);
+    }
+
+    /**
+     * Creates a reply to request for values held locally.
+     *
+     * @param requests message containing the request
+     * @param self node identifier representing local node
+     * @param localValues local valeds held on this node
+     * @return reply message
+     */
+    public static DeviceAntiEntropyReply reply(
+            DeviceAntiEntropyReply requests,
+            NodeId self,
+            Map<DeviceId, VersionedValue<Device>> localValues
+            ) {
+
+        Set<DeviceId> reqs = requests.request();
+
+        Map<DeviceId, VersionedValue<Device>> requested = new HashMap<>(reqs.size());
+        for (DeviceId id : reqs) {
+            final VersionedValue<Device> value = localValues.get(id);
+            if (value != null) {
+                requested.put(id, value);
+            }
+        }
+
+        Set<DeviceId> empty = ImmutableSet.of();
+        return new DeviceAntiEntropyReply(self, requested, empty);
+    }
+
+    // For serializer
+    protected DeviceAntiEntropyReply() {}
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/OnosDistributedDeviceStore.java b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/OnosDistributedDeviceStore.java
index bd5f2fd..d912983 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/OnosDistributedDeviceStore.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/OnosDistributedDeviceStore.java
@@ -40,6 +40,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static org.onlab.onos.net.device.DeviceEvent.Type.*;
@@ -59,8 +60,8 @@
 
     public static final String DEVICE_NOT_FOUND = "Device with ID %s not found";
 
-    private ConcurrentHashMap<DeviceId, VersionedValue<Device>> devices;
-    private ConcurrentHashMap<DeviceId, Map<PortNumber, VersionedValue<Port>>> devicePorts;
+    private ConcurrentMap<DeviceId, VersionedValue<Device>> devices;
+    private ConcurrentMap<DeviceId, Map<PortNumber, VersionedValue<Port>>> devicePorts;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ClockService clockService;
@@ -191,7 +192,7 @@
     }
 
     @Override
-    public List<DeviceEvent> updatePorts(DeviceId deviceId,
+    public List<DeviceEvent> updatePorts(ProviderId providerId, DeviceId deviceId,
                                          List<PortDescription> portDescriptions) {
         List<DeviceEvent> events = new ArrayList<>();
         synchronized (this) {
@@ -295,7 +296,7 @@
     }
 
     @Override
-    public DeviceEvent updatePortStatus(DeviceId deviceId,
+    public DeviceEvent updatePortStatus(ProviderId providerId, DeviceId deviceId,
                                         PortDescription portDescription) {
         VersionedValue<Device> device = devices.get(deviceId);
         checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/VersionedValue.java b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/VersionedValue.java
index 1a85c53..a0f485a 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/VersionedValue.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/VersionedValue.java
@@ -1,5 +1,7 @@
 package org.onlab.onos.store.device.impl;
 
+import java.util.Objects;
+
 import org.onlab.onos.store.Timestamp;
 
 /**
@@ -42,4 +44,35 @@
     public Timestamp timestamp() {
         return timestamp;
     }
+
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(entity, timestamp, isUp);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        @SuppressWarnings("unchecked")
+        VersionedValue<T> that = (VersionedValue<T>) obj;
+        return Objects.equals(this.entity, that.entity) &&
+                Objects.equals(this.timestamp, that.timestamp) &&
+                Objects.equals(this.isUp, that.isUp);
+    }
+
+    // Default constructor for serializer
+    protected VersionedValue() {
+        this.entity = null;
+        this.isUp = false;
+        this.timestamp = null;
+    }
 }
diff --git a/core/store/hz/cluster/src/main/java/org/onlab/onos/store/cluster/impl/DistributedMastershipStore.java b/core/store/hz/cluster/src/main/java/org/onlab/onos/store/cluster/impl/DistributedMastershipStore.java
index 50c5f08..18e6e96 100644
--- a/core/store/hz/cluster/src/main/java/org/onlab/onos/store/cluster/impl/DistributedMastershipStore.java
+++ b/core/store/hz/cluster/src/main/java/org/onlab/onos/store/cluster/impl/DistributedMastershipStore.java
@@ -123,6 +123,12 @@
         return null;
     }
 
+    @Override
+    public MastershipEvent unsetMaster(NodeId nodeId, DeviceId deviceId) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
     private class RemoteMasterShipEventHandler extends RemoteCacheEventHandler<DeviceId, NodeId> {
         public RemoteMasterShipEventHandler(LoadingCache<DeviceId, Optional<NodeId>> cache) {
             super(cache);
diff --git a/core/store/hz/net/src/main/java/org/onlab/onos/store/device/impl/DistributedDeviceStore.java b/core/store/hz/net/src/main/java/org/onlab/onos/store/device/impl/DistributedDeviceStore.java
index a3d340b..5feb1ba 100644
--- a/core/store/hz/net/src/main/java/org/onlab/onos/store/device/impl/DistributedDeviceStore.java
+++ b/core/store/hz/net/src/main/java/org/onlab/onos/store/device/impl/DistributedDeviceStore.java
@@ -221,7 +221,7 @@
     }
 
     @Override
-    public List<DeviceEvent> updatePorts(DeviceId deviceId,
+    public List<DeviceEvent> updatePorts(ProviderId providerId, DeviceId deviceId,
                                          List<PortDescription> portDescriptions) {
         List<DeviceEvent> events = new ArrayList<>();
         synchronized (this) {
@@ -319,7 +319,7 @@
     }
 
     @Override
-    public DeviceEvent updatePortStatus(DeviceId deviceId,
+    public DeviceEvent updatePortStatus(ProviderId providerId, DeviceId deviceId,
                                         PortDescription portDescription) {
         synchronized (this) {
             Device device = devices.getUnchecked(deviceId).orNull();
diff --git a/core/store/hz/net/src/main/java/org/onlab/onos/store/flow/impl/DistributedFlowRuleStore.java b/core/store/hz/net/src/main/java/org/onlab/onos/store/flow/impl/DistributedFlowRuleStore.java
index 5a5592a..6ec7c51 100644
--- a/core/store/hz/net/src/main/java/org/onlab/onos/store/flow/impl/DistributedFlowRuleStore.java
+++ b/core/store/hz/net/src/main/java/org/onlab/onos/store/flow/impl/DistributedFlowRuleStore.java
@@ -28,7 +28,7 @@
 import com.google.common.collect.Multimap;
 
 /**
- * Manages inventory of flow rules using trivial in-memory implementation.
+ * TEMPORARY: Manages inventory of flow rules using distributed store implementation.
  */
 //FIXME: I LIE I AM NOT DISTRIBUTED
 @Component(immediate = true)
diff --git a/core/store/hz/net/src/main/java/org/onlab/onos/store/flow/impl/package-info.java b/core/store/hz/net/src/main/java/org/onlab/onos/store/flow/impl/package-info.java
new file mode 100644
index 0000000..f09ac11
--- /dev/null
+++ b/core/store/hz/net/src/main/java/org/onlab/onos/store/flow/impl/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Implementation of flow store using Hazelcast distributed structures.
+ */
+package org.onlab.onos.store.flow.impl;
diff --git a/core/store/hz/net/src/main/java/org/onlab/onos/store/host/impl/DistributedHostStore.java b/core/store/hz/net/src/main/java/org/onlab/onos/store/host/impl/DistributedHostStore.java
index 09820f4..5c706e6 100644
--- a/core/store/hz/net/src/main/java/org/onlab/onos/store/host/impl/DistributedHostStore.java
+++ b/core/store/hz/net/src/main/java/org/onlab/onos/store/host/impl/DistributedHostStore.java
@@ -39,8 +39,8 @@
 import com.google.common.collect.Sets;
 
 /**
- * Manages inventory of end-station hosts using trivial in-memory
- * implementation.
+ * TEMPORARY: Manages inventory of end-station hosts using distributed
+ * structures implementation.
  */
 //FIXME: I LIE I AM NOT DISTRIBUTED
 @Component(immediate = true)
diff --git a/core/store/hz/net/src/main/java/org/onlab/onos/store/host/impl/package-info.java b/core/store/hz/net/src/main/java/org/onlab/onos/store/host/impl/package-info.java
new file mode 100644
index 0000000..2a9998a
--- /dev/null
+++ b/core/store/hz/net/src/main/java/org/onlab/onos/store/host/impl/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Implementation of host store using Hazelcast distributed structures.
+ */
+package org.onlab.onos.store.host.impl;
diff --git a/core/store/hz/net/src/main/java/org/onlab/onos/store/topology/impl/DistributedTopologyStore.java b/core/store/hz/net/src/main/java/org/onlab/onos/store/topology/impl/DistributedTopologyStore.java
index 567861e..4728850 100644
--- a/core/store/hz/net/src/main/java/org/onlab/onos/store/topology/impl/DistributedTopologyStore.java
+++ b/core/store/hz/net/src/main/java/org/onlab/onos/store/topology/impl/DistributedTopologyStore.java
@@ -28,7 +28,7 @@
 import org.slf4j.Logger;
 
 /**
- * Manages inventory of topology snapshots using trivial in-memory
+ * TEMPORARY: Manages inventory of topology snapshots using distributed
  * structures implementation.
  */
 //FIXME: I LIE I AM NOT DISTRIBUTED
diff --git a/core/store/hz/net/src/main/java/org/onlab/onos/store/topology/impl/package-info.java b/core/store/hz/net/src/main/java/org/onlab/onos/store/topology/impl/package-info.java
new file mode 100644
index 0000000..28b7704
--- /dev/null
+++ b/core/store/hz/net/src/main/java/org/onlab/onos/store/topology/impl/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Implementation of topology store using Hazelcast distributed structures.
+ */
+package org.onlab.onos.store.topology.impl;
diff --git a/core/store/hz/net/src/test/java/org/onlab/onos/store/device/impl/DistributedDeviceStoreTest.java b/core/store/hz/net/src/test/java/org/onlab/onos/store/device/impl/DistributedDeviceStoreTest.java
index 97b9ebe..80c9464 100644
--- a/core/store/hz/net/src/test/java/org/onlab/onos/store/device/impl/DistributedDeviceStoreTest.java
+++ b/core/store/hz/net/src/test/java/org/onlab/onos/store/device/impl/DistributedDeviceStoreTest.java
@@ -201,7 +201,7 @@
                 new DefaultPortDescription(P2, true)
                 );
 
-        List<DeviceEvent> events = deviceStore.updatePorts(DID1, pds);
+        List<DeviceEvent> events = deviceStore.updatePorts(PID, DID1, pds);
 
         Set<PortNumber> expectedPorts = Sets.newHashSet(P1, P2);
         for (DeviceEvent event : events) {
@@ -220,7 +220,7 @@
                 new DefaultPortDescription(P3, true)
                 );
 
-        events = deviceStore.updatePorts(DID1, pds2);
+        events = deviceStore.updatePorts(PID, DID1, pds2);
         assertFalse("event should be triggered", events.isEmpty());
         for (DeviceEvent event : events) {
             PortNumber num = event.port().number();
@@ -243,7 +243,7 @@
                 new DefaultPortDescription(P1, false),
                 new DefaultPortDescription(P2, true)
                 );
-        events = deviceStore.updatePorts(DID1, pds3);
+        events = deviceStore.updatePorts(PID, DID1, pds3);
         assertFalse("event should be triggered", events.isEmpty());
         for (DeviceEvent event : events) {
             PortNumber num = event.port().number();
@@ -268,9 +268,9 @@
         List<PortDescription> pds = Arrays.<PortDescription>asList(
                 new DefaultPortDescription(P1, true)
                 );
-        deviceStore.updatePorts(DID1, pds);
+        deviceStore.updatePorts(PID, DID1, pds);
 
-        DeviceEvent event = deviceStore.updatePortStatus(DID1,
+        DeviceEvent event = deviceStore.updatePortStatus(PID, DID1,
                 new DefaultPortDescription(P1, false));
         assertEquals(PORT_UPDATED, event.type());
         assertDevice(DID1, SW1, event.subject());
@@ -286,7 +286,7 @@
                 new DefaultPortDescription(P1, true),
                 new DefaultPortDescription(P2, true)
                 );
-        deviceStore.updatePorts(DID1, pds);
+        deviceStore.updatePorts(PID, DID1, pds);
 
         Set<PortNumber> expectedPorts = Sets.newHashSet(P1, P2);
         List<Port> ports = deviceStore.getPorts(DID1);
@@ -309,7 +309,7 @@
                 new DefaultPortDescription(P1, true),
                 new DefaultPortDescription(P2, false)
                 );
-        deviceStore.updatePorts(DID1, pds);
+        deviceStore.updatePorts(PID, DID1, pds);
 
         Port port1 = deviceStore.getPort(DID1, P1);
         assertEquals(P1, port1.number());
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/ImmutableMapSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/ImmutableMapSerializer.java
new file mode 100644
index 0000000..244cc57
--- /dev/null
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/ImmutableMapSerializer.java
@@ -0,0 +1,49 @@
+package org.onlab.onos.store.serializers;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.onlab.util.KryoPool.FamilySerializer;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+import com.esotericsoftware.kryo.serializers.MapSerializer;
+import com.google.common.collect.ImmutableMap;
+
+/**
+* Kryo Serializer for {@link ImmutableMap}.
+*/
+public class ImmutableMapSerializer extends FamilySerializer<ImmutableMap<?, ?>> {
+
+    private final MapSerializer mapSerializer = new MapSerializer();
+
+    public ImmutableMapSerializer() {
+        // non-null, immutable
+        super(false, true);
+    }
+
+    @Override
+    public void write(Kryo kryo, Output output, ImmutableMap<?, ?> object) {
+        // wrapping with unmodifiableMap proxy
+        // to avoid Kryo from writing only the reference marker of this instance,
+        // which will be embedded right before this method call.
+        kryo.writeObject(output, Collections.unmodifiableMap(object), mapSerializer);
+    }
+
+    @Override
+    public ImmutableMap<?, ?> read(Kryo kryo, Input input,
+                                    Class<ImmutableMap<?, ?>> type) {
+        Map<?, ?> map = kryo.readObject(input, HashMap.class, mapSerializer);
+        return ImmutableMap.copyOf(map);
+    }
+
+    @Override
+    public void registerFamilies(Kryo kryo) {
+        kryo.register(ImmutableMap.of().getClass(), this);
+        kryo.register(ImmutableMap.of(1, 2).getClass(), this);
+        kryo.register(ImmutableMap.of(1, 2, 3, 4).getClass(), this);
+        // TODO register required ImmutableMap variants
+    }
+}
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/ImmutableSetSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/ImmutableSetSerializer.java
new file mode 100644
index 0000000..c08bf9a
--- /dev/null
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/ImmutableSetSerializer.java
@@ -0,0 +1,45 @@
+package org.onlab.onos.store.serializers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.onlab.util.KryoPool.FamilySerializer;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+import com.esotericsoftware.kryo.serializers.CollectionSerializer;
+import com.google.common.collect.ImmutableSet;
+
+/**
+* Kryo Serializer for {@link ImmutableSet}.
+*/
+public class ImmutableSetSerializer extends FamilySerializer<ImmutableSet<?>> {
+
+    private final CollectionSerializer serializer = new CollectionSerializer();
+
+    public ImmutableSetSerializer() {
+        // non-null, immutable
+        super(false, true);
+    }
+
+    @Override
+    public void write(Kryo kryo, Output output, ImmutableSet<?> object) {
+        kryo.writeObject(output, object.asList(), serializer);
+    }
+
+    @Override
+    public ImmutableSet<?> read(Kryo kryo, Input input,
+                                Class<ImmutableSet<?>> type) {
+        List<?> elms = kryo.readObject(input, ArrayList.class, serializer);
+        return ImmutableSet.copyOf(elms);
+    }
+
+    @Override
+    public void registerFamilies(Kryo kryo) {
+        kryo.register(ImmutableSet.of().getClass(), this);
+        kryo.register(ImmutableSet.of(1).getClass(), this);
+        kryo.register(ImmutableSet.of(1, 2).getClass(), this);
+        // TODO register required ImmutableSet variants
+    }
+}
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoSerializationManager.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoSerializationManager.java
index 84e1b73..4b8410d 100644
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoSerializationManager.java
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoSerializationManager.java
@@ -1,6 +1,7 @@
 package org.onlab.onos.store.serializers;
 
 import java.net.URI;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.HashMap;
 
@@ -100,4 +101,14 @@
         return serializerPool.deserialize(bytes);
     }
 
+    @Override
+    public void serialize(Object obj, ByteBuffer buffer) {
+        serializerPool.serialize(obj, buffer);
+    }
+
+    @Override
+    public <T> T deserialize(ByteBuffer buffer) {
+        return serializerPool.deserialize(buffer);
+    }
+
 }
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoSerializationService.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoSerializationService.java
index e92cc4b..385128c 100644
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoSerializationService.java
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoSerializationService.java
@@ -1,5 +1,7 @@
 package org.onlab.onos.store.serializers;
 
+import java.nio.ByteBuffer;
+
 // TODO: To be replaced with SerializationService from IOLoop activity
 /**
  * Service to serialize Objects into byte array.
@@ -16,6 +18,15 @@
     public byte[] serialize(final Object obj);
 
     /**
+     * Serializes the specified object into bytes using one of the
+     * pre-registered serializers.
+     *
+     * @param obj object to be serialized
+     * @param buffer to write serialized bytes
+     */
+    public void serialize(final Object obj, ByteBuffer buffer);
+
+    /**
      * Deserializes the specified bytes into an object using one of the
      * pre-registered serializers.
      *
@@ -24,4 +35,12 @@
      */
     public <T> T deserialize(final byte[] bytes);
 
+    /**
+     * Deserializes the specified bytes into an object using one of the
+     * pre-registered serializers.
+     *
+     * @param buffer bytes to be deserialized
+     * @return deserialized object
+     */
+    public <T> T deserialize(final ByteBuffer buffer);
 }
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/MastershipRoleSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/MastershipRoleSerializer.java
new file mode 100644
index 0000000..3903491
--- /dev/null
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/MastershipRoleSerializer.java
@@ -0,0 +1,26 @@
+package org.onlab.onos.store.serializers;
+
+import org.onlab.onos.net.MastershipRole;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Kryo Serializer for {@link org.onlab.onos.net.MastershipRole}.
+ */
+public class MastershipRoleSerializer extends Serializer<MastershipRole> {
+
+    @Override
+    public MastershipRole read(Kryo kryo, Input input, Class<MastershipRole> type) {
+        final String role = kryo.readObject(input, String.class);
+        return MastershipRole.valueOf(role);
+    }
+
+    @Override
+    public void write(Kryo kryo, Output output, MastershipRole object) {
+        kryo.writeObject(output, object.toString());
+    }
+
+}
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/MastershipTermSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/MastershipTermSerializer.java
new file mode 100644
index 0000000..a5d6198
--- /dev/null
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/MastershipTermSerializer.java
@@ -0,0 +1,29 @@
+package org.onlab.onos.store.serializers;
+
+import org.onlab.onos.cluster.MastershipTerm;
+import org.onlab.onos.cluster.NodeId;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Kryo Serializer for {@link org.onlab.onos.cluster.MastershipTerm}.
+ */
+public class MastershipTermSerializer extends Serializer<MastershipTerm> {
+
+    @Override
+    public MastershipTerm read(Kryo kryo, Input input, Class<MastershipTerm> type) {
+        final NodeId node = new NodeId(kryo.readObject(input, String.class));
+        final int term = input.readInt();
+        return MastershipTerm.of(node, term);
+    }
+
+    @Override
+    public void write(Kryo kryo, Output output, MastershipTerm object) {
+        output.writeString(object.master().toString());
+        output.writeInt(object.termNumber());
+    }
+
+}
diff --git a/core/store/serializers/src/test/java/org/onlab/onos/store/serializers/KryoSerializerTests.java b/core/store/serializers/src/test/java/org/onlab/onos/store/serializers/KryoSerializerTests.java
new file mode 100644
index 0000000..c972d1a
--- /dev/null
+++ b/core/store/serializers/src/test/java/org/onlab/onos/store/serializers/KryoSerializerTests.java
@@ -0,0 +1,133 @@
+package org.onlab.onos.store.serializers;
+
+import static org.onlab.onos.net.DeviceId.deviceId;
+import static org.onlab.onos.net.PortNumber.portNumber;
+
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.onlab.onos.cluster.MastershipTerm;
+import org.onlab.onos.cluster.NodeId;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DefaultDevice;
+import org.onlab.onos.net.DefaultLink;
+import org.onlab.onos.net.DefaultPort;
+import org.onlab.onos.net.Device;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.LinkKey;
+import org.onlab.onos.net.MastershipRole;
+import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.packet.IpPrefix;
+import org.onlab.util.KryoPool;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.testing.EqualsTester;
+
+import de.javakaffee.kryoserializers.URISerializer;
+
+public class KryoSerializerTests {
+    private static final ProviderId PID = new ProviderId("of", "foo");
+    private static final DeviceId DID1 = deviceId("of:foo");
+    private static final DeviceId DID2 = deviceId("of:bar");
+    private static final PortNumber P1 = portNumber(1);
+    private static final PortNumber P2 = portNumber(2);
+    private static final ConnectPoint CP1 = new ConnectPoint(DID1, P1);
+    private static final ConnectPoint CP2 = new ConnectPoint(DID2, P2);
+    private static final String MFR = "whitebox";
+    private static final String HW = "1.1.x";
+    private static final String SW1 = "3.8.1";
+    private static final String SW2 = "3.9.5";
+    private static final String SN = "43311-12345";
+    private static final Device DEV1 = new DefaultDevice(PID, DID1, Device.Type.SWITCH, MFR, HW, SW1, SN);
+
+    private static KryoPool kryos;
+
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception {
+        kryos = KryoPool.newBuilder()
+                .register(
+                        ArrayList.class,
+                        HashMap.class
+                        )
+                .register(
+                        Device.Type.class,
+                        Link.Type.class
+
+//                      ControllerNode.State.class,
+//                        DefaultControllerNode.class,
+//                        MastershipRole.class,
+//                        Port.class,
+//                        Element.class,
+                        )
+                .register(ConnectPoint.class, new ConnectPointSerializer())
+                .register(DefaultLink.class, new DefaultLinkSerializer())
+                .register(DefaultPort.class, new DefaultPortSerializer())
+                .register(DeviceId.class, new DeviceIdSerializer())
+                .register(ImmutableMap.class, new ImmutableMapSerializer())
+                .register(ImmutableSet.class, new ImmutableSetSerializer())
+                .register(IpPrefix.class, new IpPrefixSerializer())
+                .register(LinkKey.class, new LinkKeySerializer())
+                .register(NodeId.class, new NodeIdSerializer())
+                .register(PortNumber.class, new PortNumberSerializer())
+                .register(ProviderId.class, new ProviderIdSerializer())
+
+                .register(DefaultDevice.class)
+
+                .register(URI.class, new URISerializer())
+
+                .register(MastershipRole.class, new MastershipRoleSerializer())
+                .register(MastershipTerm.class, new MastershipTermSerializer())
+                .build();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // removing Kryo instance to use fresh Kryo on each tests
+        kryos.getKryo();
+    }
+
+    private static <T> void testSerialized(T original) {
+        ByteBuffer buffer = ByteBuffer.allocate(1 * 1024 * 1024);
+        kryos.serialize(original, buffer);
+        buffer.flip();
+        T copy = kryos.deserialize(buffer);
+
+        new EqualsTester()
+            .addEqualityGroup(original, copy)
+            .testEquals();
+    }
+
+
+    @Test
+    public final void test() {
+        testSerialized(new ConnectPoint(DID1, P1));
+        testSerialized(new DefaultLink(PID, CP1, CP2, Link.Type.DIRECT));
+        testSerialized(new DefaultPort(DEV1, P1, true));
+        testSerialized(DID1);
+        testSerialized(ImmutableMap.of(DID1, DEV1, DID2, DEV1));
+        testSerialized(ImmutableMap.of(DID1, DEV1));
+        testSerialized(ImmutableMap.of());
+        testSerialized(ImmutableSet.of(DID1, DID2));
+        testSerialized(ImmutableSet.of(DID1));
+        testSerialized(ImmutableSet.of());
+        testSerialized(IpPrefix.valueOf("192.168.0.1/24"));
+        testSerialized(new LinkKey(CP1, CP2));
+        testSerialized(new NodeId("SomeNodeIdentifier"));
+        testSerialized(P1);
+        testSerialized(PID);
+    }
+
+}
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleMastershipStore.java b/core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleMastershipStore.java
deleted file mode 100644
index 61dbe61..0000000
--- a/core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleMastershipStore.java
+++ /dev/null
@@ -1,136 +0,0 @@
-package org.onlab.onos.net.trivial.impl;
-
-import static org.slf4j.LoggerFactory.getLogger;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.atomic.AtomicInteger;
-
-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.Service;
-import org.onlab.onos.cluster.ControllerNode;
-import org.onlab.onos.cluster.DefaultControllerNode;
-import org.onlab.onos.cluster.MastershipEvent;
-import org.onlab.onos.cluster.MastershipStore;
-import org.onlab.onos.cluster.MastershipStoreDelegate;
-import org.onlab.onos.cluster.MastershipTerm;
-import org.onlab.onos.cluster.NodeId;
-import org.onlab.onos.net.DeviceId;
-import org.onlab.onos.net.MastershipRole;
-import org.onlab.onos.store.AbstractStore;
-import org.onlab.packet.IpPrefix;
-import org.slf4j.Logger;
-
-import static org.onlab.onos.cluster.MastershipEvent.Type.*;
-
-/**
- * Manages inventory of controller mastership over devices using
- * trivial, non-distributed in-memory structures implementation.
- */
-@Component(immediate = true)
-@Service
-public class SimpleMastershipStore
-        extends AbstractStore<MastershipEvent, MastershipStoreDelegate>
-        implements MastershipStore {
-
-    private final Logger log = getLogger(getClass());
-
-    public static final IpPrefix LOCALHOST = IpPrefix.valueOf("127.0.0.1");
-
-    private ControllerNode instance =
-            new DefaultControllerNode(new NodeId("local"), LOCALHOST);
-
-    //devices mapped to their masters, to emulate multiple nodes
-    protected final ConcurrentMap<DeviceId, NodeId> masterMap =
-            new ConcurrentHashMap<>();
-    protected final Map<DeviceId, AtomicInteger> termMap = new HashMap<>();
-
-    @Activate
-    public void activate() {
-        log.info("Started");
-    }
-
-    @Deactivate
-    public void deactivate() {
-        log.info("Stopped");
-    }
-
-    @Override
-    public MastershipEvent setMaster(NodeId nodeId, DeviceId deviceId) {
-
-        NodeId node = masterMap.get(deviceId);
-        if (node == null) {
-            synchronized (this) {
-                masterMap.put(deviceId, nodeId);
-                termMap.put(deviceId, new AtomicInteger());
-            }
-            return new MastershipEvent(MASTER_CHANGED, deviceId, nodeId);
-        }
-
-        if (node.equals(nodeId)) {
-            return null;
-        } else {
-            synchronized (this) {
-                masterMap.put(deviceId, nodeId);
-                termMap.get(deviceId).incrementAndGet();
-                return new MastershipEvent(MASTER_CHANGED, deviceId, nodeId);
-            }
-        }
-    }
-
-    @Override
-    public NodeId getMaster(DeviceId deviceId) {
-        return masterMap.get(deviceId);
-    }
-
-    @Override
-    public Set<DeviceId> getDevices(NodeId nodeId) {
-        Set<DeviceId> ids = new HashSet<>();
-        for (Map.Entry<DeviceId, NodeId> d : masterMap.entrySet()) {
-            if (d.getValue().equals(nodeId)) {
-                ids.add(d.getKey());
-            }
-        }
-        return Collections.unmodifiableSet(ids);
-    }
-
-    @Override
-    public MastershipRole requestRole(DeviceId deviceId) {
-        return getRole(instance.id(), deviceId);
-    }
-
-    @Override
-    public MastershipRole getRole(NodeId nodeId, DeviceId deviceId) {
-        NodeId node = masterMap.get(deviceId);
-        MastershipRole role;
-        if (node != null) {
-            if (node.equals(nodeId)) {
-                role = MastershipRole.MASTER;
-            } else {
-                role = MastershipRole.STANDBY;
-            }
-        } else {
-            //masterMap doesn't contain it.
-            role = MastershipRole.MASTER;
-            masterMap.put(deviceId, nodeId);
-        }
-        return role;
-    }
-
-    @Override
-    public MastershipTerm getTermFor(DeviceId deviceId) {
-        if (masterMap.get(deviceId) == null) {
-            return null;
-        }
-        return MastershipTerm.of(
-                masterMap.get(deviceId), termMap.get(deviceId).get());
-    }
-
-}
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/DefaultTopology.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/DefaultTopology.java
similarity index 99%
rename from core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/DefaultTopology.java
rename to core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/DefaultTopology.java
index e65ad08..e85e091 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/DefaultTopology.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/DefaultTopology.java
@@ -1,4 +1,4 @@
-package org.onlab.onos.net.trivial.impl;
+package org.onlab.onos.store.trivial.impl;
 
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/DefaultTopologyGraph.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/DefaultTopologyGraph.java
similarity index 94%
rename from core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/DefaultTopologyGraph.java
rename to core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/DefaultTopologyGraph.java
index 401dfd2..b7b721d 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/DefaultTopologyGraph.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/DefaultTopologyGraph.java
@@ -1,4 +1,4 @@
-package org.onlab.onos.net.trivial.impl;
+package org.onlab.onos.store.trivial.impl;
 
 import org.onlab.graph.AdjacencyListsGraph;
 import org.onlab.onos.net.topology.TopologyEdge;
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/NoOpClockService.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/NoOpClockService.java
similarity index 94%
rename from core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/NoOpClockService.java
rename to core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/NoOpClockService.java
index 88fcddf..b3f8320 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/NoOpClockService.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/NoOpClockService.java
@@ -1,4 +1,4 @@
-package org.onlab.onos.net.trivial.impl;
+package org.onlab.onos.store.trivial.impl;
 
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Service;
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/PathKey.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/PathKey.java
similarity index 95%
rename from core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/PathKey.java
rename to core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/PathKey.java
index da3b055..7169290 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/PathKey.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/PathKey.java
@@ -1,4 +1,4 @@
-package org.onlab.onos.net.trivial.impl;
+package org.onlab.onos.store.trivial.impl;
 
 import org.onlab.onos.net.DeviceId;
 
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleClusterStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleClusterStore.java
similarity index 97%
rename from core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleClusterStore.java
rename to core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleClusterStore.java
index 2208c86..1363fd0 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleClusterStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleClusterStore.java
@@ -1,4 +1,4 @@
-package org.onlab.onos.net.trivial.impl;
+package org.onlab.onos.store.trivial.impl;
 
 import com.google.common.collect.ImmutableSet;
 import org.apache.felix.scr.annotations.Activate;
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStore.java
similarity index 97%
rename from core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceStore.java
rename to core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStore.java
index 7c7d38f..df20b2d 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStore.java
@@ -1,4 +1,4 @@
-package org.onlab.onos.net.trivial.impl;
+package org.onlab.onos.store.trivial.impl;
 
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
@@ -143,7 +143,7 @@
     }
 
     @Override
-    public List<DeviceEvent> updatePorts(DeviceId deviceId,
+    public List<DeviceEvent> updatePorts(ProviderId providerId, DeviceId deviceId,
                                   List<PortDescription> portDescriptions) {
         List<DeviceEvent> events = new ArrayList<>();
         synchronized (this) {
@@ -221,7 +221,7 @@
     }
 
     @Override
-    public DeviceEvent updatePortStatus(DeviceId deviceId,
+    public DeviceEvent updatePortStatus(ProviderId providerId, DeviceId deviceId,
                                  PortDescription portDescription) {
         synchronized (this) {
             Device device = devices.get(deviceId);
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleFlowRuleStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleFlowRuleStore.java
similarity index 98%
rename from core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleFlowRuleStore.java
rename to core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleFlowRuleStore.java
index 6b6c157..2f43211 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleFlowRuleStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleFlowRuleStore.java
@@ -1,4 +1,4 @@
-package org.onlab.onos.net.trivial.impl;
+package org.onlab.onos.store.trivial.impl;
 
 import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_ADDED;
 import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleHostStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleHostStore.java
similarity index 99%
rename from core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleHostStore.java
rename to core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleHostStore.java
index 94a3f05..92d6a22 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleHostStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleHostStore.java
@@ -1,4 +1,4 @@
-package org.onlab.onos.net.trivial.impl;
+package org.onlab.onos.store.trivial.impl;
 
 import static org.onlab.onos.net.host.HostEvent.Type.HOST_ADDED;
 import static org.onlab.onos.net.host.HostEvent.Type.HOST_MOVED;
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleLinkStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleLinkStore.java
similarity index 98%
rename from core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleLinkStore.java
rename to core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleLinkStore.java
index 319df89..a0e569d 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleLinkStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleLinkStore.java
@@ -1,4 +1,4 @@
-package org.onlab.onos.net.trivial.impl;
+package org.onlab.onos.store.trivial.impl;
 
 import com.google.common.collect.HashMultimap;
 import com.google.common.collect.ImmutableSet;
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleMastershipStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleMastershipStore.java
new file mode 100644
index 0000000..0439d79
--- /dev/null
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleMastershipStore.java
@@ -0,0 +1,217 @@
+package org.onlab.onos.store.trivial.impl;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+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.Service;
+import org.onlab.onos.cluster.ControllerNode;
+import org.onlab.onos.cluster.DefaultControllerNode;
+import org.onlab.onos.cluster.MastershipEvent;
+import org.onlab.onos.cluster.MastershipStore;
+import org.onlab.onos.cluster.MastershipStoreDelegate;
+import org.onlab.onos.cluster.MastershipTerm;
+import org.onlab.onos.cluster.NodeId;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.MastershipRole;
+import org.onlab.onos.store.AbstractStore;
+import org.onlab.packet.IpPrefix;
+import org.slf4j.Logger;
+
+import static org.onlab.onos.cluster.MastershipEvent.Type.*;
+
+/**
+ * Manages inventory of controller mastership over devices using
+ * trivial, non-distributed in-memory structures implementation.
+ */
+@Component(immediate = true)
+@Service
+public class SimpleMastershipStore
+        extends AbstractStore<MastershipEvent, MastershipStoreDelegate>
+        implements MastershipStore {
+
+    private final Logger log = getLogger(getClass());
+
+    public static final IpPrefix LOCALHOST = IpPrefix.valueOf("127.0.0.1");
+
+    private ControllerNode instance =
+            new DefaultControllerNode(new NodeId("local"), LOCALHOST);
+
+    //devices mapped to their masters, to emulate multiple nodes
+    protected final Map<DeviceId, NodeId> masterMap = new HashMap<>();
+    //emulate backups with pile of nodes
+    protected final Set<NodeId> backups = new HashSet<>();
+    //terms
+    protected final Map<DeviceId, AtomicInteger> termMap = new HashMap<>();
+
+    @Activate
+    public void activate() {
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        log.info("Stopped");
+    }
+
+    @Override
+    public MastershipEvent setMaster(NodeId nodeId, DeviceId deviceId) {
+        MastershipRole role = getRole(nodeId, deviceId);
+
+        synchronized (this) {
+            switch (role) {
+                case MASTER:
+                    return null;
+                case STANDBY:
+                    masterMap.put(deviceId, nodeId);
+                    termMap.get(deviceId).incrementAndGet();
+                    backups.add(nodeId);
+                    break;
+                case NONE:
+                    masterMap.put(deviceId, nodeId);
+                    termMap.put(deviceId, new AtomicInteger());
+                    backups.add(nodeId);
+                    break;
+                default:
+                    log.warn("unknown Mastership Role {}", role);
+                    return null;
+            }
+        }
+
+        return new MastershipEvent(MASTER_CHANGED, deviceId, nodeId);
+    }
+
+    @Override
+    public NodeId getMaster(DeviceId deviceId) {
+        return masterMap.get(deviceId);
+    }
+
+    @Override
+    public Set<DeviceId> getDevices(NodeId nodeId) {
+        Set<DeviceId> ids = new HashSet<>();
+        for (Map.Entry<DeviceId, NodeId> d : masterMap.entrySet()) {
+            if (d.getValue().equals(nodeId)) {
+                ids.add(d.getKey());
+            }
+        }
+        return Collections.unmodifiableSet(ids);
+    }
+
+    @Override
+    public MastershipRole requestRole(DeviceId deviceId) {
+        //query+possible reelection
+        NodeId node = instance.id();
+        MastershipRole role = getRole(node, deviceId);
+
+        switch (role) {
+            case MASTER:
+                break;
+            case STANDBY:
+                synchronized (this) {
+                    //try to "re-elect", since we're really not distributed
+                    NodeId rel = reelect(node);
+                    if (rel == null) {
+                        masterMap.put(deviceId, node);
+                        termMap.put(deviceId, new AtomicInteger());
+                        role = MastershipRole.MASTER;
+                    }
+                    backups.add(node);
+                }
+                break;
+            case NONE:
+                //first to get to it, say we are master
+                synchronized (this) {
+                    masterMap.put(deviceId, node);
+                    termMap.put(deviceId, new AtomicInteger());
+                    backups.add(node);
+                    role = MastershipRole.MASTER;
+                }
+                break;
+            default:
+                log.warn("unknown Mastership Role {}", role);
+        }
+        return role;
+    }
+
+    @Override
+    public MastershipRole getRole(NodeId nodeId, DeviceId deviceId) {
+        //just query
+        NodeId current = masterMap.get(deviceId);
+        MastershipRole role;
+
+        if (current == null) {
+            if (backups.contains(nodeId)) {
+                role = MastershipRole.STANDBY;
+            } else {
+                role = MastershipRole.NONE;
+            }
+        } else {
+            if (current.equals(nodeId)) {
+                role = MastershipRole.MASTER;
+            } else {
+                role = MastershipRole.STANDBY;
+            }
+        }
+        return role;
+    }
+
+    @Override
+    public MastershipTerm getTermFor(DeviceId deviceId) {
+        if ((masterMap.get(deviceId) == null) ||
+                (termMap.get(deviceId) == null)) {
+            return null;
+        }
+        return MastershipTerm.of(
+                masterMap.get(deviceId), termMap.get(deviceId).get());
+    }
+
+    @Override
+    public MastershipEvent unsetMaster(NodeId nodeId, DeviceId deviceId) {
+        MastershipRole role = getRole(nodeId, deviceId);
+        synchronized (this) {
+            switch (role) {
+                case MASTER:
+                    NodeId backup = reelect(nodeId);
+                    if (backup == null) {
+                        masterMap.remove(deviceId);
+                    } else {
+                        masterMap.put(deviceId, backup);
+                        termMap.get(deviceId).incrementAndGet();
+                        return new MastershipEvent(MASTER_CHANGED, deviceId, backup);
+                    }
+                case STANDBY:
+                case NONE:
+                    if (!termMap.containsKey(deviceId)) {
+                        termMap.put(deviceId, new AtomicInteger());
+                    }
+                    backups.add(nodeId);
+                    break;
+                default:
+                    log.warn("unknown Mastership Role {}", role);
+            }
+        }
+        return null;
+    }
+
+    //dumbly selects next-available node that's not the current one
+    //emulate leader election
+    private NodeId reelect(NodeId nodeId) {
+        NodeId backup = null;
+        for (NodeId n : backups) {
+            if (!n.equals(nodeId)) {
+                backup = n;
+                break;
+            }
+        }
+        return backup;
+    }
+
+}
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleTopologyStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleTopologyStore.java
similarity index 98%
rename from core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleTopologyStore.java
rename to core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleTopologyStore.java
index 32cc1f7..4e7d5ed 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleTopologyStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleTopologyStore.java
@@ -1,4 +1,4 @@
-package org.onlab.onos.net.trivial.impl;
+package org.onlab.onos.store.trivial.impl;
 
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/package-info.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/package-info.java
similarity index 73%
rename from core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/package-info.java
rename to core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/package-info.java
index 9e3f28f..f697a87 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/net/trivial/impl/package-info.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/package-info.java
@@ -2,4 +2,4 @@
  * Implementations of in-memory stores suitable for unit testing and
  * experimentation; not for production use.
  */
-package org.onlab.onos.net.trivial.impl;
+package org.onlab.onos.store.trivial.impl;
diff --git a/core/store/trivial/src/test/java/org/onlab/onos/net/trivial/impl/DefaultTopologyTest.java b/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/DefaultTopologyTest.java
similarity index 98%
rename from core/store/trivial/src/test/java/org/onlab/onos/net/trivial/impl/DefaultTopologyTest.java
rename to core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/DefaultTopologyTest.java
index 57f4d78..ef383c8 100644
--- a/core/store/trivial/src/test/java/org/onlab/onos/net/trivial/impl/DefaultTopologyTest.java
+++ b/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/DefaultTopologyTest.java
@@ -1,4 +1,4 @@
-package org.onlab.onos.net.trivial.impl;
+package org.onlab.onos.store.trivial.impl;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/core/store/trivial/src/test/java/org/onlab/onos/net/trivial/impl/SimpleDeviceStoreTest.java b/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStoreTest.java
similarity index 96%
rename from core/store/trivial/src/test/java/org/onlab/onos/net/trivial/impl/SimpleDeviceStoreTest.java
rename to core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStoreTest.java
index f973d9b..42dc7c3 100644
--- a/core/store/trivial/src/test/java/org/onlab/onos/net/trivial/impl/SimpleDeviceStoreTest.java
+++ b/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStoreTest.java
@@ -1,7 +1,7 @@
 /**
  *
  */
-package org.onlab.onos.net.trivial.impl;
+package org.onlab.onos.store.trivial.impl;
 
 import static org.junit.Assert.*;
 import static org.onlab.onos.net.Device.Type.SWITCH;
@@ -182,7 +182,7 @@
                 new DefaultPortDescription(P2, true)
                 );
 
-        List<DeviceEvent> events = deviceStore.updatePorts(DID1, pds);
+        List<DeviceEvent> events = deviceStore.updatePorts(PID, DID1, pds);
 
         Set<PortNumber> expectedPorts = Sets.newHashSet(P1, P2);
         for (DeviceEvent event : events) {
@@ -201,7 +201,7 @@
                 new DefaultPortDescription(P3, true)
                 );
 
-        events = deviceStore.updatePorts(DID1, pds2);
+        events = deviceStore.updatePorts(PID, DID1, pds2);
         assertFalse("event should be triggered", events.isEmpty());
         for (DeviceEvent event : events) {
             PortNumber num = event.port().number();
@@ -224,7 +224,7 @@
                 new DefaultPortDescription(P1, false),
                 new DefaultPortDescription(P2, true)
                 );
-        events = deviceStore.updatePorts(DID1, pds3);
+        events = deviceStore.updatePorts(PID, DID1, pds3);
         assertFalse("event should be triggered", events.isEmpty());
         for (DeviceEvent event : events) {
             PortNumber num = event.port().number();
@@ -249,9 +249,9 @@
         List<PortDescription> pds = Arrays.<PortDescription>asList(
                 new DefaultPortDescription(P1, true)
                 );
-        deviceStore.updatePorts(DID1, pds);
+        deviceStore.updatePorts(PID, DID1, pds);
 
-        DeviceEvent event = deviceStore.updatePortStatus(DID1,
+        DeviceEvent event = deviceStore.updatePortStatus(PID, DID1,
                 new DefaultPortDescription(P1, false));
         assertEquals(PORT_UPDATED, event.type());
         assertDevice(DID1, SW1, event.subject());
@@ -267,7 +267,7 @@
                 new DefaultPortDescription(P1, true),
                 new DefaultPortDescription(P2, true)
                 );
-        deviceStore.updatePorts(DID1, pds);
+        deviceStore.updatePorts(PID, DID1, pds);
 
         Set<PortNumber> expectedPorts = Sets.newHashSet(P1, P2);
         List<Port> ports = deviceStore.getPorts(DID1);
@@ -290,7 +290,7 @@
                 new DefaultPortDescription(P1, true),
                 new DefaultPortDescription(P2, false)
                 );
-        deviceStore.updatePorts(DID1, pds);
+        deviceStore.updatePorts(PID, DID1, pds);
 
         Port port1 = deviceStore.getPort(DID1, P1);
         assertEquals(P1, port1.number());
diff --git a/core/store/trivial/src/test/java/org/onlab/onos/net/trivial/impl/SimpleLinkStoreTest.java b/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleLinkStoreTest.java
similarity index 99%
rename from core/store/trivial/src/test/java/org/onlab/onos/net/trivial/impl/SimpleLinkStoreTest.java
rename to core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleLinkStoreTest.java
index 50d0e47..eb4a312 100644
--- a/core/store/trivial/src/test/java/org/onlab/onos/net/trivial/impl/SimpleLinkStoreTest.java
+++ b/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleLinkStoreTest.java
@@ -1,4 +1,4 @@
-package org.onlab.onos.net.trivial.impl;
+package org.onlab.onos.store.trivial.impl;
 
 import static org.junit.Assert.*;
 import static org.onlab.onos.net.DeviceId.deviceId;
diff --git a/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleMastershipStoreTest.java b/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleMastershipStoreTest.java
new file mode 100644
index 0000000..32d3d68
--- /dev/null
+++ b/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleMastershipStoreTest.java
@@ -0,0 +1,160 @@
+package org.onlab.onos.store.trivial.impl;
+
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.onos.cluster.MastershipTerm;
+import org.onlab.onos.cluster.NodeId;
+import org.onlab.onos.net.DeviceId;
+
+import com.google.common.collect.Sets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.onlab.onos.net.MastershipRole.*;
+import static org.onlab.onos.cluster.MastershipEvent.Type.*;
+
+/**
+ * Test for the simple MastershipStore implementation.
+ */
+public class SimpleMastershipStoreTest {
+
+    private static final DeviceId DID1 = DeviceId.deviceId("of:01");
+    private static final DeviceId DID2 = DeviceId.deviceId("of:02");
+    private static final DeviceId DID3 = DeviceId.deviceId("of:03");
+    private static final DeviceId DID4 = DeviceId.deviceId("of:04");
+
+    private static final NodeId N1 = new NodeId("local");
+    private static final NodeId N2 = new NodeId("other");
+
+    private SimpleMastershipStore sms;
+
+    @Before
+    public void setUp() throws Exception {
+        sms = new SimpleMastershipStore();
+        sms.activate();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        sms.deactivate();
+    }
+
+    @Test
+    public void getRole() {
+        //special case, no backup or master
+        put(DID1, N1, false, false);
+        assertEquals("wrong role", NONE, sms.getRole(N1, DID1));
+
+        //backup exists but we aren't mapped
+        put(DID2, N1, false, true);
+        assertEquals("wrong role", STANDBY, sms.getRole(N1, DID2));
+
+        //N2 is master
+        put(DID3, N2, true, true);
+        assertEquals("wrong role", MASTER, sms.getRole(N2, DID3));
+
+        //N2 is master but N1 is only in backups set
+        put(DID4, N2, true, false);
+        assertEquals("wrong role", STANDBY, sms.getRole(N1, DID4));
+    }
+
+    @Test
+    public void getMaster() {
+        put(DID3, N2, true, true);
+        assertEquals("wrong role", MASTER, sms.getRole(N2, DID3));
+        assertEquals("wrong device", N2, sms.getMaster(DID3));
+    }
+
+    @Test
+    public void setMaster() {
+        put(DID1, N1, false, false);
+        assertEquals("wrong event", MASTER_CHANGED, sms.setMaster(N1, DID1).type());
+        assertEquals("wrong role", MASTER, sms.getRole(N1, DID1));
+        //set node that's already master - should be ignored
+        assertNull("wrong event", sms.setMaster(N1, DID1));
+
+        //set STANDBY to MASTER
+        put(DID2, N1, false, true);
+        assertEquals("wrong role", STANDBY, sms.getRole(N1, DID2));
+        assertEquals("wrong event", MASTER_CHANGED, sms.setMaster(N1, DID2).type());
+        assertEquals("wrong role", MASTER, sms.getRole(N1, DID2));
+    }
+
+    @Test
+    public void getDevices() {
+        Set<DeviceId> d = Sets.newHashSet(DID1, DID2);
+
+        put(DID1, N2, true, true);
+        put(DID2, N2, true, true);
+        put(DID3, N1, true, true);
+        assertTrue("wrong devices", d.equals(sms.getDevices(N2)));
+    }
+
+    @Test
+    public void getTermFor() {
+        put(DID1, N1, true, true);
+        assertEquals("wrong term", MastershipTerm.of(N1, 0), sms.getTermFor(DID1));
+
+        //switch to N2 and back - 2 term switches
+        sms.setMaster(N2, DID1);
+        sms.setMaster(N1, DID1);
+        assertEquals("wrong term", MastershipTerm.of(N1, 2), sms.getTermFor(DID1));
+    }
+
+    @Test
+    public void requestRole() {
+        //NONE - become MASTER
+        put(DID1, N1, false, false);
+        assertEquals("wrong role", MASTER, sms.requestRole(DID1));
+
+        //STANDBY without backup - become MASTER
+        put(DID2, N1, false, true);
+        assertEquals("wrong role", MASTER, sms.requestRole(DID2));
+
+        //STANDBY with backup - stay STANDBY
+        put(DID3, N2, false, true);
+        assertEquals("wrong role", STANDBY, sms.requestRole(DID3));
+
+        //local (N1) is MASTER - stay MASTER
+        put(DID4, N1, true, true);
+        assertEquals("wrong role", MASTER, sms.requestRole(DID4));
+    }
+
+    @Test
+    public void unsetMaster() {
+        //NONE - record backup but take no other action
+        put(DID1, N1, false, false);
+        sms.unsetMaster(N1, DID1);
+        assertTrue("not backed up", sms.backups.contains(N1));
+        sms.termMap.clear();
+        sms.unsetMaster(N1, DID1);
+        assertTrue("term not set", sms.termMap.containsKey(DID1));
+
+        //no backup, MASTER
+        put(DID1, N1, true, true);
+        assertNull("wrong event", sms.unsetMaster(N1, DID1));
+        assertNull("wrong node", sms.masterMap.get(DID1));
+
+        //backup, switch
+        sms.masterMap.clear();
+        put(DID1, N1, true, true);
+        put(DID2, N2, true, true);
+        assertEquals("wrong event", MASTER_CHANGED, sms.unsetMaster(N1, DID1).type());
+    }
+
+    //helper to populate master/backup structures
+    private void put(DeviceId dev, NodeId node, boolean store, boolean backup) {
+        if (store) {
+            sms.masterMap.put(dev, node);
+        }
+        if (backup) {
+            sms.backups.add(node);
+        }
+        sms.termMap.put(dev, new AtomicInteger());
+    }
+}
diff --git a/pom.xml b/pom.xml
index 5cf67de..3c2140f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -419,7 +419,7 @@
                         <group>
                             <title>Core Subsystems</title>
                             <packages>
-                                org.onlab.onos.cluster.impl:org.onlab.onos.net.device.impl:org.onlab.onos.net.link.impl:org.onlab.onos.net.host.impl:org.onlab.onos.net.topology.impl:org.onlab.onos.net.packet.impl:org.onlab.onos.net.flow.impl:org.onlab.onos.net.trivial.*:org.onlab.onos.net.*.impl:org.onlab.onos.event.impl:org.onlab.onos.store.*
+                                org.onlab.onos.cluster.impl:org.onlab.onos.net.device.impl:org.onlab.onos.net.link.impl:org.onlab.onos.net.host.impl:org.onlab.onos.net.topology.impl:org.onlab.onos.net.packet.impl:org.onlab.onos.net.flow.impl:org.onlab.onos.store.trivial.*:org.onlab.onos.net.*.impl:org.onlab.onos.event.impl:org.onlab.onos.store.*
                             </packages>
                         </group>
                         <group>
diff --git a/tools/dev/bash_profile b/tools/dev/bash_profile
index 8389ff1..1d3097f 100644
--- a/tools/dev/bash_profile
+++ b/tools/dev/bash_profile
@@ -6,22 +6,21 @@
 export ONOS_ROOT=${ONOS_ROOT:-~/onos-next}
 
 # Setup some environmental context for developers
-export JAVA_HOME=$(/usr/libexec/java_home)
+export JAVA_HOME=$(/usr/libexec/java_home -v 1.7)
 export MAVEN=${MAVEN:-~/Applications/apache-maven-3.2.2}
 export KARAF=${KARAF:-~/Applications/apache-karaf-3.0.1}
 export KARAF_LOG=$KARAF/data/log/karaf.log
 
 # Setup a path
-export PS=":"
-export PATH="$PATH:$ONOS_ROOT/tools/dev:$ONOS_ROOT/tools/build"
-export PATH="$PATH:$ONOS_ROOT/tools/test/bin"
+export PATH="$PATH:$ONOS_ROOT/tools/dev/bin:$ONOS_ROOT/tools/test/bin"
+export PATH="$PATH:$ONOS_ROOT/tools/build"
 export PATH="$PATH:$MAVEN/bin:$KARAF/bin"
 export PATH="$PATH:."
 
 # Convenience utility to warp to various ONOS source projects
 # e.g. 'o api', 'o dev', 'o'
 function o {
-    cd $(find $ONOS_ROOT/ -type d | egrep -v '\.git|target|src' | \
+    cd $(find $ONOS_ROOT/ -type d | egrep -v '\.git|target' | \
         egrep "${1:-$ONOS_ROOT}" | head -n 1)
 }
 
@@ -30,11 +29,12 @@
 
 # Short-hand for ONOS build, package and test.
 alias ob='onos-build'
+alias obs='onos-build-selective'
 alias op='onos-package'
 alias ot='onos-test'
 
 # Short-hand for tailing the ONOS (karaf) log 
-alias tl='$ONOS_ROOT/tools/dev/watchLog'
+alias tl='$ONOS_ROOT/tools/dev/bin/onos-local-log'
 alias tlo='tl | grep --colour=always org.onlab'
 
 # Pretty-print JSON output
diff --git a/tools/dev/bin/onos-build-selective b/tools/dev/bin/onos-build-selective
new file mode 100755
index 0000000..2fe0188
--- /dev/null
+++ b/tools/dev/bin/onos-build-selective
@@ -0,0 +1,12 @@
+#!/bin/bash
+#------------------------------------------------------------------------------
+# Selectively builds only those projects that contained modified Java files.
+#------------------------------------------------------------------------------
+
+projects=$(find $ONOS_ROOT -name '*.java' \
+    -not -path '*/openflowj/*' -and -not -path '.git/*' \
+    -exec $ONOS_ROOT/tools/dev/bin/onos-build-selective-hook {} \; | \
+    sort -u | sed "s:$ONOS_ROOT::g" | tr '\n' ',' | \
+    sed 's:/,:,:g;s:,/:,:g;s:^/::g;s:,$::g')
+
+[ -n "$projects" ] && cd $ONOS_ROOT && mvn --projects $projects ${@:-clean install}
\ No newline at end of file
diff --git a/tools/dev/bin/onos-build-selective-hook b/tools/dev/bin/onos-build-selective-hook
new file mode 100755
index 0000000..233ab1a
--- /dev/null
+++ b/tools/dev/bin/onos-build-selective-hook
@@ -0,0 +1,18 @@
+#------------------------------------------------------------------------------
+# Echoes project-level directory if a Java file within is newer than its
+# class file counterpart
+#------------------------------------------------------------------------------
+
+javaFile=${1#*\/src\/*\/java/}
+basename=${1/*\//}
+
+[ $basename = "package-info.java" ] && exit 0
+
+src=${1/$javaFile/}
+project=${src/src*/}
+classFile=${javaFile/.java/.class}
+
+[ ${project}target/classes/$classFile -nt ${src}$javaFile -o \
+    ${project}target/test-classes/$classFile -nt ${src}$javaFile ] \
+    || echo ${src/src*/}
+
diff --git a/tools/dev/watchLog b/tools/dev/bin/onos-local-log
similarity index 100%
rename from tools/dev/watchLog
rename to tools/dev/bin/onos-local-log
diff --git a/tools/test/cells/.reset b/tools/test/cells/.reset
index c025225..5b1bc2c 100644
--- a/tools/test/cells/.reset
+++ b/tools/test/cells/.reset
@@ -1 +1 @@
-unset OC1 OC2 OC3 OC4 OC5 OC6 OC7 OC8 OC9 OCN ONOS_NIC
+unset OC1 OC2 OC3 OC4 OC5 OC6 OC7 OC8 OC9 OCN ONOS_NIC ONOS_FEATURES
diff --git a/utils/misc/src/main/java/org/onlab/util/KryoPool.java b/utils/misc/src/main/java/org/onlab/util/KryoPool.java
index be662a6..3fae0c5 100644
--- a/utils/misc/src/main/java/org/onlab/util/KryoPool.java
+++ b/utils/misc/src/main/java/org/onlab/util/KryoPool.java
@@ -239,12 +239,41 @@
         Kryo kryo = new Kryo();
         kryo.setRegistrationRequired(registrationRequired);
         for (Pair<Class<?>, Serializer<?>> registry : registeredTypes) {
-            if (registry.getRight() == null) {
+            final Serializer<?> serializer = registry.getRight();
+            if (serializer == null) {
                 kryo.register(registry.getLeft());
             } else {
-                kryo.register(registry.getLeft(), registry.getRight());
+                kryo.register(registry.getLeft(), serializer);
+                if (serializer instanceof FamilySerializer) {
+                    FamilySerializer<?> fser = (FamilySerializer<?>) serializer;
+                    fser.registerFamilies(kryo);
+                }
             }
         }
         return kryo;
     }
+
+    /**
+     * Serializer implementation, which required registration of family of Classes.
+     * @param <T> base type of this serializer.
+     */
+    public abstract static class FamilySerializer<T> extends Serializer<T> {
+
+
+        public FamilySerializer(boolean acceptsNull) {
+            super(acceptsNull);
+        }
+
+        public FamilySerializer(boolean acceptsNull, boolean immutable) {
+            super(acceptsNull, immutable);
+        }
+
+        /**
+         * Registers other classes this Serializer supports.
+         *
+         * @param kryo instance to register classes to
+         */
+        public void registerFamilies(Kryo kryo) {
+        }
+    }
 }