Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next

Conflicts:
	core/net/src/main/java/org/onlab/onos/net/device/impl/DeviceManager.java
	core/store/hz/cluster/src/main/java/org/onlab/onos/store/cluster/impl/DistributedMastershipStore.java

Change-Id: Ia1274657b27e01366a4a87196a13068d7104ee80
diff --git a/apps/foo/pom.xml b/apps/foo/pom.xml
index 868b992..6109263 100644
--- a/apps/foo/pom.xml
+++ b/apps/foo/pom.xml
@@ -24,10 +24,20 @@
         </dependency>
         <dependency>
             <groupId>org.onlab.onos</groupId>
+            <artifactId>onlab-osgi</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.onlab.onos</groupId>
             <artifactId>onlab-nio</artifactId>
             <version>${project.version}</version>
         </dependency>
         <dependency>
+            <groupId>org.onlab.onos</groupId>
+            <artifactId>onlab-netty</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
             <groupId>org.apache.karaf.shell</groupId>
             <artifactId>org.apache.karaf.shell.console</artifactId>
         </dependency>
diff --git a/apps/foo/src/main/java/org/onlab/onos/foo/FooComponent.java b/apps/foo/src/main/java/org/onlab/onos/foo/FooComponent.java
index 1fafc32..473ea6e 100644
--- a/apps/foo/src/main/java/org/onlab/onos/foo/FooComponent.java
+++ b/apps/foo/src/main/java/org/onlab/onos/foo/FooComponent.java
@@ -11,6 +11,9 @@
 import org.onlab.onos.net.device.DeviceEvent;
 import org.onlab.onos.net.device.DeviceListener;
 import org.onlab.onos.net.device.DeviceService;
+import org.onlab.onos.net.intent.IntentEvent;
+import org.onlab.onos.net.intent.IntentListener;
+import org.onlab.onos.net.intent.IntentService;
 import org.slf4j.Logger;
 
 import static org.slf4j.LoggerFactory.getLogger;
@@ -29,13 +32,18 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected DeviceService deviceService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentService intentService;
+
     private final ClusterEventListener clusterListener = new InnerClusterListener();
     private final DeviceListener deviceListener = new InnerDeviceListener();
+    private final IntentListener intentListener = new InnerIntentListener();
 
     @Activate
     public void activate() {
         clusterService.addListener(clusterListener);
         deviceService.addListener(deviceListener);
+        intentService.addListener(intentListener);
         log.info("Started");
     }
 
@@ -43,6 +51,7 @@
     public void deactivate() {
         clusterService.removeListener(clusterListener);
         deviceService.removeListener(deviceListener);
+        intentService.removeListener(intentListener);
         log.info("Stopped");
     }
 
@@ -59,6 +68,23 @@
             log.info("YEEEEHAAAAW! {}", event);
         }
     }
+
+    private class InnerIntentListener implements IntentListener {
+        @Override
+        public void event(IntentEvent event) {
+            String message;
+            if (event.type() == IntentEvent.Type.SUBMITTED) {
+                message = "WOW! It looks like someone has some intentions: {}";
+            } else if (event.type() == IntentEvent.Type.INSTALLED) {
+                message = "AWESOME! So far things are going great: {}";
+            } else if (event.type() == IntentEvent.Type.WITHDRAWN) {
+                message = "HMMM! Ambitions are fading apparently: {}";
+            } else {
+                message = "CRAP!!! Things are not turning out as intended: {}";
+            }
+            log.info(message, event.subject());
+        }
+    }
 }
 
 
diff --git a/apps/foo/src/main/java/org/onlab/onos/foo/NettyEchoHandler.java b/apps/foo/src/main/java/org/onlab/onos/foo/NettyEchoHandler.java
new file mode 100644
index 0000000..1049a6d
--- /dev/null
+++ b/apps/foo/src/main/java/org/onlab/onos/foo/NettyEchoHandler.java
@@ -0,0 +1,23 @@
+package org.onlab.onos.foo;
+
+import java.io.IOException;
+
+import org.onlab.netty.Message;
+import org.onlab.netty.MessageHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Message handler that echos the message back to the sender.
+ */
+public class NettyEchoHandler implements MessageHandler {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Override
+    public void handle(Message message) throws IOException {
+        //log.info("Received message. Echoing it back to the sender.");
+        message.respond(message.payload());
+    }
+}
diff --git a/apps/foo/src/main/java/org/onlab/onos/foo/NettyLoggingHandler.java b/apps/foo/src/main/java/org/onlab/onos/foo/NettyLoggingHandler.java
new file mode 100644
index 0000000..b35a46f
--- /dev/null
+++ b/apps/foo/src/main/java/org/onlab/onos/foo/NettyLoggingHandler.java
@@ -0,0 +1,19 @@
+package org.onlab.onos.foo;
+
+import org.onlab.netty.Message;
+import org.onlab.netty.MessageHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A MessageHandler that simply logs the information.
+ */
+public class NettyLoggingHandler implements MessageHandler {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Override
+    public void handle(Message message) {
+        //log.info("Received message. Payload has {} bytes", message.payload().length);
+    }
+}
diff --git a/apps/foo/src/main/java/org/onlab/onos/foo/SimpleNettyClient.java b/apps/foo/src/main/java/org/onlab/onos/foo/SimpleNettyClient.java
new file mode 100644
index 0000000..06daad8
--- /dev/null
+++ b/apps/foo/src/main/java/org/onlab/onos/foo/SimpleNettyClient.java
@@ -0,0 +1,82 @@
+package org.onlab.onos.foo;
+
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.onlab.metrics.MetricsComponent;
+import org.onlab.metrics.MetricsFeature;
+import org.onlab.metrics.MetricsManager;
+import org.onlab.netty.Endpoint;
+import org.onlab.netty.NettyMessagingService;
+import org.onlab.netty.Response;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.codahale.metrics.Timer;
+
+// FIXME: Should be move out to test or app
+public final class SimpleNettyClient {
+
+private static Logger log = LoggerFactory.getLogger(SimpleNettyClient.class);
+
+        private SimpleNettyClient() {
+    }
+
+    public static void main(String[] args)
+            throws IOException, InterruptedException, ExecutionException,
+            TimeoutException {
+        try {
+            startStandalone(args);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        System.exit(0);
+    }
+    public static void startStandalone(String... args) throws Exception {
+        String host = args.length > 0 ? args[0] : "localhost";
+        int port = args.length > 1 ? Integer.parseInt(args[1]) : 8081;
+        int warmup = args.length > 2 ? Integer.parseInt(args[2]) : 1000;
+        int iterations = args.length > 3 ? Integer.parseInt(args[3]) : 50 * 100000;
+        NettyMessagingService messaging = new TestNettyMessagingService(9081);
+        MetricsManager metrics = new MetricsManager();
+        messaging.activate();
+        metrics.activate();
+        MetricsFeature feature = new MetricsFeature("latency");
+        MetricsComponent component = metrics.registerComponent("NettyMessaging");
+        log.info("warmup....");
+
+        for (int i = 0; i < warmup; i++) {
+            messaging.sendAsync(new Endpoint(host, port), "simple", "Hello World".getBytes());
+            Response response = messaging
+                    .sendAndReceive(new Endpoint(host, port), "echo",
+                            "Hello World".getBytes());
+        }
+
+        log.info("measuring async sender");
+        Timer sendAsyncTimer = metrics.createTimer(component, feature, "AsyncSender");
+
+        for (int i = 0; i < iterations; i++) {
+            Timer.Context context = sendAsyncTimer.time();
+            messaging.sendAsync(new Endpoint(host, port), "simple", "Hello World".getBytes());
+            context.stop();
+        }
+
+        Timer sendAndReceiveTimer = metrics.createTimer(component, feature, "SendAndReceive");
+        for (int i = 0; i < iterations; i++) {
+            Timer.Context context = sendAndReceiveTimer.time();
+            Response response = messaging
+                    .sendAndReceive(new Endpoint(host, port), "echo",
+                                    "Hello World".getBytes());
+            // System.out.println("Got back:" + new String(response.get(2, TimeUnit.SECONDS)));
+            context.stop();
+        }
+    }
+
+    public static class TestNettyMessagingService extends NettyMessagingService {
+        public TestNettyMessagingService(int port) throws Exception {
+            super(port);
+        }
+    }
+}
diff --git a/apps/foo/src/main/java/org/onlab/onos/foo/SimpleNettyClientCommand.java b/apps/foo/src/main/java/org/onlab/onos/foo/SimpleNettyClientCommand.java
new file mode 100644
index 0000000..cce31db
--- /dev/null
+++ b/apps/foo/src/main/java/org/onlab/onos/foo/SimpleNettyClientCommand.java
@@ -0,0 +1,42 @@
+package org.onlab.onos.foo;
+
+import static org.onlab.onos.foo.SimpleNettyClient.startStandalone;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.onos.cli.AbstractShellCommand;
+
+/**
+ * Test Netty client performance.
+ */
+@Command(scope = "onos", name = "simple-netty-client",
+        description = "Starts the simple Netty client")
+public class SimpleNettyClientCommand extends AbstractShellCommand {
+
+    //FIXME: replace these arguments with proper ones needed for the test.
+    @Argument(index = 0, name = "hostname", description = "Server Hostname",
+            required = false, multiValued = false)
+    String host = "localhost";
+
+    @Argument(index = 3, name = "port", description = "Port",
+            required = false, multiValued = false)
+    String port = "8081";
+
+    @Argument(index = 1, name = "warmupCount", description = "Warm-up count",
+            required = false, multiValued = false)
+    String warmup = "1000";
+
+    @Argument(index = 2, name = "messageCount", description = "Message count",
+            required = false, multiValued = false)
+    String messageCount = "100000";
+
+    @Override
+    protected void execute() {
+        try {
+            startStandalone(new String[]{host, port, warmup, messageCount});
+        } catch (Exception e) {
+            error("Unable to start client %s", e);
+        }
+    }
+
+}
diff --git a/apps/foo/src/main/java/org/onlab/onos/foo/SimpleNettyServer.java b/apps/foo/src/main/java/org/onlab/onos/foo/SimpleNettyServer.java
new file mode 100644
index 0000000..5578fcd
--- /dev/null
+++ b/apps/foo/src/main/java/org/onlab/onos/foo/SimpleNettyServer.java
@@ -0,0 +1,27 @@
+package org.onlab.onos.foo;
+
+import org.onlab.netty.NettyMessagingService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test to measure Messaging performance.
+ */
+    public final class SimpleNettyServer {
+        private static Logger log = LoggerFactory.getLogger(SimpleNettyServer.class);
+
+            private SimpleNettyServer() {}
+
+            public static void main(String... args) throws Exception {
+                startStandalone(args);
+                System.exit(0);
+            }
+
+        public static void startStandalone(String[] args) throws Exception {
+            NettyMessagingService server = new NettyMessagingService(8081);
+            server.activate();
+            server.registerHandler("simple", new NettyLoggingHandler());
+            server.registerHandler("echo", new NettyEchoHandler());
+        }
+    }
+
diff --git a/apps/foo/src/main/java/org/onlab/onos/foo/SimpleNettyServerCommand.java b/apps/foo/src/main/java/org/onlab/onos/foo/SimpleNettyServerCommand.java
new file mode 100644
index 0000000..17b2586
--- /dev/null
+++ b/apps/foo/src/main/java/org/onlab/onos/foo/SimpleNettyServerCommand.java
@@ -0,0 +1,38 @@
+package org.onlab.onos.foo;
+
+import static org.onlab.onos.foo.SimpleNettyServer.startStandalone;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.onos.cli.AbstractShellCommand;
+
+/**
+ * Starts the Simple Netty server.
+ */
+@Command(scope = "onos", name = "simple-netty-server",
+         description = "Starts the simple netty server")
+public class SimpleNettyServerCommand extends AbstractShellCommand {
+
+    //FIXME: Replace these with parameters for
+    @Argument(index = 0, name = "serverIp", description = "Server IP address",
+              required = false, multiValued = false)
+    String serverIp = "127.0.0.1";
+
+    @Argument(index = 1, name = "workers", description = "IO workers",
+              required = false, multiValued = false)
+    String workers = "6";
+
+    @Argument(index = 2, name = "messageLength", description = "Message length (bytes)",
+              required = false, multiValued = false)
+    String messageLength = "128";
+
+    @Override
+    protected void execute() {
+        try {
+            startStandalone(new String[]{serverIp, workers, messageLength});
+        } catch (Exception e) {
+            error("Unable to start server %s", e);
+        }
+    }
+
+}
diff --git a/apps/foo/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/foo/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index 93bd020..f8ee2b8 100644
--- a/apps/foo/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/apps/foo/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -7,6 +7,12 @@
         <command>
             <action class="org.onlab.onos.foo.TestIOServerCommand"/>
         </command>
+        <command>
+            <action class="org.onlab.onos.foo.SimpleNettyServerCommand"/>
+        </command>
+        <command>
+            <action class="org.onlab.onos.foo.SimpleNettyClientCommand"/>
+        </command>
     </command-bundle>
 
 </blueprint>
diff --git a/apps/fwd/src/main/java/org/onlab/onos/fwd/ReactiveForwarding.java b/apps/fwd/src/main/java/org/onlab/onos/fwd/ReactiveForwarding.java
index aaf5350..1accddb 100644
--- a/apps/fwd/src/main/java/org/onlab/onos/fwd/ReactiveForwarding.java
+++ b/apps/fwd/src/main/java/org/onlab/onos/fwd/ReactiveForwarding.java
@@ -26,9 +26,7 @@
 import org.onlab.onos.net.packet.PacketContext;
 import org.onlab.onos.net.packet.PacketProcessor;
 import org.onlab.onos.net.packet.PacketService;
-import org.onlab.onos.net.proxyarp.ProxyArpService;
 import org.onlab.onos.net.topology.TopologyService;
-import org.onlab.packet.ARP;
 import org.onlab.packet.Ethernet;
 import org.slf4j.Logger;
 
@@ -39,6 +37,7 @@
 public class ReactiveForwarding {
 
     private static final int TIMEOUT = 10;
+    private static final int PRIORITY = 10;
 
     private final Logger log = getLogger(getClass());
 
@@ -54,9 +53,6 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected FlowRuleService flowRuleService;
 
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected ProxyArpService proxyArpService;
-
     private ReactivePacketProcessor processor = new ReactivePacketProcessor();
 
     private ApplicationId appId;
@@ -64,7 +60,7 @@
     @Activate
     public void activate() {
         appId = ApplicationId.getAppId();
-        packetService.addProcessor(processor, PacketProcessor.ADVISOR_MAX + 1);
+        packetService.addProcessor(processor, PacketProcessor.ADVISOR_MAX + 2);
         log.info("Started with Application ID {}", appId.id());
     }
 
@@ -92,16 +88,6 @@
 
             InboundPacket pkt = context.inPacket();
             Ethernet ethPkt = pkt.parsed();
-            if (ethPkt.getEtherType() == Ethernet.TYPE_ARP) {
-                ARP arp = (ARP) ethPkt.getPayload();
-                if (arp.getOpCode() == ARP.OP_REPLY) {
-                    proxyArpService.forward(ethPkt);
-                } else if (arp.getOpCode() == ARP.OP_REQUEST) {
-                    proxyArpService.reply(ethPkt);
-                }
-                context.block();
-                return;
-            }
 
             HostId id = HostId.hostId(ethPkt.getDestinationMAC());
 
@@ -180,24 +166,24 @@
         // We don't yet support bufferids in the flowservice so packet out first.
         packetOut(context, portNumber);
 
-        if (context.inPacket().parsed().getEtherType() == Ethernet.TYPE_IPV4) {
 
-            // Install the flow rule to handle this type of message from now on.
-            Ethernet inPkt = context.inPacket().parsed();
-            TrafficSelector.Builder builder = new DefaultTrafficSelector.Builder();
-            builder.matchEthType(inPkt.getEtherType())
-                .matchEthSrc(inPkt.getSourceMAC())
-                .matchEthDst(inPkt.getDestinationMAC())
-                .matchInport(context.inPacket().receivedFrom().port());
 
-            TrafficTreatment.Builder treat = new DefaultTrafficTreatment.Builder();
-            treat.setOutput(portNumber);
+        // Install the flow rule to handle this type of message from now on.
+        Ethernet inPkt = context.inPacket().parsed();
+        TrafficSelector.Builder builder = DefaultTrafficSelector.builder();
+        builder.matchEthType(inPkt.getEtherType())
+        .matchEthSrc(inPkt.getSourceMAC())
+        .matchEthDst(inPkt.getDestinationMAC())
+        .matchInport(context.inPacket().receivedFrom().port());
 
-            FlowRule f = new DefaultFlowRule(context.inPacket().receivedFrom().deviceId(),
-                    builder.build(), treat.build(), 0, appId, TIMEOUT);
+        TrafficTreatment.Builder treat = DefaultTrafficTreatment.builder();
+        treat.setOutput(portNumber);
 
-            flowRuleService.applyFlowRules(f);
-        }
+        FlowRule f = new DefaultFlowRule(context.inPacket().receivedFrom().deviceId(),
+                builder.build(), treat.build(), PRIORITY, appId, TIMEOUT);
+
+        flowRuleService.applyFlowRules(f);
+
     }
 
 }
diff --git a/apps/ifwd/pom.xml b/apps/ifwd/pom.xml
new file mode 100644
index 0000000..bd89143
--- /dev/null
+++ b/apps/ifwd/pom.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.onlab.onos</groupId>
+        <artifactId>onos-apps</artifactId>
+        <version>1.0.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>onos-app-ifwd</artifactId>
+    <packaging>bundle</packaging>
+
+    <description>ONOS simple reactive forwarding app that uses intent service</description>
+
+</project>
diff --git a/apps/ifwd/src/main/java/org/onlab/onos/ifwd/IntentReactiveForwarding.java b/apps/ifwd/src/main/java/org/onlab/onos/ifwd/IntentReactiveForwarding.java
new file mode 100644
index 0000000..fded98f
--- /dev/null
+++ b/apps/ifwd/src/main/java/org/onlab/onos/ifwd/IntentReactiveForwarding.java
@@ -0,0 +1,136 @@
+package org.onlab.onos.ifwd;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+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.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.onos.net.Host;
+import org.onlab.onos.net.HostId;
+import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.flow.DefaultTrafficSelector;
+import org.onlab.onos.net.flow.DefaultTrafficTreatment;
+import org.onlab.onos.net.flow.TrafficSelector;
+import org.onlab.onos.net.flow.TrafficTreatment;
+import org.onlab.onos.net.host.HostService;
+import org.onlab.onos.net.intent.HostToHostIntent;
+import org.onlab.onos.net.intent.IntentId;
+import org.onlab.onos.net.intent.IntentService;
+import org.onlab.onos.net.packet.DefaultOutboundPacket;
+import org.onlab.onos.net.packet.InboundPacket;
+import org.onlab.onos.net.packet.OutboundPacket;
+import org.onlab.onos.net.packet.PacketContext;
+import org.onlab.onos.net.packet.PacketProcessor;
+import org.onlab.onos.net.packet.PacketService;
+import org.onlab.onos.net.topology.TopologyService;
+import org.onlab.packet.Ethernet;
+import org.slf4j.Logger;
+
+/**
+ * WORK-IN-PROGRESS: Sample reactive forwarding application using intent framework.
+ */
+@Component(immediate = true)
+public class IntentReactiveForwarding {
+
+    private final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected TopologyService topologyService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentService intentService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    private ReactivePacketProcessor processor = new ReactivePacketProcessor();
+
+    private static long intentId = 0x123000;
+
+    @Activate
+    public void activate() {
+        packetService.addProcessor(processor, PacketProcessor.ADVISOR_MAX + 2);
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        packetService.removeProcessor(processor);
+        processor = null;
+        log.info("Stopped");
+    }
+
+    /**
+     * Packet processor responsible for forwarding packets along their paths.
+     */
+    private class ReactivePacketProcessor implements PacketProcessor {
+
+        @Override
+        public void process(PacketContext context) {
+            // Stop processing if the packet has been handled, since we
+            // can't do any more to it.
+            if (context.isHandled()) {
+                return;
+            }
+
+            InboundPacket pkt = context.inPacket();
+            Ethernet ethPkt = pkt.parsed();
+
+            HostId srcId = HostId.hostId(ethPkt.getSourceMAC());
+            HostId dstId = HostId.hostId(ethPkt.getDestinationMAC());
+
+            // Do we know who this is for? If not, flood and bail.
+            Host dst = hostService.getHost(dstId);
+            if (dst == null) {
+                flood(context);
+                return;
+            }
+
+            // Otherwise forward and be done with it.
+            setUpConnectivity(context, srcId, dstId);
+            forwardPacketToDst(context, dst);
+        }
+    }
+
+    // Floods the specified packet if permissible.
+    private void flood(PacketContext context) {
+        if (topologyService.isBroadcastPoint(topologyService.currentTopology(),
+                                             context.inPacket().receivedFrom())) {
+            packetOut(context, PortNumber.FLOOD);
+        } else {
+            context.block();
+        }
+    }
+
+    // Sends a packet out the specified port.
+    private void packetOut(PacketContext context, PortNumber portNumber) {
+        context.treatmentBuilder().setOutput(portNumber);
+        context.send();
+    }
+
+    private void forwardPacketToDst(PacketContext context, Host dst) {
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder().setOutput(dst.location().port()).build();
+        OutboundPacket packet = new DefaultOutboundPacket(dst.location().deviceId(),
+                                                          treatment, context.inPacket().unparsed());
+        packetService.emit(packet);
+        log.info("sending packet: {}", packet);
+    }
+
+    // Install a rule forwarding the packet to the specified port.
+    private void setUpConnectivity(PacketContext context, HostId srcId, HostId dstId) {
+        TrafficSelector selector = DefaultTrafficSelector.builder().build();
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();
+
+        HostToHostIntent intent =
+                new HostToHostIntent(new IntentId(intentId++), srcId, dstId,
+                                     selector, treatment);
+
+        intentService.submit(intent);
+    }
+
+}
diff --git a/apps/ifwd/src/main/java/org/onlab/onos/ifwd/package-info.java b/apps/ifwd/src/main/java/org/onlab/onos/ifwd/package-info.java
new file mode 100644
index 0000000..81c4a84
--- /dev/null
+++ b/apps/ifwd/src/main/java/org/onlab/onos/ifwd/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Trivial application that provides simple form of reactive forwarding
+ * using the intent service.
+ */
+package org.onlab.onos.ifwd;
diff --git a/apps/pom.xml b/apps/pom.xml
index 010c531..55a786c 100644
--- a/apps/pom.xml
+++ b/apps/pom.xml
@@ -19,8 +19,10 @@
     <modules>
         <module>tvue</module>
         <module>fwd</module>
+        <module>ifwd</module>
         <module>foo</module>
         <module>mobility</module>
+        <module>proxyarp</module>
 	    <module>config</module>
     </modules>
 
diff --git a/apps/proxyarp/pom.xml b/apps/proxyarp/pom.xml
new file mode 100644
index 0000000..4051d19
--- /dev/null
+++ b/apps/proxyarp/pom.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.onlab.onos</groupId>
+        <artifactId>onos-apps</artifactId>
+        <version>1.0.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>onos-app-proxyarp</artifactId>
+    <packaging>bundle</packaging>
+
+    <description>ONOS simple proxy arp module</description>
+
+</project>
diff --git a/apps/proxyarp/src/main/java/org/onlab/onos/proxyarp/ProxyArp.java b/apps/proxyarp/src/main/java/org/onlab/onos/proxyarp/ProxyArp.java
new file mode 100644
index 0000000..a06470f
--- /dev/null
+++ b/apps/proxyarp/src/main/java/org/onlab/onos/proxyarp/ProxyArp.java
@@ -0,0 +1,70 @@
+package org.onlab.onos.proxyarp;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+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.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.onos.ApplicationId;
+import org.onlab.onos.net.packet.PacketContext;
+import org.onlab.onos.net.packet.PacketProcessor;
+import org.onlab.onos.net.packet.PacketService;
+import org.onlab.onos.net.proxyarp.ProxyArpService;
+import org.slf4j.Logger;
+
+/**
+ * Sample reactive proxy arp application.
+ */
+@Component(immediate = true)
+public class ProxyArp {
+
+
+    private final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ProxyArpService proxyArpService;
+
+    private ProxyArpProcessor processor = new ProxyArpProcessor();
+
+    private ApplicationId appId;
+
+    @Activate
+    public void activate() {
+        appId = ApplicationId.getAppId();
+        packetService.addProcessor(processor, PacketProcessor.ADVISOR_MAX + 1);
+        log.info("Started with Application ID {}", appId.id());
+    }
+
+    @Deactivate
+    public void deactivate() {
+        packetService.removeProcessor(processor);
+        processor = null;
+        log.info("Stopped");
+    }
+
+
+    /**
+     * Packet processor responsible for forwarding packets along their paths.
+     */
+    private class ProxyArpProcessor implements PacketProcessor {
+
+        @Override
+        public void process(PacketContext context) {
+            // Stop processing if the packet has been handled, since we
+            // can't do any more to it.
+            if (context.isHandled()) {
+                return;
+            }
+
+            //handle the arp packet.
+            proxyArpService.handleArp(context);
+        }
+    }
+}
+
+
diff --git a/apps/proxyarp/src/main/java/org/onlab/onos/proxyarp/package-info.java b/apps/proxyarp/src/main/java/org/onlab/onos/proxyarp/package-info.java
new file mode 100644
index 0000000..3f7da98
--- /dev/null
+++ b/apps/proxyarp/src/main/java/org/onlab/onos/proxyarp/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Proxy Arp  application that handles arp resolution for you.
+ */
+package org.onlab.onos.proxyarp;
diff --git a/cli/src/main/java/org/onlab/onos/cli/SummaryCommand.java b/cli/src/main/java/org/onlab/onos/cli/SummaryCommand.java
new file mode 100644
index 0000000..e843770
--- /dev/null
+++ b/cli/src/main/java/org/onlab/onos/cli/SummaryCommand.java
@@ -0,0 +1,37 @@
+package org.onlab.onos.cli;
+
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.onos.CoreService;
+import org.onlab.onos.cluster.ClusterService;
+import org.onlab.onos.net.device.DeviceService;
+import org.onlab.onos.net.flow.FlowRuleService;
+import org.onlab.onos.net.host.HostService;
+import org.onlab.onos.net.intent.IntentService;
+import org.onlab.onos.net.link.LinkService;
+import org.onlab.onos.net.topology.Topology;
+import org.onlab.onos.net.topology.TopologyService;
+
+/**
+ * Provides summary of ONOS model.
+ */
+@Command(scope = "onos", name = "summary",
+         description = "Provides summary of ONOS model")
+public class SummaryCommand extends AbstractShellCommand {
+
+    @Override
+    protected void execute() {
+        TopologyService topologyService = get(TopologyService.class);
+        Topology topology = topologyService.currentTopology();
+        print("version=%s, nodes=%d, devices=%d, links=%d, hosts=%d, clusters=%s, paths=%d, flows=%d, intents=%d",
+              get(CoreService.class).version().toString(),
+              get(ClusterService.class).getNodes().size(),
+              get(DeviceService.class).getDeviceCount(),
+              get(LinkService.class).getLinkCount(),
+              get(HostService.class).getHostCount(),
+              topologyService.getClusters(topology).size(),
+              topology.pathCount(),
+              get(FlowRuleService.class).getFlowRuleCount(),
+              get(IntentService.class).getIntentCount());
+    }
+
+}
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/AddHostToHostIntentCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/AddHostToHostIntentCommand.java
new file mode 100644
index 0000000..837a0a7
--- /dev/null
+++ b/cli/src/main/java/org/onlab/onos/cli/net/AddHostToHostIntentCommand.java
@@ -0,0 +1,48 @@
+package org.onlab.onos.cli.net;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.onos.cli.AbstractShellCommand;
+import org.onlab.onos.net.HostId;
+import org.onlab.onos.net.flow.DefaultTrafficSelector;
+import org.onlab.onos.net.flow.DefaultTrafficTreatment;
+import org.onlab.onos.net.flow.TrafficSelector;
+import org.onlab.onos.net.flow.TrafficTreatment;
+import org.onlab.onos.net.intent.HostToHostIntent;
+import org.onlab.onos.net.intent.IntentId;
+import org.onlab.onos.net.intent.IntentService;
+
+/**
+ * Installs host-to-host connectivity intent.
+ */
+@Command(scope = "onos", name = "add-host-intent",
+         description = "Installs host-to-host connectivity intent")
+public class AddHostToHostIntentCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "one", description = "One host ID",
+              required = true, multiValued = false)
+    String one = null;
+
+    @Argument(index = 1, name = "two", description = "Another host ID",
+              required = true, multiValued = false)
+    String two = null;
+
+    private static long id = 0x7870001;
+
+    @Override
+    protected void execute() {
+        IntentService service = get(IntentService.class);
+
+        HostId oneId = HostId.hostId(one);
+        HostId twoId = HostId.hostId(two);
+
+        TrafficSelector selector = DefaultTrafficSelector.builder().build();
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();
+
+        HostToHostIntent intent =
+                new HostToHostIntent(new IntentId(id++), oneId, twoId,
+                                     selector, treatment);
+        service.submit(intent);
+    }
+
+}
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/AddPointToPointIntentCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/AddPointToPointIntentCommand.java
new file mode 100644
index 0000000..89ec7f6
--- /dev/null
+++ b/cli/src/main/java/org/onlab/onos/cli/net/AddPointToPointIntentCommand.java
@@ -0,0 +1,93 @@
+package org.onlab.onos.cli.net;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.onos.cli.AbstractShellCommand;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.flow.DefaultTrafficSelector;
+import org.onlab.onos.net.flow.DefaultTrafficTreatment;
+import org.onlab.onos.net.flow.TrafficSelector;
+import org.onlab.onos.net.flow.TrafficTreatment;
+import org.onlab.onos.net.intent.Intent;
+import org.onlab.onos.net.intent.IntentId;
+import org.onlab.onos.net.intent.IntentService;
+import org.onlab.onos.net.intent.PointToPointIntent;
+import org.onlab.packet.Ethernet;
+
+/**
+ * Installs point-to-point connectivity intents.
+ */
+@Command(scope = "onos", name = "add-point-intent",
+         description = "Installs point-to-point connectivity intent")
+public class AddPointToPointIntentCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "ingressDevice",
+              description = "Ingress Device/Port Description",
+              required = true, multiValued = false)
+    String ingressDeviceString = null;
+
+    @Argument(index = 1, name = "egressDevice",
+              description = "Egress Device/Port Description",
+              required = true, multiValued = false)
+    String egressDeviceString = null;
+
+    private static long id = 0x7470001;
+
+    @Override
+    protected void execute() {
+        IntentService service = get(IntentService.class);
+
+        DeviceId ingressDeviceId = DeviceId.deviceId(getDeviceId(ingressDeviceString));
+        PortNumber ingressPortNumber =
+                PortNumber.portNumber(getPortNumber(ingressDeviceString));
+        ConnectPoint ingress = new ConnectPoint(ingressDeviceId, ingressPortNumber);
+
+        DeviceId egressDeviceId = DeviceId.deviceId(getDeviceId(egressDeviceString));
+        PortNumber egressPortNumber =
+                PortNumber.portNumber(getPortNumber(egressDeviceString));
+        ConnectPoint egress = new ConnectPoint(egressDeviceId, egressPortNumber);
+
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .build();
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();
+
+        Intent intent =
+                new PointToPointIntent(new IntentId(id++),
+                                                 selector,
+                                                 treatment,
+                                                 ingress,
+                                                 egress);
+        service.submit(intent);
+    }
+
+    /**
+     * Extracts the port number portion of the ConnectPoint.
+     *
+     * @param deviceString string representing the device/port
+     * @return port number as a string, empty string if the port is not found
+     */
+    private String getPortNumber(String deviceString) {
+        int slash = deviceString.indexOf('/');
+        if (slash <= 0) {
+            return "";
+        }
+        return deviceString.substring(slash + 1, deviceString.length());
+    }
+
+    /**
+     * Extracts the device ID portion of the ConnectPoint.
+     *
+     * @param deviceString string representing the device/port
+     * @return device ID string
+     */
+    private String getDeviceId(String deviceString) {
+        int slash = deviceString.indexOf('/');
+        if (slash <= 0) {
+            return "";
+        }
+        return deviceString.substring(0, slash);
+    }
+}
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/ConnectPointCompleter.java b/cli/src/main/java/org/onlab/onos/cli/net/ConnectPointCompleter.java
new file mode 100644
index 0000000..29994c7
--- /dev/null
+++ b/cli/src/main/java/org/onlab/onos/cli/net/ConnectPointCompleter.java
@@ -0,0 +1,38 @@
+package org.onlab.onos.cli.net;
+
+import java.util.List;
+import java.util.SortedSet;
+
+import org.apache.karaf.shell.console.Completer;
+import org.apache.karaf.shell.console.completer.StringsCompleter;
+import org.onlab.onos.cli.AbstractShellCommand;
+import org.onlab.onos.net.Device;
+import org.onlab.onos.net.Port;
+import org.onlab.onos.net.device.DeviceService;
+
+/**
+ * ConnectPoint completer.
+ */
+public class ConnectPointCompleter implements Completer {
+    @Override
+    public int complete(String buffer, int cursor, List<String> candidates) {
+        // Delegate string completer
+        StringsCompleter delegate = new StringsCompleter();
+
+        // Fetch our service and feed it's offerings to the string completer
+        DeviceService service = AbstractShellCommand.get(DeviceService.class);
+
+        // Generate the device ID/port number identifiers
+        for (Device device : service.getDevices()) {
+            SortedSet<String> strings = delegate.getStrings();
+
+            for (Port port : service.getPorts(device.id())) {
+                strings.add(device.id().toString() + "/" + port.number());
+            }
+        }
+
+        // Now let the completer do the work for figuring out what to offer.
+        return delegate.complete(buffer, cursor, candidates);
+    }
+
+}
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/DevicesListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/DevicesListCommand.java
index 6f0dd30..f34f97e 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/DevicesListCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/DevicesListCommand.java
@@ -35,7 +35,7 @@
      * @param service device service
      * @return sorted device list
      */
-    protected List<Device> getSortedDevices(DeviceService service) {
+    protected static List<Device> getSortedDevices(DeviceService service) {
         List<Device> devices = newArrayList(service.getDevices());
         Collections.sort(devices, Comparators.ELEMENT_COMPARATOR);
         return devices;
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/FlowRuleStatusCompleter.java b/cli/src/main/java/org/onlab/onos/cli/net/FlowRuleStatusCompleter.java
index 134d995..d8c795a 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/FlowRuleStatusCompleter.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/FlowRuleStatusCompleter.java
@@ -5,7 +5,7 @@
 
 import org.apache.karaf.shell.console.Completer;
 import org.apache.karaf.shell.console.completer.StringsCompleter;
-import org.onlab.onos.net.flow.FlowRule.FlowRuleState;
+import org.onlab.onos.net.flow.FlowEntry.FlowEntryState;
 
 /**
  * Device ID completer.
@@ -16,7 +16,7 @@
         // Delegate string completer
         StringsCompleter delegate = new StringsCompleter();
 
-        FlowRuleState[] states = FlowRuleState.values();
+        FlowEntryState[] states = FlowEntryState.values();
         SortedSet<String> strings = delegate.getStrings();
         for (int i = 0; i < states.length; i++) {
             strings.add(states[i].toString().toLowerCase());
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/FlowsListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/FlowsListCommand.java
index 41f30a7..902b27b 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/FlowsListCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/FlowsListCommand.java
@@ -1,6 +1,7 @@
 package org.onlab.onos.cli.net;
 
 import static com.google.common.collect.Lists.newArrayList;
+import static org.onlab.onos.cli.net.DevicesListCommand.getSortedDevices;
 
 import java.util.Collections;
 import java.util.List;
@@ -13,8 +14,8 @@
 import org.onlab.onos.net.Device;
 import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.net.device.DeviceService;
-import org.onlab.onos.net.flow.FlowRule;
-import org.onlab.onos.net.flow.FlowRule.FlowRuleState;
+import org.onlab.onos.net.flow.FlowEntry;
+import org.onlab.onos.net.flow.FlowEntry.FlowEntryState;
 import org.onlab.onos.net.flow.FlowRuleService;
 
 import com.google.common.collect.Maps;
@@ -45,8 +46,8 @@
     protected void execute() {
         DeviceService deviceService = get(DeviceService.class);
         FlowRuleService service = get(FlowRuleService.class);
-        Map<Device, List<FlowRule>> flows = getSortedFlows(deviceService, service);
-        for (Device d : flows.keySet()) {
+        Map<Device, List<FlowEntry>> flows = getSortedFlows(deviceService, service);
+        for (Device d : getSortedDevices(deviceService)) {
             printFlows(d, flows.get(d));
         }
     }
@@ -57,21 +58,22 @@
      * @param service device service
      * @return sorted device list
      */
-    protected Map<Device, List<FlowRule>> getSortedFlows(DeviceService deviceService, FlowRuleService service) {
-        Map<Device, List<FlowRule>> flows = Maps.newHashMap();
-        List<FlowRule> rules;
-        FlowRuleState s = null;
+    protected Map<Device, List<FlowEntry>> getSortedFlows(DeviceService deviceService,
+                                                          FlowRuleService service) {
+        Map<Device, List<FlowEntry>> flows = Maps.newHashMap();
+        List<FlowEntry> rules;
+        FlowEntryState s = null;
         if (state != null && !state.equals("any")) {
-            s = FlowRuleState.valueOf(state.toUpperCase());
+            s = FlowEntryState.valueOf(state.toUpperCase());
         }
-        Iterable<Device> devices = uri == null ?  deviceService.getDevices() :
+        Iterable<Device> devices = uri == null ? deviceService.getDevices() :
             Collections.singletonList(deviceService.getDevice(DeviceId.deviceId(uri)));
         for (Device d : devices) {
             if (s == null) {
                 rules = newArrayList(service.getFlowEntries(d.id()));
             } else {
                 rules = newArrayList();
-                for (FlowRule f : service.getFlowEntries(d.id())) {
+                for (FlowEntry f : service.getFlowEntries(d.id())) {
                     if (f.state().equals(s)) {
                         rules.add(f);
                     }
@@ -88,19 +90,17 @@
      * @param d the device
      * @param flows the set of flows for that device.
      */
-    protected void printFlows(Device d, List<FlowRule> flows) {
-        print("Device: " + d.id());
-        if (flows == null | flows.isEmpty()) {
-            print(" %s", "No flows.");
-            return;
+    protected void printFlows(Device d, List<FlowEntry> flows) {
+        boolean empty = flows == null || flows.isEmpty();
+        print("deviceId=%s, flowRuleCount=%d", d.id(), empty ? 0 : flows.size());
+        if (!empty) {
+            for (FlowEntry f : flows) {
+                print(FMT, Long.toHexString(f.id().value()), f.state(), f.bytes(),
+                      f.packets(), f.life(), f.priority());
+                print(SFMT, f.selector().criteria());
+                print(TFMT, f.treatment().instructions());
+            }
         }
-        for (FlowRule f : flows) {
-            print(FMT, Long.toHexString(f.id().value()), f.state(), f.bytes(),
-                    f.packets(), f.lifeMillis(), f.priority());
-            print(SFMT, f.selector().criteria());
-            print(TFMT, f.treatment().instructions());
-        }
-
     }
 
 }
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/IntentIdCompleter.java b/cli/src/main/java/org/onlab/onos/cli/net/IntentIdCompleter.java
new file mode 100644
index 0000000..5d2e952
--- /dev/null
+++ b/cli/src/main/java/org/onlab/onos/cli/net/IntentIdCompleter.java
@@ -0,0 +1,34 @@
+package org.onlab.onos.cli.net;
+
+import org.apache.karaf.shell.console.Completer;
+import org.apache.karaf.shell.console.completer.StringsCompleter;
+import org.onlab.onos.cli.AbstractShellCommand;
+import org.onlab.onos.net.intent.Intent;
+import org.onlab.onos.net.intent.IntentService;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.SortedSet;
+
+/**
+ * Intent ID completer.
+ */
+public class IntentIdCompleter implements Completer {
+    @Override
+    public int complete(String buffer, int cursor, List<String> candidates) {
+        // Delegate string completer
+        StringsCompleter delegate = new StringsCompleter();
+
+        // Fetch our service and feed it's offerings to the string completer
+        IntentService service = AbstractShellCommand.get(IntentService.class);
+        Iterator<Intent> it = service.getIntents().iterator();
+        SortedSet<String> strings = delegate.getStrings();
+        while (it.hasNext()) {
+            strings.add(it.next().id().toString());
+        }
+
+        // Now let the completer do the work for figuring out what to offer.
+        return delegate.complete(buffer, cursor, candidates);
+    }
+
+}
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/IntentRemoveCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/IntentRemoveCommand.java
new file mode 100644
index 0000000..7684da4
--- /dev/null
+++ b/cli/src/main/java/org/onlab/onos/cli/net/IntentRemoveCommand.java
@@ -0,0 +1,37 @@
+package org.onlab.onos.cli.net;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.onos.cli.AbstractShellCommand;
+import org.onlab.onos.net.intent.Intent;
+import org.onlab.onos.net.intent.IntentId;
+import org.onlab.onos.net.intent.IntentService;
+
+/**
+ * Removes host-to-host connectivity intent.
+ */
+@Command(scope = "onos", name = "remove-intent",
+         description = "Removes the specified intent")
+public class IntentRemoveCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "id", description = "Intent ID",
+              required = true, multiValued = false)
+    String id = null;
+
+    @Override
+    protected void execute() {
+        IntentService service = get(IntentService.class);
+
+        int radix = id.startsWith("0x") ? 16 : 10;
+        if (radix == 16) {
+            id = id.replaceFirst("0x", "");
+        }
+        IntentId intentId = new IntentId(Long.parseLong(id, radix));
+
+
+        Intent intent = service.getIntent(intentId);
+        if (intent != null) {
+            service.withdraw(intent);
+        }
+    }
+}
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/IntentsListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/IntentsListCommand.java
new file mode 100644
index 0000000..a0c9845
--- /dev/null
+++ b/cli/src/main/java/org/onlab/onos/cli/net/IntentsListCommand.java
@@ -0,0 +1,25 @@
+package org.onlab.onos.cli.net;
+
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.onos.cli.AbstractShellCommand;
+import org.onlab.onos.net.intent.Intent;
+import org.onlab.onos.net.intent.IntentService;
+import org.onlab.onos.net.intent.IntentState;
+
+/**
+ * Lists the inventory of intents and their states.
+ */
+@Command(scope = "onos", name = "intents",
+         description = "Lists the inventory of intents and their states")
+public class IntentsListCommand extends AbstractShellCommand {
+
+    @Override
+    protected void execute() {
+        IntentService service = get(IntentService.class);
+        for (Intent intent : service.getIntents()) {
+            IntentState state = service.getIntentState(intent.id());
+            print("%s %s %s", intent.id(), state, intent);
+        }
+    }
+
+}
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/WipeOutCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/WipeOutCommand.java
index d0e6a70..fe18ba0 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/WipeOutCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/WipeOutCommand.java
@@ -1,5 +1,6 @@
 package org.onlab.onos.cli.net;
 
+import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
 import org.onlab.onos.net.Device;
 import org.onlab.onos.net.Host;
@@ -7,28 +8,51 @@
 import org.onlab.onos.net.device.DeviceService;
 import org.onlab.onos.net.host.HostAdminService;
 import org.onlab.onos.net.host.HostService;
+import org.onlab.onos.net.intent.Intent;
+import org.onlab.onos.net.intent.IntentService;
+import org.onlab.onos.net.intent.IntentState;
 
 /**
- * Wipes-out the entire network information base, i.e. devices, links, hosts.
+ * Wipes-out the entire network information base, i.e. devices, links, hosts, intents.
  */
 @Command(scope = "onos", name = "wipe-out",
          description = "Wipes-out the entire network information base, i.e. devices, links, hosts")
 public class WipeOutCommand extends ClustersListCommand {
 
+    private static final String DISCLAIMER = "Delete everything please.";
+
+    @Argument(index = 0, name = "disclaimer", description = "Device ID",
+              required = false, multiValued = false)
+    String disclaimer = null;
+
     @Override
     protected void execute() {
+        if (disclaimer == null || !disclaimer.equals(DISCLAIMER)) {
+            print("I'm afraid I can't do that!\nPlease acknowledge with phrase: '%s'",
+                  DISCLAIMER);
+            return;
+        }
+
+        print("Wiping devices");
         DeviceAdminService deviceAdminService = get(DeviceAdminService.class);
         DeviceService deviceService = get(DeviceService.class);
         for (Device device : deviceService.getDevices()) {
             deviceAdminService.removeDevice(device.id());
         }
 
+        print("Wiping hosts");
         HostAdminService hostAdminService = get(HostAdminService.class);
         HostService hostService = get(HostService.class);
         for (Host host : hostService.getHosts()) {
             hostAdminService.removeHost(host.id());
         }
+
+        print("Wiping intents");
+        IntentService intentService = get(IntentService.class);
+        for (Intent intent : intentService.getIntents()) {
+            if (intentService.getIntentState(intent.id()) == IntentState.INSTALLED) {
+                intentService.withdraw(intent);
+            }
+        }
     }
-
-
 }
diff --git a/cli/src/main/javadoc/doc-files/intent-states.png b/cli/src/main/javadoc/doc-files/intent-states.png
new file mode 100644
index 0000000..70c333b
--- /dev/null
+++ b/cli/src/main/javadoc/doc-files/intent-states.png
Binary files differ
diff --git a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index 16b5672..6120d30 100644
--- a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -2,6 +2,9 @@
 
     <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
         <command>
+            <action class="org.onlab.onos.cli.SummaryCommand"/>
+        </command>
+        <command>
             <action class="org.onlab.onos.cli.NodesListCommand"/>
         </command>
         <command>
@@ -56,6 +59,30 @@
                 <ref component-id="deviceIdCompleter"/>
             </completers>
         </command>
+
+        <command>
+            <action class="org.onlab.onos.cli.net.IntentsListCommand"/>
+        </command>
+        <command>
+            <action class="org.onlab.onos.cli.net.IntentRemoveCommand"/>
+            <completers>
+                <ref component-id="intentIdCompleter"/>
+            </completers>
+        </command>
+        <command>
+            <action class="org.onlab.onos.cli.net.AddHostToHostIntentCommand"/>
+            <completers>
+                <ref component-id="hostIdCompleter"/>
+            </completers>
+        </command>
+        <command>
+            <action class="org.onlab.onos.cli.net.AddPointToPointIntentCommand"/>
+            <completers>
+                <ref component-id="connectPointCompleter"/>
+                <ref component-id="connectPointCompleter"/>
+            </completers>
+        </command>
+
         <command>
             <action class="org.onlab.onos.cli.net.ClustersListCommand"/>
         </command>
@@ -94,6 +121,8 @@
     <bean id="clusterIdCompleter" class="org.onlab.onos.cli.net.ClusterIdCompleter"/>
     <bean id="roleCompleter" class="org.onlab.onos.cli.net.RoleCompleter"/>
     <bean id="hostIdCompleter" class="org.onlab.onos.cli.net.HostIdCompleter"/>
+    <bean id="intentIdCompleter" class="org.onlab.onos.cli.net.IntentIdCompleter"/>
     <bean id="flowRuleStatusCompleter" class="org.onlab.onos.cli.net.FlowRuleStatusCompleter"/>
+    <bean id="connectPointCompleter" class="org.onlab.onos.cli.net.ConnectPointCompleter"/>
 
 </blueprint>
diff --git a/core/api/src/main/java/org/onlab/onos/ApplicationId.java b/core/api/src/main/java/org/onlab/onos/ApplicationId.java
index f345607..433265e 100644
--- a/core/api/src/main/java/org/onlab/onos/ApplicationId.java
+++ b/core/api/src/main/java/org/onlab/onos/ApplicationId.java
@@ -8,7 +8,7 @@
  */
 public final class ApplicationId {
 
-    private static AtomicInteger idDispenser;
+    private static final AtomicInteger ID_DISPENCER = new AtomicInteger(1);
     private final Integer id;
 
     // Ban public construction
@@ -50,10 +50,7 @@
      * @return app id
      */
     public static ApplicationId getAppId() {
-        if (ApplicationId.idDispenser == null) {
-            ApplicationId.idDispenser = new AtomicInteger(1);
-        }
-        return new ApplicationId(ApplicationId.idDispenser.getAndIncrement());
+        return new ApplicationId(ApplicationId.ID_DISPENCER.getAndIncrement());
     }
 
 }
diff --git a/core/api/src/main/java/org/onlab/onos/CoreService.java b/core/api/src/main/java/org/onlab/onos/CoreService.java
new file mode 100644
index 0000000..32c36c5
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/CoreService.java
@@ -0,0 +1,15 @@
+package org.onlab.onos;
+
+/**
+ * Service for interacting with the core system of the controller.
+ */
+public interface CoreService {
+
+    /**
+     * Returns the product version.
+     *
+     * @return product version
+     */
+    Version version();
+
+}
diff --git a/core/api/src/main/java/org/onlab/onos/Version.java b/core/api/src/main/java/org/onlab/onos/Version.java
new file mode 100644
index 0000000..5d071b7
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/Version.java
@@ -0,0 +1,113 @@
+package org.onlab.onos;
+
+import java.util.Objects;
+
+import static java.lang.Integer.parseInt;
+
+/**
+ * Representation of the product version.
+ */
+public final class Version {
+
+    public static final String FORMAT = "%d.%d.%d.%s";
+
+    private final int major;
+    private final int minor;
+    private final int patch;
+    private final String build;
+
+    private final String format;
+
+    // Creates a new version descriptor
+    private Version(int major, int minor, int patch, String build) {
+        this.major = major;
+        this.minor = minor;
+        this.patch = patch;
+        this.build = build;
+        this.format = String.format(FORMAT, major, minor, patch, build);
+    }
+
+
+    /**
+     * Creates a new version from the specified constituent numbers.
+     *
+     * @param major major version number
+     * @param minor minod version number
+     * @param patch version patch number
+     * @param build build string
+     * @return version descriptor
+     */
+    public static Version version(int major, int minor, int patch, String build) {
+        return new Version(major, minor, patch, build);
+    }
+
+    /**
+     * Creates a new version by parsing the specified string.
+     *
+     * @param string version string
+     * @return version descriptor
+     */
+    public static Version version(String string) {
+        String[] fields = string.split("[.-]");
+        return new Version(parseInt(fields[0]), parseInt(fields[1]),
+                           parseInt(fields[2]), fields[3]);
+    }
+
+    /**
+     * Returns the major version number.
+     *
+     * @return major version number
+     */
+    public int major() {
+        return major;
+    }
+
+    /**
+     * Returns the minor version number.
+     *
+     * @return minor version number
+     */
+    public int minor() {
+        return minor;
+    }
+
+    /**
+     * Returns the version patch number.
+     *
+     * @return patch number
+     */
+    public int patch() {
+        return patch;
+    }
+
+    /**
+     * Returns the version build string.
+     *
+     * @return build string
+     */
+    public String build() {
+        return build;
+    }
+
+    @Override
+    public String toString() {
+        return format;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(format);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof Version) {
+            final Version other = (Version) obj;
+            return Objects.equals(this.format, other.format);
+        }
+        return false;
+    }
+}
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 ffee162..51b6f6a 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
@@ -17,6 +17,7 @@
      * Returns the role of the local node for the specified device, without
      * triggering master selection.
      *
+     * @param deviceId the the identifier of the device
      * @return role of the current node
      */
     MastershipRole getLocalRole(DeviceId deviceId);
diff --git a/core/api/src/main/java/org/onlab/onos/net/AnnotationsUtil.java b/core/api/src/main/java/org/onlab/onos/net/AnnotationsUtil.java
new file mode 100644
index 0000000..3b71d95
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/AnnotationsUtil.java
@@ -0,0 +1,27 @@
+package org.onlab.onos.net;
+
+public final class AnnotationsUtil {
+
+    public static boolean isEqual(Annotations lhs, Annotations rhs) {
+        if (lhs == rhs) {
+            return true;
+        }
+        if (lhs == null || rhs == null) {
+            return false;
+        }
+
+        if (!lhs.keys().equals(rhs.keys())) {
+            return false;
+        }
+
+        for (String key : lhs.keys()) {
+            if (!lhs.value(key).equals(rhs.value(key))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    // not to be instantiated
+    private AnnotationsUtil() {}
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/ConnectPoint.java b/core/api/src/main/java/org/onlab/onos/net/ConnectPoint.java
index 35adde9..17f9348 100644
--- a/core/api/src/main/java/org/onlab/onos/net/ConnectPoint.java
+++ b/core/api/src/main/java/org/onlab/onos/net/ConnectPoint.java
@@ -47,7 +47,23 @@
             return (DeviceId) elementId;
         }
         throw new IllegalStateException("Connection point not associated " +
-                "with an infrastructure device");
+                                                "with an infrastructure device");
+    }
+
+    /**
+     * Returns the identifier of the infrastructure device if the connection
+     * point belongs to a network element which is indeed an end-station host.
+     *
+     * @return network element identifier as a host identifier
+     * @throws java.lang.IllegalStateException if connection point is not
+     *                                         associated with a host
+     */
+    public HostId hostId() {
+        if (elementId instanceof HostId) {
+            return (HostId) elementId;
+        }
+        throw new IllegalStateException("Connection point not associated " +
+                                                "with an end-station host");
     }
 
     /**
diff --git a/core/api/src/main/java/org/onlab/onos/net/DefaultAnnotations.java b/core/api/src/main/java/org/onlab/onos/net/DefaultAnnotations.java
index 0c0f375..b2e7e61 100644
--- a/core/api/src/main/java/org/onlab/onos/net/DefaultAnnotations.java
+++ b/core/api/src/main/java/org/onlab/onos/net/DefaultAnnotations.java
@@ -73,31 +73,63 @@
     }
 
     /**
-     * Convert Annotations to DefaultAnnotations if needed and merges.
+     * Creates the union of two given SparseAnnotations.
+     * Unlike the {@link #merge(DefaultAnnotations, SparseAnnotations)} method,
+     * result will be {@link SparseAnnotations} instead of {@link Annotations}.
      *
-     * @see #merge(DefaultAnnotations, SparseAnnotations)
+     * A key tagged for removal will remain in the output SparseAnnotations,
+     * if the counterpart of the input does not contain the same key.
      *
      * @param annotations       base annotations
      * @param sparseAnnotations additional sparse annotations
      * @return combined annotations or the original base annotations if there
      * are not additional annotations
      */
-    public static DefaultAnnotations merge(Annotations annotations,
-                                    SparseAnnotations sparseAnnotations) {
-        if (annotations instanceof DefaultAnnotations) {
-            return merge((DefaultAnnotations) annotations, sparseAnnotations);
+    public static SparseAnnotations union(SparseAnnotations annotations,
+                                          SparseAnnotations sparseAnnotations) {
+
+        if (sparseAnnotations == null || sparseAnnotations.keys().isEmpty()) {
+            return annotations;
         }
 
-        DefaultAnnotations.Builder builder = DefaultAnnotations.builder();
-        for (String key : annotations.keys()) {
-            builder.set(key, annotations.value(key));
+        final HashMap<String, String> newMap;
+        if (annotations instanceof DefaultAnnotations) {
+            newMap = copy(((DefaultAnnotations) annotations).map);
+        } else {
+            newMap = new HashMap<>(annotations.keys().size() +
+                                   sparseAnnotations.keys().size());
+            putAllSparseAnnotations(newMap, annotations);
         }
-        return merge(builder.build(), sparseAnnotations);
+
+        putAllSparseAnnotations(newMap, sparseAnnotations);
+        return new DefaultAnnotations(newMap);
+    }
+
+    // adds the key-values contained in sparseAnnotations to
+    // newMap, if sparseAnnotations had a key tagged for removal,
+    // and corresponding key exist in newMap, entry will be removed.
+    // if corresponding key does not exist, removal tag will be added to
+    // the newMap.
+    private static void putAllSparseAnnotations(
+                            final HashMap<String, String> newMap,
+                            SparseAnnotations sparseAnnotations) {
+
+        for (String key : sparseAnnotations.keys()) {
+            if (sparseAnnotations.isRemoved(key)) {
+                if (newMap.containsKey(key)) {
+                    newMap.remove(key);
+                } else {
+                    newMap.put(key, Builder.REMOVED);
+                }
+            } else {
+                String value = sparseAnnotations.value(key);
+                newMap.put(key, value);
+            }
+        }
     }
 
     @Override
     public Set<String> keys() {
-        // TODO: unmodifiable to be removed after switching to ImmutableMap;
         return Collections.unmodifiableSet(map.keySet());
     }
 
@@ -115,7 +147,7 @@
     @SuppressWarnings("unchecked")
     private static HashMap<String, String> copy(Map<String, String> original) {
         if (original instanceof HashMap) {
-            return (HashMap) ((HashMap) original).clone();
+            return (HashMap<String, String>) ((HashMap<?, ?>) original).clone();
         }
         throw new IllegalArgumentException("Expecting HashMap instance");
     }
diff --git a/core/api/src/main/java/org/onlab/onos/net/DefaultEdgeLink.java b/core/api/src/main/java/org/onlab/onos/net/DefaultEdgeLink.java
index 6074c67..46a582a 100644
--- a/core/api/src/main/java/org/onlab/onos/net/DefaultEdgeLink.java
+++ b/core/api/src/main/java/org/onlab/onos/net/DefaultEdgeLink.java
@@ -3,6 +3,7 @@
 import org.onlab.onos.net.provider.ProviderId;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
  * Default edge link model implementation.
@@ -18,9 +19,9 @@
      * @param providerId   provider identity
      * @param hostPoint    host-side connection point
      * @param hostLocation location where host attaches to the network
-     * @param isIngress    true to indicated host-to-network direction; false
+     * @param isIngress    true to indicate host-to-network direction; false
      *                     for network-to-host direction
-     * @param annotations optional key/value annotations
+     * @param annotations  optional key/value annotations
      */
     public DefaultEdgeLink(ProviderId providerId, ConnectPoint hostPoint,
                            HostLocation hostLocation, boolean isIngress,
@@ -42,4 +43,24 @@
     public HostLocation hostLocation() {
         return hostLocation;
     }
+
+    /**
+     * Creates a phantom edge link, to an unspecified end-station. This link
+     * does not represent any actually discovered link stored in the system.
+     *
+     * @param edgePort  network edge port
+     * @param isIngress true to indicate host-to-network direction; false
+     *                  for network-to-host direction
+     * @return new phantom edge link
+     */
+    public static DefaultEdgeLink createEdgeLink(ConnectPoint edgePort,
+                                                 boolean isIngress) {
+        checkNotNull(edgePort, "Edge port cannot be null");
+        HostLocation location = (edgePort instanceof HostLocation) ?
+                (HostLocation) edgePort : new HostLocation(edgePort, 0);
+        return new DefaultEdgeLink(ProviderId.NONE,
+                                   new ConnectPoint(HostId.NONE, PortNumber.P0),
+                                   location, isIngress);
+    }
+
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/HostId.java b/core/api/src/main/java/org/onlab/onos/net/HostId.java
index 1768f24..f2c0303 100644
--- a/core/api/src/main/java/org/onlab/onos/net/HostId.java
+++ b/core/api/src/main/java/org/onlab/onos/net/HostId.java
@@ -10,6 +10,14 @@
  */
 public final class HostId extends ElementId {
 
+    private static final String NIC = "nic";
+
+    /**
+     * Represents either no host, or an unspecified host; used for creating
+     * open ingress/egress edge links.
+     */
+    public static final HostId NONE = hostId(NIC + ":none-0");
+
     // Public construction is prohibited
     private HostId(URI uri) {
         super(uri);
@@ -43,8 +51,7 @@
      * @return host identifier
      */
     public static HostId hostId(MacAddress mac, VlanId vlanId) {
-        // FIXME: use more efficient means of encoding
-        return hostId("nic" + ":" + mac + "-" + vlanId);
+        return hostId(NIC + ":" + mac + "-" + vlanId);
     }
 
     /**
diff --git a/core/api/src/main/java/org/onlab/onos/net/HostLocation.java b/core/api/src/main/java/org/onlab/onos/net/HostLocation.java
index 76e2312..60c5945 100644
--- a/core/api/src/main/java/org/onlab/onos/net/HostLocation.java
+++ b/core/api/src/main/java/org/onlab/onos/net/HostLocation.java
@@ -22,6 +22,17 @@
     }
 
     /**
+     * Creates a new host location derived from the supplied connection point.
+     *
+     * @param connectPoint connection point
+     * @param time         time when detected, in millis since start of epoch
+     */
+    public HostLocation(ConnectPoint connectPoint, long time) {
+        super(connectPoint.deviceId(), connectPoint.port());
+        this.time = time;
+    }
+
+    /**
      * Returns the time when the location was established, given in
      * milliseconds since start of epoch.
      *
diff --git a/core/api/src/main/java/org/onlab/onos/net/LinkKey.java b/core/api/src/main/java/org/onlab/onos/net/LinkKey.java
index dee4e88..d3ff0f4 100644
--- a/core/api/src/main/java/org/onlab/onos/net/LinkKey.java
+++ b/core/api/src/main/java/org/onlab/onos/net/LinkKey.java
@@ -6,6 +6,7 @@
 
 // TODO Consider renaming.
 // it's an identifier for a Link, but it's not ElementId, so not using LinkId.
+
 /**
  * Immutable representation of a link identity.
  */
@@ -43,6 +44,15 @@
         this.dst = dst;
     }
 
+    /**
+     * Creates a link identifier for the specified link.
+     *
+     * @param link link descriptor
+     */
+    public LinkKey(Link link) {
+        this(link.src(), link.dst());
+    }
+
     @Override
     public int hashCode() {
         return Objects.hash(src(), dst);
diff --git a/core/api/src/main/java/org/onlab/onos/net/PortNumber.java b/core/api/src/main/java/org/onlab/onos/net/PortNumber.java
index cfb11d5..60c3305 100644
--- a/core/api/src/main/java/org/onlab/onos/net/PortNumber.java
+++ b/core/api/src/main/java/org/onlab/onos/net/PortNumber.java
@@ -9,6 +9,8 @@
  */
 public final class PortNumber {
 
+    public static final PortNumber P0 = portNumber(0);
+
     // TODO: revisit the max and the logical port value assignments
 
     private static final long MAX_NUMBER = (2L * Integer.MAX_VALUE) + 1;
diff --git a/core/api/src/main/java/org/onlab/onos/net/device/DefaultDeviceDescription.java b/core/api/src/main/java/org/onlab/onos/net/device/DefaultDeviceDescription.java
index 788d23a..ede2eb2 100644
--- a/core/api/src/main/java/org/onlab/onos/net/device/DefaultDeviceDescription.java
+++ b/core/api/src/main/java/org/onlab/onos/net/device/DefaultDeviceDescription.java
@@ -96,4 +96,13 @@
                 .toString();
     }
 
+    // default constructor for serialization
+    private DefaultDeviceDescription() {
+        this.uri = null;
+        this.type = null;
+        this.manufacturer = null;
+        this.hwVersion = null;
+        this.swVersion = null;
+        this.serialNumber = null;
+    }
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/device/DefaultPortDescription.java b/core/api/src/main/java/org/onlab/onos/net/device/DefaultPortDescription.java
index eb75ede..e1dcf9e 100644
--- a/core/api/src/main/java/org/onlab/onos/net/device/DefaultPortDescription.java
+++ b/core/api/src/main/java/org/onlab/onos/net/device/DefaultPortDescription.java
@@ -48,4 +48,9 @@
         return isEnabled;
     }
 
+    // default constructor for serialization
+    private DefaultPortDescription() {
+        this.number = null;
+        this.isEnabled = false;
+    }
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/CompletedBatchOperation.java b/core/api/src/main/java/org/onlab/onos/net/flow/CompletedBatchOperation.java
new file mode 100644
index 0000000..bde752e
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/CompletedBatchOperation.java
@@ -0,0 +1,6 @@
+package org.onlab.onos.net.flow;
+
+public class CompletedBatchOperation {
+
+
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowEntry.java b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowEntry.java
new file mode 100644
index 0000000..5a0f55b
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowEntry.java
@@ -0,0 +1,111 @@
+package org.onlab.onos.net.flow;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import org.onlab.onos.net.DeviceId;
+import org.slf4j.Logger;
+
+public class DefaultFlowEntry extends DefaultFlowRule implements FlowEntry {
+
+    private final Logger log = getLogger(getClass());
+
+    private long life;
+    private long packets;
+    private long bytes;
+    private FlowEntryState state;
+
+    private long lastSeen = -1;
+
+
+    public DefaultFlowEntry(DeviceId deviceId, TrafficSelector selector,
+            TrafficTreatment treatment, int priority, FlowEntryState state,
+            long life, long packets, long bytes, long flowId,
+            int timeout) {
+        super(deviceId, selector, treatment, priority, flowId, timeout);
+        this.state = state;
+        this.life = life;
+        this.packets = packets;
+        this.bytes = bytes;
+        this.lastSeen = System.currentTimeMillis();
+    }
+
+    public DefaultFlowEntry(FlowRule rule, FlowEntryState state,
+            long life, long packets, long bytes) {
+        super(rule);
+        this.state = state;
+        this.life = life;
+        this.packets = packets;
+        this.bytes = bytes;
+        this.lastSeen = System.currentTimeMillis();
+    }
+
+    public DefaultFlowEntry(FlowRule rule) {
+        super(rule);
+        this.state = FlowEntryState.PENDING_ADD;
+        this.life = 0;
+        this.packets = 0;
+        this.bytes = 0;
+        this.lastSeen = System.currentTimeMillis();
+    }
+
+    @Override
+    public long life() {
+        return life;
+    }
+
+    @Override
+    public long packets() {
+        return packets;
+    }
+
+    @Override
+    public long bytes() {
+        return bytes;
+    }
+
+    @Override
+    public FlowEntryState state() {
+        return this.state;
+    }
+
+    @Override
+    public long lastSeen() {
+        return lastSeen;
+    }
+
+    @Override
+    public void setLastSeen() {
+        this.lastSeen = System.currentTimeMillis();
+    }
+
+    @Override
+    public void setState(FlowEntryState newState) {
+        this.state = newState;
+    }
+
+    @Override
+    public void setLife(long life) {
+        this.life = life;
+    }
+
+    @Override
+    public void setPackets(long packets) {
+        this.packets = packets;
+    }
+
+    @Override
+    public void setBytes(long bytes) {
+        this.bytes = bytes;
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("rule", super.toString())
+                .add("state", state)
+                .toString();
+    }
+
+
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowRule.java b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowRule.java
index bb4805b..47e9fed 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowRule.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowRule.java
@@ -18,10 +18,6 @@
     private final TrafficSelector selector;
     private final TrafficTreatment treatment;
     private final long created;
-    private final long life;
-    private final long packets;
-    private final long bytes;
-    private final FlowRuleState state;
 
     private final FlowId id;
 
@@ -29,73 +25,50 @@
 
     private final int timeout;
 
+
     public DefaultFlowRule(DeviceId deviceId, TrafficSelector selector,
-            TrafficTreatment treatment, int priority, FlowRuleState state,
-            long life, long packets, long bytes, long flowId, boolean expired,
+            TrafficTreatment treatment, int priority, long flowId,
             int timeout) {
         this.deviceId = deviceId;
         this.priority = priority;
         this.selector = selector;
         this.treatment = treatment;
-        this.state = state;
+        this.timeout = timeout;
+        this.created = System.currentTimeMillis();
+
         this.appId = ApplicationId.valueOf((int) (flowId >> 32));
         this.id = FlowId.valueOf(flowId);
-        this.life = life;
-        this.packets = packets;
-        this.bytes = bytes;
-        this.created = System.currentTimeMillis();
-        this.timeout = timeout;
     }
 
     public DefaultFlowRule(DeviceId deviceId, TrafficSelector selector,
             TrafficTreatment treatement, int priority, ApplicationId appId,
             int timeout) {
-        this(deviceId, selector, treatement, priority,
-                FlowRuleState.CREATED, appId, timeout);
-    }
 
-    public DefaultFlowRule(FlowRule rule, FlowRuleState state) {
-        this(rule.deviceId(), rule.selector(), rule.treatment(),
-                rule.priority(), state, rule.id(), rule.appId(),
-                rule.timeout());
-    }
+        if (priority < FlowRule.MIN_PRIORITY) {
+            throw new IllegalArgumentException("Priority cannot be less than " + MIN_PRIORITY);
+        }
 
-    private DefaultFlowRule(DeviceId deviceId,
-            TrafficSelector selector, TrafficTreatment treatment,
-            int priority, FlowRuleState state, ApplicationId appId,
-            int timeout) {
         this.deviceId = deviceId;
         this.priority = priority;
         this.selector = selector;
-        this.treatment = treatment;
-        this.state = state;
-        this.life = 0;
-        this.packets = 0;
-        this.bytes = 0;
+        this.treatment = treatement;
         this.appId = appId;
-
         this.timeout = timeout;
+        this.created = System.currentTimeMillis();
 
         this.id = FlowId.valueOf((((long) appId().id()) << 32) | (this.hash() & 0xffffffffL));
-        this.created = System.currentTimeMillis();
     }
 
-    private DefaultFlowRule(DeviceId deviceId,
-            TrafficSelector selector, TrafficTreatment treatment,
-            int priority, FlowRuleState state, FlowId flowId, ApplicationId appId,
-            int timeout) {
-        this.deviceId = deviceId;
-        this.priority = priority;
-        this.selector = selector;
-        this.treatment = treatment;
-        this.state = state;
-        this.life = 0;
-        this.packets = 0;
-        this.bytes = 0;
-        this.appId = appId;
-        this.id = flowId;
-        this.timeout = timeout;
+    public DefaultFlowRule(FlowRule rule) {
+        this.deviceId = rule.deviceId();
+        this.priority = rule.priority();
+        this.selector = rule.selector();
+        this.treatment = rule.treatment();
+        this.appId = rule.appId();
+        this.id = rule.id();
+        this.timeout = rule.timeout();
         this.created = System.currentTimeMillis();
+
     }
 
 
@@ -129,26 +102,6 @@
         return treatment;
     }
 
-    @Override
-    public long lifeMillis() {
-        return life;
-    }
-
-    @Override
-    public long packets() {
-        return packets;
-    }
-
-    @Override
-    public long bytes() {
-        return bytes;
-    }
-
-    @Override
-    public FlowRuleState state() {
-        return this.state;
-    }
-
 
     @Override
     /*
@@ -162,7 +115,7 @@
     }
 
     public int hash() {
-        return Objects.hash(deviceId, selector, id);
+        return Objects.hash(deviceId, selector, treatment);
     }
 
     @Override
@@ -179,7 +132,7 @@
         if (obj instanceof DefaultFlowRule) {
             DefaultFlowRule that = (DefaultFlowRule) obj;
             return Objects.equals(deviceId, that.deviceId) &&
-                    //Objects.equals(id, that.id) &&
+                    Objects.equals(id, that.id) &&
                     Objects.equals(priority, that.priority) &&
                     Objects.equals(selector, that.selector);
 
@@ -190,19 +143,18 @@
     @Override
     public String toString() {
         return toStringHelper(this)
-                .add("id", id)
+                .add("id", Long.toHexString(id.value()))
                 .add("deviceId", deviceId)
                 .add("priority", priority)
                 .add("selector", selector.criteria())
                 .add("treatment", treatment == null ? "N/A" : treatment.instructions())
                 .add("created", created)
-                .add("state", state)
                 .toString();
     }
 
     @Override
     public int timeout() {
-        return timeout > MAX_TIMEOUT ? MAX_TIMEOUT : this.timeout;
+        return timeout;
     }
 
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficSelector.java b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficSelector.java
index d792c7e..31c53a8 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficSelector.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficSelector.java
@@ -1,36 +1,43 @@
 package org.onlab.onos.net.flow;
 
-import static org.slf4j.LoggerFactory.getLogger;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Objects;
-import java.util.Set;
-
+import com.google.common.collect.ImmutableSet;
 import org.onlab.onos.net.PortNumber;
 import org.onlab.onos.net.flow.criteria.Criteria;
 import org.onlab.onos.net.flow.criteria.Criterion;
 import org.onlab.packet.IpPrefix;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
-import org.slf4j.Logger;
 
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Default traffic selector implementation.
+ */
 public final class DefaultTrafficSelector implements TrafficSelector {
 
-    private final Set<Criterion> selector;
+    private final Set<Criterion> criteria;
 
-    private DefaultTrafficSelector(Set<Criterion> selector) {
-        this.selector = Collections.unmodifiableSet(selector);
+    /**
+     * Creates a new traffic selector with the specified criteria.
+     *
+     * @param criteria criteria
+     */
+    private DefaultTrafficSelector(Set<Criterion> criteria) {
+        this.criteria = Collections.unmodifiableSet(criteria);
     }
 
     @Override
     public Set<Criterion> criteria() {
-        return selector;
+        return criteria;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(selector);
+        return Objects.hash(criteria);
     }
 
     @Override
@@ -40,23 +47,50 @@
         }
         if (obj instanceof DefaultTrafficSelector) {
             DefaultTrafficSelector that = (DefaultTrafficSelector) obj;
-            return Objects.equals(selector, that.selector);
+            return Objects.equals(criteria, that.criteria);
 
         }
         return false;
     }
 
+    /**
+     * Returns a new traffic selector builder.
+     *
+     * @return traffic selector builder
+     */
+    public static TrafficSelector.Builder builder() {
+        return new Builder();
+    }
 
+    /**
+     * Returns a new traffic selector builder primed to produce entities
+     * patterned after the supplied selector.
+     *
+     * @return traffic selector builder
+     */
+    public static TrafficSelector.Builder builder(TrafficSelector selector) {
+        return new Builder(selector);
+    }
 
-    public static class Builder implements TrafficSelector.Builder {
+    /**
+     * Builder of traffic selector entities.
+     */
+    public static final class Builder implements TrafficSelector.Builder {
 
-        private final Logger log = getLogger(getClass());
+        private final Map<Criterion.Type, Criterion> selector = new HashMap<>();
 
-        private final Set<Criterion> selector = new HashSet<>();
+        private Builder() {
+        }
+
+        private Builder(TrafficSelector selector) {
+            for (Criterion c : selector.criteria()) {
+                add(c);
+            }
+        }
 
         @Override
         public Builder add(Criterion criterion) {
-            selector.add(criterion);
+            selector.put(criterion.type(), criterion);
             return this;
         }
 
@@ -107,7 +141,7 @@
 
         @Override
         public TrafficSelector build() {
-            return new DefaultTrafficSelector(selector);
+            return new DefaultTrafficSelector(ImmutableSet.copyOf(selector.values()));
         }
 
     }
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficTreatment.java b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficTreatment.java
index 2ce233f..7182916 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficTreatment.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficTreatment.java
@@ -5,6 +5,7 @@
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Objects;
 
 import org.onlab.onos.net.PortNumber;
 import org.onlab.onos.net.flow.instructions.Instruction;
@@ -14,10 +15,18 @@
 import org.onlab.packet.VlanId;
 import org.slf4j.Logger;
 
+/**
+ * Default traffic treatment implementation.
+ */
 public final class DefaultTrafficTreatment implements TrafficTreatment {
 
     private final List<Instruction> instructions;
 
+    /**
+     * Creates a new traffic treatment from the specified list of instructions.
+     *
+     * @param instructions treatment instructions
+     */
     private DefaultTrafficTreatment(List<Instruction> instructions) {
         this.instructions = Collections.unmodifiableList(instructions);
     }
@@ -28,12 +37,38 @@
     }
 
     /**
+     * Returns a new traffic treatment builder.
+     *
+     * @return traffic treatment builder
+     */
+    public static TrafficTreatment.Builder builder() {
+        return new Builder();
+    }
+
+    //FIXME: Order of instructions may affect hashcode
+    @Override
+    public int hashCode() {
+        return Objects.hash(instructions);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof DefaultTrafficTreatment) {
+            DefaultTrafficTreatment that = (DefaultTrafficTreatment) obj;
+            return  Objects.equals(instructions, that.instructions);
+
+        }
+        return false;
+    }
+
+    /**
      * Builds a list of treatments following the following order.
      * Modifications -> Group -> Output (including drop)
-     *
      */
-
-    public static class Builder implements TrafficTreatment.Builder {
+    public static final class Builder implements TrafficTreatment.Builder {
 
         private final Logger log = getLogger(getClass());
 
@@ -47,27 +82,32 @@
         // TODO: should be a list of instructions based on modification objects
         List<Instruction> modifications = new LinkedList<>();
 
+        // Creates a new builder
+        private Builder() {
+        }
+
+        @Override
         public Builder add(Instruction instruction) {
             if (drop) {
                 return this;
             }
             switch (instruction.type()) {
-            case DROP:
-                drop = true;
-                break;
-            case OUTPUT:
-                outputs.add(instruction);
-                break;
-            case L2MODIFICATION:
-            case L3MODIFICATION:
-                // TODO: enforce modification order if any
-                modifications.add(instruction);
-                break;
-            case GROUP:
-                groups.add(instruction);
-                break;
-            default:
-                log.warn("Unknown instruction type {}", instruction.type());
+                case DROP:
+                    drop = true;
+                    break;
+                case OUTPUT:
+                    outputs.add(instruction);
+                    break;
+                case L2MODIFICATION:
+                case L3MODIFICATION:
+                    // TODO: enforce modification order if any
+                    modifications.add(instruction);
+                    break;
+                case GROUP:
+                    groups.add(instruction);
+                    break;
+                default:
+                    log.warn("Unknown instruction type {}", instruction.type());
             }
             return this;
         }
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowEntry.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowEntry.java
new file mode 100644
index 0000000..5b5f89b
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowEntry.java
@@ -0,0 +1,98 @@
+package org.onlab.onos.net.flow;
+
+
+/**
+ * Represents a generalized match &amp; action pair to be applied to
+ * an infrastucture device.
+ */
+public interface FlowEntry extends FlowRule {
+
+
+    public enum FlowEntryState {
+
+        /**
+         * Indicates that this rule has been submitted for addition.
+         * Not necessarily in  the flow table.
+         */
+        PENDING_ADD,
+
+        /**
+         * Rule has been added which means it is in the flow table.
+         */
+        ADDED,
+
+        /**
+         * Flow has been marked for removal, might still be in flow table.
+         */
+        PENDING_REMOVE,
+
+        /**
+         * Flow has been removed from flow table and can be purged.
+         */
+        REMOVED
+    }
+
+    /**
+     * Returns the flow entry state.
+     *
+     * @return flow entry state
+     */
+    FlowEntryState state();
+
+    /**
+     * Returns the number of milliseconds this flow rule has been applied.
+     *
+     * @return number of millis
+     */
+    long life();
+
+    /**
+     * Returns the number of packets this flow rule has matched.
+     *
+     * @return number of packets
+     */
+    long packets();
+
+    /**
+     * Returns the number of bytes this flow rule has matched.
+     *
+     * @return number of bytes
+     */
+    long bytes();
+
+    /**
+     * When this flow entry was last deemed active.
+     * @return epoch time of last activity
+     */
+    long lastSeen();
+
+    /**
+     * Sets the last active epoch time.
+     */
+    void setLastSeen();
+
+    /**
+     * Sets the new state for this entry.
+     * @param newState new flow entry state.
+     */
+    void setState(FlowEntryState newState);
+
+    /**
+     * Sets how long this entry has been entered in the system.
+     * @param life epoch time
+     */
+    void setLife(long life);
+
+    /**
+     * Number of packets seen by this entry.
+     * @param packets a long value
+     */
+    void setPackets(long packets);
+
+    /**
+     * Number of bytes seen by this rule.
+     * @param bytes a long value
+     */
+    void setBytes(long bytes);
+
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowId.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowId.java
index 6bcf1db..52eac0a 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowId.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowId.java
@@ -26,6 +26,9 @@
         if (this == obj) {
             return true;
         }
+        if (obj == null) {
+            return false;
+        }
         if (obj.getClass()  == this.getClass()) {
             FlowId that = (FlowId) obj;
             return Objects.equal(this.flowid, that.flowid);
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRule.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRule.java
index 4d1b3cf..410aed4 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRule.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRule.java
@@ -2,49 +2,16 @@
 
 import org.onlab.onos.ApplicationId;
 import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.intent.BatchOperationTarget;
 
 /**
  * Represents a generalized match &amp; action pair to be applied to
  * an infrastucture device.
  */
-public interface FlowRule {
+public interface FlowRule extends BatchOperationTarget {
 
     static final int MAX_TIMEOUT = 60;
-
-    public enum FlowRuleState {
-        /**
-         * Indicates that this rule has been created.
-         */
-        CREATED,
-
-        /**
-         * Indicates that this rule has been submitted for addition.
-         * Not necessarily in  the flow table.
-         */
-        PENDING_ADD,
-
-        /**
-         * Rule has been added which means it is in the flow table.
-         */
-        ADDED,
-
-        /**
-         * Flow has been marked for removal, might still be in flow table.
-         */
-        PENDING_REMOVE,
-
-        /**
-         * Flow has been removed from flow table and can be purged.
-         */
-        REMOVED
-    }
-
-    /**
-     * Returns the flow rule state.
-     *
-     * @return flow rule state
-     */
-    FlowRuleState state();
+    static final int MIN_PRIORITY = 0;
 
     //TODO: build cookie value
     /**
@@ -92,27 +59,6 @@
     TrafficTreatment treatment();
 
     /**
-     * Returns the number of milliseconds this flow rule has been applied.
-     *
-     * @return number of millis
-     */
-    long lifeMillis();
-
-    /**
-     * Returns the number of packets this flow rule has matched.
-     *
-     * @return number of packets
-     */
-    long packets();
-
-    /**
-     * Returns the number of bytes this flow rule has matched.
-     *
-     * @return number of bytes
-     */
-    long bytes();
-
-    /**
      * Returns the timeout for this flow requested by an application.
      * @return integer value of the timeout
      */
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchEntry.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchEntry.java
new file mode 100644
index 0000000..d5a1472
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchEntry.java
@@ -0,0 +1,20 @@
+package org.onlab.onos.net.flow;
+
+import org.onlab.onos.net.flow.FlowRuleBatchEntry.FlowRuleOperation;
+import org.onlab.onos.net.intent.BatchOperationEntry;
+
+
+public class FlowRuleBatchEntry
+        extends BatchOperationEntry<FlowRuleOperation, FlowRule> {
+
+    public FlowRuleBatchEntry(FlowRuleOperation operator, FlowRule target) {
+        super(operator, target);
+    }
+
+    public enum FlowRuleOperation {
+        ADD,
+        REMOVE,
+        MODIFY
+    }
+
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchOperation.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchOperation.java
new file mode 100644
index 0000000..74ef165
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchOperation.java
@@ -0,0 +1,13 @@
+package org.onlab.onos.net.flow;
+
+import java.util.Collection;
+
+import org.onlab.onos.net.intent.BatchOperation;
+
+public class FlowRuleBatchOperation
+    extends BatchOperation<FlowRuleBatchEntry> {
+
+    public FlowRuleBatchOperation(Collection<FlowRuleBatchEntry> operations) {
+        super(operations);
+    }
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleProvider.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleProvider.java
index c4e2f92..68762ac 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleProvider.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleProvider.java
@@ -1,6 +1,9 @@
 package org.onlab.onos.net.flow;
 
+import java.util.concurrent.Future;
+
 import org.onlab.onos.ApplicationId;
+import org.onlab.onos.net.intent.BatchOperation;
 import org.onlab.onos.net.provider.Provider;
 
 /**
@@ -34,4 +37,6 @@
      */
     void removeRulesById(ApplicationId id, FlowRule... flowRules);
 
+    Future<Void> executeBatch(BatchOperation<FlowRuleBatchEntry> batch);
+
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleProviderService.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleProviderService.java
index 2076103..8164579 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleProviderService.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleProviderService.java
@@ -14,7 +14,7 @@
      *
      * @param flowRule information about the removed flow
      */
-    void flowRemoved(FlowRule flowRule);
+    void flowRemoved(FlowEntry flowEntry);
 
     /**
      * Pushes the collection of flow entries currently applied on the given
@@ -22,7 +22,7 @@
      *
      * @param flowRules collection of flow rules
      */
-    void pushFlowMetrics(DeviceId deviceId, Iterable<FlowRule> flowRules);
+    void pushFlowMetrics(DeviceId deviceId, Iterable<FlowEntry> flowEntries);
 
 
 
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleService.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleService.java
index c09a56d..6d04810 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleService.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleService.java
@@ -1,5 +1,7 @@
 package org.onlab.onos.net.flow;
 
+import java.util.concurrent.Future;
+
 import org.onlab.onos.ApplicationId;
 import org.onlab.onos.net.DeviceId;
 
@@ -13,6 +15,13 @@
 public interface FlowRuleService {
 
     /**
+     * Returns the number of flow rules in the system.
+     *
+     * @return flow rule count
+     */
+    int getFlowRuleCount();
+
+    /**
      * Returns the collection of flow entries applied on the specified device.
      * This will include flow rules which may not yet have been applied to
      * the device.
@@ -20,7 +29,7 @@
      * @param deviceId device identifier
      * @return collection of flow rules
      */
-    Iterable<FlowRule> getFlowEntries(DeviceId deviceId);
+    Iterable<FlowEntry> getFlowEntries(DeviceId deviceId);
 
     // TODO: add createFlowRule factory method and execute operations method
 
@@ -60,6 +69,13 @@
     Iterable<FlowRule> getFlowRulesById(ApplicationId id);
 
     /**
+     * Applies a batch operation of FlowRules.
+     *
+     * @return future indicating the state of the batch operation
+     */
+    Future<CompletedBatchOperation> applyBatch(FlowRuleBatchOperation batch);
+
+    /**
      * Adds the specified flow rule listener.
      *
      * @param listener flow rule listener
@@ -72,7 +88,4 @@
      * @param listener flow rule listener
      */
     void removeListener(FlowRuleListener listener);
-
-
-
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleStore.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleStore.java
index c6093384..5ce7eb1 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleStore.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleStore.java
@@ -10,11 +10,19 @@
 public interface FlowRuleStore extends Store<FlowRuleEvent, FlowRuleStoreDelegate> {
 
     /**
+     * Returns the number of flow rule in the store.
+     *
+     * @return number of flow rules
+     */
+    int getFlowRuleCount();
+
+    /**
      * Returns the stored flow.
+     *
      * @param rule the rule to look for
      * @return a flow rule
      */
-    FlowRule getFlowRule(FlowRule rule);
+    FlowEntry getFlowEntry(FlowRule rule);
 
     /**
      * Returns the flow entries associated with a device.
@@ -22,7 +30,7 @@
      * @param deviceId the device ID
      * @return the flow entries
      */
-    Iterable<FlowRule> getFlowEntries(DeviceId deviceId);
+    Iterable<FlowEntry> getFlowEntries(DeviceId deviceId);
 
     /**
      * Returns the flow entries associated with an application.
@@ -30,7 +38,7 @@
      * @param appId the application id
      * @return the flow entries
      */
-    Iterable<FlowRule> getFlowEntriesByAppId(ApplicationId appId);
+    Iterable<FlowRule> getFlowRulesByAppId(ApplicationId appId);
 
     /**
      * Stores a new flow rule without generating events.
@@ -40,7 +48,8 @@
     void storeFlowRule(FlowRule rule);
 
     /**
-     * Deletes a flow rule without generating events.
+     * Marks a flow rule for deletion. Actual deletion will occur
+     * when the provider indicates that the flow has been removed.
      *
      * @param rule the flow rule to delete
      */
@@ -52,12 +61,11 @@
      * @param rule the flow rule to add or update
      * @return flow_added event, or null if just an update
      */
-    FlowRuleEvent addOrUpdateFlowRule(FlowRule rule);
+    FlowRuleEvent addOrUpdateFlowRule(FlowEntry rule);
 
     /**
-     * @param rule the flow rule to remove
+     * @param rule the flow entry to remove
      * @return flow_removed event, or null if nothing removed
      */
-    FlowRuleEvent removeFlowRule(FlowRule rule);
-
+    FlowRuleEvent removeFlowRule(FlowEntry rule);
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/instructions/Instructions.java b/core/api/src/main/java/org/onlab/onos/net/flow/instructions/Instructions.java
index 1bf5531..b2ebdee 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/instructions/Instructions.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/instructions/Instructions.java
@@ -3,6 +3,8 @@
 import static com.google.common.base.MoreObjects.toStringHelper;
 import static com.google.common.base.Preconditions.checkNotNull;
 
+import java.util.Objects;
+
 import org.onlab.onos.net.PortNumber;
 import org.onlab.onos.net.flow.instructions.L2ModificationInstruction.L2SubType;
 import org.onlab.onos.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction;
@@ -117,6 +119,24 @@
             return toStringHelper(type()).toString();
 
         }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(type());
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj instanceof DropInstruction) {
+                DropInstruction that = (DropInstruction) obj;
+                return Objects.equals(type(), that.type());
+
+            }
+            return false;
+        }
     }
 
 
@@ -140,6 +160,26 @@
             return toStringHelper(type().toString())
                     .add("port", port).toString();
         }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(port, type());
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj instanceof OutputInstruction) {
+                OutputInstruction that = (OutputInstruction) obj;
+                        Objects.equals(port, that.port);
+
+            }
+            return false;
+        }
     }
 
 }
+
+
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/instructions/L2ModificationInstruction.java b/core/api/src/main/java/org/onlab/onos/net/flow/instructions/L2ModificationInstruction.java
index 8c51624..ce7e16b 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/instructions/L2ModificationInstruction.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/instructions/L2ModificationInstruction.java
@@ -2,6 +2,8 @@
 
 import static com.google.common.base.MoreObjects.toStringHelper;
 
+import java.util.Objects;
+
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
 
@@ -74,6 +76,25 @@
                     .add("mac", mac).toString();
         }
 
+        @Override
+        public int hashCode() {
+            return Objects.hash(mac, subtype);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj instanceof ModEtherInstruction) {
+                ModEtherInstruction that = (ModEtherInstruction) obj;
+                return  Objects.equals(mac, that.mac) &&
+                        Objects.equals(subtype, that.subtype);
+
+            }
+            return false;
+        }
+
 
     }
 
@@ -103,6 +124,25 @@
                     .add("id", vlanId).toString();
         }
 
+        @Override
+        public int hashCode() {
+            return Objects.hash(vlanId, subtype());
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj instanceof ModVlanIdInstruction) {
+                ModVlanIdInstruction that = (ModVlanIdInstruction) obj;
+                return  Objects.equals(vlanId, that.vlanId);
+
+            }
+            return false;
+        }
+
+
     }
 
     /**
@@ -131,6 +171,24 @@
                     .add("pcp", Long.toHexString(vlanPcp)).toString();
         }
 
+        @Override
+        public int hashCode() {
+            return Objects.hash(vlanPcp, subtype());
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj instanceof ModVlanPcpInstruction) {
+                ModVlanPcpInstruction that = (ModVlanPcpInstruction) obj;
+                return  Objects.equals(vlanPcp, that.vlanPcp);
+
+            }
+            return false;
+        }
+
     }
 
 
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/instructions/L3ModificationInstruction.java b/core/api/src/main/java/org/onlab/onos/net/flow/instructions/L3ModificationInstruction.java
index ae82cd9..cf81f86 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/instructions/L3ModificationInstruction.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/instructions/L3ModificationInstruction.java
@@ -2,6 +2,8 @@
 
 import static com.google.common.base.MoreObjects.toStringHelper;
 
+import java.util.Objects;
+
 import org.onlab.packet.IpPrefix;
 
 /**
@@ -66,5 +68,23 @@
                     .add("ip", ip).toString();
         }
 
+        @Override
+        public int hashCode() {
+            return Objects.hash(ip, subtype());
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj instanceof ModIPInstruction) {
+                ModIPInstruction that = (ModIPInstruction) obj;
+                return  Objects.equals(ip, that.ip);
+
+            }
+            return false;
+        }
+
     }
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/AbstractIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/AbstractIntent.java
index eefe750..c8a4a05 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/AbstractIntent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/AbstractIntent.java
@@ -24,7 +24,7 @@
     }
 
     @Override
-    public IntentId getId() {
+    public IntentId id() {
         return id;
     }
 
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperation.java b/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperation.java
index ad34a2c..72a9847 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperation.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperation.java
@@ -3,6 +3,7 @@
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
+import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
@@ -11,11 +12,11 @@
  * A list of BatchOperationEntry.
  *
  * @param <T> the enum of operators <br>
- *        This enum must be defined in each sub-classes.
- *
+ *            This enum must be defined in each sub-classes.
  */
 public abstract class BatchOperation<T extends BatchOperationEntry<?, ?>> {
-    private List<T> ops;
+
+    private final List<T> ops;
 
     /**
      * Creates new {@link BatchOperation} object.
@@ -30,7 +31,7 @@
      *
      * @param batchOperations the list of batch operation entries.
      */
-    public BatchOperation(List<T> batchOperations) {
+    public BatchOperation(Collection<T> batchOperations) {
         ops = new LinkedList<>(checkNotNull(batchOperations));
     }
 
@@ -61,6 +62,10 @@
 
     /**
      * Adds an operation.
+     * FIXME: Brian promises that the Intent Framework
+     * will not modify the batch operation after it has submitted it.
+     * Ali would prefer immutablity, but trusts brian for better or
+     * for worse.
      *
      * @param entry the operation to be added
      * @return this object if succeeded, null otherwise
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperationEntry.java b/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperationEntry.java
index b5dfa88..4e57d33 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperationEntry.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperationEntry.java
@@ -15,14 +15,7 @@
     private final T operator;
     private final U target;
 
-    /**
-     * Default constructor for serializer.
-     */
-    @Deprecated
-    protected BatchOperationEntry() {
-        this.operator = null;
-        this.target = null;
-    }
+
 
     /**
      * Constructs new instance for the entry of the BatchOperation.
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/ConnectivityIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/ConnectivityIntent.java
index 629a9d1..ed0c5cc 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/ConnectivityIntent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/ConnectivityIntent.java
@@ -1,11 +1,10 @@
 package org.onlab.onos.net.intent;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-
+import com.google.common.base.Objects;
 import org.onlab.onos.net.flow.TrafficSelector;
 import org.onlab.onos.net.flow.TrafficTreatment;
 
-import com.google.common.base.Objects;
+import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
  * Abstraction of connectivity intent for traffic matching some criteria.
@@ -26,17 +25,18 @@
 
     /**
      * Creates a connectivity intent that matches on the specified intent
-     * and applies the specified action.
+     * and applies the specified treatement.
      *
-     * @param id    intent identifier
-     * @param match traffic match
-     * @param action action
-     * @throws NullPointerException if the match or action is null
+     * @param intentId   intent identifier
+     * @param selector   traffic selector
+     * @param treatement treatement
+     * @throws NullPointerException if the selector or treatement is null
      */
-    protected ConnectivityIntent(IntentId id, TrafficSelector match, TrafficTreatment action) {
-        super(id);
-        this.selector = checkNotNull(match);
-        this.treatment = checkNotNull(action);
+    protected ConnectivityIntent(IntentId intentId, TrafficSelector selector,
+                                 TrafficTreatment treatement) {
+        super(intentId);
+        this.selector = checkNotNull(selector);
+        this.treatment = checkNotNull(treatement);
     }
 
     /**
@@ -53,7 +53,7 @@
      *
      * @return traffic match
      */
-    public TrafficSelector getTrafficSelector() {
+    public TrafficSelector selector() {
         return selector;
     }
 
@@ -62,7 +62,7 @@
      *
      * @return applied action
      */
-    public TrafficTreatment getTrafficTreatment() {
+    public TrafficTreatment treatment() {
         return treatment;
     }
 
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/HostToHostIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/HostToHostIntent.java
new file mode 100644
index 0000000..f420fc2
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/HostToHostIntent.java
@@ -0,0 +1,91 @@
+package org.onlab.onos.net.intent;
+
+import com.google.common.base.MoreObjects;
+import org.onlab.onos.net.HostId;
+import org.onlab.onos.net.flow.TrafficSelector;
+import org.onlab.onos.net.flow.TrafficTreatment;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Abstraction of end-station to end-station bidirectional connectivity.
+ */
+public class HostToHostIntent extends ConnectivityIntent {
+
+    private final HostId one;
+    private final HostId two;
+
+    /**
+     * Creates a new point-to-point intent with the supplied ingress/egress
+     * ports.
+     *
+     * @param intentId  intent identifier
+     * @param one       first host
+     * @param two       second host
+     * @param selector  action
+     * @param treatment ingress port
+     * @throws NullPointerException if {@code ingressPort} or {@code egressPort}
+     *                              is null.
+     */
+    public HostToHostIntent(IntentId intentId, HostId one, HostId two,
+                            TrafficSelector selector,
+                            TrafficTreatment treatment) {
+        super(intentId, selector, treatment);
+        this.one = checkNotNull(one);
+        this.two = checkNotNull(two);
+    }
+
+    /**
+     * Returns identifier of the first host.
+     *
+     * @return first host identifier
+     */
+    public HostId one() {
+        return one;
+    }
+
+    /**
+     * Returns identifier of the second host.
+     *
+     * @return second host identifier
+     */
+    public HostId two() {
+        return two;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        if (!super.equals(o)) {
+            return false;
+        }
+
+        HostToHostIntent that = (HostToHostIntent) o;
+        return Objects.equals(this.one, that.one)
+                && Objects.equals(this.two, that.two);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), one, two);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                .add("id", id())
+                .add("selector", selector())
+                .add("treatment", treatment())
+                .add("one", one)
+                .add("two", two)
+                .toString();
+    }
+
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/InstallableIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/InstallableIntent.java
index 66bc759..488695c 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/InstallableIntent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/InstallableIntent.java
@@ -1,8 +1,22 @@
 package org.onlab.onos.net.intent;
 
+import org.onlab.onos.net.Link;
+
+import java.util.Collection;
+
 /**
  * Abstraction of an intent that can be installed into
  * the underlying system without additional compilation.
  */
 public interface InstallableIntent extends Intent {
+
+    /**
+     * Returns the collection of links that are required for this installable
+     * intent to exist.
+     *
+     * @return collection of links
+     */
+    // FIXME: replace this with 'NetworkResource'
+    Collection<Link> requiredLinks();
+
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/Intent.java b/core/api/src/main/java/org/onlab/onos/net/intent/Intent.java
index b239ede..3e339d1 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/Intent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/Intent.java
@@ -2,7 +2,7 @@
 
 /**
  * Abstraction of an application level intent.
- *
+ * <p/>
  * Make sure that an Intent should be immutable when a new type is defined.
  */
 public interface Intent extends BatchOperationTarget {
@@ -11,5 +11,5 @@
      *
      * @return intent identifier
      */
-    IntentId getId();
+    IntentId id();
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentEvent.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentEvent.java
index 27ae834..742a590 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/IntentEvent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentEvent.java
@@ -1,113 +1,55 @@
 package org.onlab.onos.net.intent;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import java.util.Objects;
-
-import com.google.common.base.MoreObjects;
+import org.onlab.onos.event.AbstractEvent;
 
 /**
  * A class to represent an intent related event.
  */
-public class IntentEvent {
+public class IntentEvent extends AbstractEvent<IntentEvent.Type, Intent> {
 
-    // TODO: determine a suitable parent class; if one does not exist, consider introducing one
+    public enum Type {
+        /**
+         * Signifies that a new intent has been submitted to the system.
+         */
+        SUBMITTED,
 
-    private final long time;
-    private final Intent intent;
-    private final IntentState state;
-    private final IntentState previous;
+        /**
+         * Signifies that an intent has been successfully installed.
+         */
+        INSTALLED,
+
+        /**
+         * Signifies that an intent has failed compilation or installation.
+         */
+        FAILED,
+
+        /**
+         * Signifies that an intent has been withdrawn from the system.
+         */
+        WITHDRAWN
+    }
 
     /**
-     * Creates an event describing a state change of an intent.
+     * Creates an event of a given type and for the specified intent and the
+     * current time.
      *
+     * @param type   event type
      * @param intent subject intent
-     * @param state new intent state
-     * @param previous previous intent state
-     * @param time time the event created in milliseconds since start of epoch
-     * @throws NullPointerException if the intent or state is null
+     * @param time   time the event created in milliseconds since start of epoch
      */
-    public IntentEvent(Intent intent, IntentState state, IntentState previous, long time) {
-        this.intent = checkNotNull(intent);
-        this.state = checkNotNull(state);
-        this.previous = previous;
-        this.time = time;
+    public IntentEvent(Type type, Intent intent, long time) {
+        super(type, intent, time);
     }
 
     /**
-     * Constructor for serializer.
-     */
-    protected IntentEvent() {
-        this.intent = null;
-        this.state = null;
-        this.previous = null;
-        this.time = 0;
-    }
-
-    /**
-     * Returns the state of the intent which caused the event.
+     * Creates an event of a given type and for the specified intent and the
+     * current time.
      *
-     * @return the state of the intent
+     * @param type   event type
+     * @param intent subject intent
      */
-    public IntentState getState() {
-        return state;
+    public IntentEvent(Type type, Intent intent) {
+        super(type, intent);
     }
 
-    /**
-     * Returns the previous state of the intent which caused the event.
-     *
-     * @return the previous state of the intent
-     */
-    public IntentState getPreviousState() {
-        return previous;
-    }
-
-    /**
-     * Returns the intent associated with the event.
-     *
-     * @return the intent
-     */
-    public Intent getIntent() {
-        return intent;
-    }
-
-    /**
-     * Returns the time at which the event was created.
-     *
-     * @return the time in milliseconds since start of epoch
-     */
-    public long getTime() {
-        return time;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-
-        IntentEvent that = (IntentEvent) o;
-        return Objects.equals(this.intent, that.intent)
-                && Objects.equals(this.state, that.state)
-                && Objects.equals(this.previous, that.previous)
-                && Objects.equals(this.time, that.time);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(intent, state, previous, time);
-    }
-
-    @Override
-    public String toString() {
-        return MoreObjects.toStringHelper(getClass())
-                .add("intent", intent)
-                .add("state", state)
-                .add("previous", previous)
-                .add("time", time)
-                .toString();
-    }
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentEventListener.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentEventListener.java
deleted file mode 100644
index f59ecfc..0000000
--- a/core/api/src/main/java/org/onlab/onos/net/intent/IntentEventListener.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package org.onlab.onos.net.intent;
-
-/**
- * Listener for {@link IntentEvent intent events}.
- */
-public interface IntentEventListener {
-    /**
-     * Processes the specified intent event.
-     *
-     * @param event the event to process
-     */
-    void event(IntentEvent event);
-}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentException.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentException.java
index 4148dea..fff55be 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/IntentException.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentException.java
@@ -26,7 +26,7 @@
      * Constructs an exception with the specified message and the underlying cause.
      *
      * @param message the message describing the specific nature of the error
-     * @param cause the underlying cause of this error
+     * @param cause   the underlying cause of this error
      */
     public IntentException(String message, Throwable cause) {
         super(message, cause);
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentExtensionService.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentExtensionService.java
index c6338a7f..8deb372 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/IntentExtensionService.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentExtensionService.java
@@ -10,9 +10,9 @@
     /**
      * Registers the specified compiler for the given intent class.
      *
-     * @param cls intent class
+     * @param cls      intent class
      * @param compiler intent compiler
-     * @param <T> the type of intent
+     * @param <T>      the type of intent
      */
     <T extends Intent> void registerCompiler(Class<T> cls, IntentCompiler<T> compiler);
 
@@ -34,9 +34,9 @@
     /**
      * Registers the specified installer for the given installable intent class.
      *
-     * @param cls installable intent class
+     * @param cls       installable intent class
      * @param installer intent installer
-     * @param <T> the type of installable intent
+     * @param <T>       the type of installable intent
      */
     <T extends InstallableIntent> void registerInstaller(Class<T> cls, IntentInstaller<T> installer);
 
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentId.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentId.java
index 798e00c..8f132c0 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/IntentId.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentId.java
@@ -2,7 +2,7 @@
 
 /**
  * Intent identifier suitable as an external key.
- *
+ * <p/>
  * This class is immutable.
  */
 public final class IntentId implements BatchOperationTarget {
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentListener.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentListener.java
new file mode 100644
index 0000000..c00c1f6
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentListener.java
@@ -0,0 +1,9 @@
+package org.onlab.onos.net.intent;
+
+import org.onlab.onos.event.EventListener;
+
+/**
+ * Listener for {@link IntentEvent intent events}.
+ */
+public interface IntentListener extends EventListener<IntentEvent> {
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentService.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentService.java
index 2b4fb59..c3aae54 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/IntentService.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentService.java
@@ -1,6 +1,5 @@
 package org.onlab.onos.net.intent;
 
-import java.util.Set;
 
 /**
  * Service for application submitting or withdrawing their intents.
@@ -8,9 +7,9 @@
 public interface IntentService {
     /**
      * Submits an intent into the system.
-     *
-     * This is an asynchronous request meaning that any compiling
-     * or installation activities may be done at later time.
+     * <p/>
+     * This is an asynchronous request meaning that any compiling or
+     * installation activities may be done at later time.
      *
      * @param intent intent to be submitted
      */
@@ -18,9 +17,9 @@
 
     /**
      * Withdraws an intent from the system.
-     *
-     * This is an asynchronous request meaning that the environment
-     * may be affected at later time.
+     * <p/>
+     * This is an asynchronous request meaning that the environment may be
+     * affected at later time.
      *
      * @param intent intent to be withdrawn
      */
@@ -29,20 +28,27 @@
     /**
      * Submits a batch of submit &amp; withdraw operations. Such a batch is
      * assumed to be processed together.
-     *
-     * This is an asynchronous request meaning that the environment
-     * may be affected at later time.
+     * <p/>
+     * This is an asynchronous request meaning that the environment may be
+     * affected at later time.
      *
      * @param operations batch of intent operations
      */
     void execute(IntentOperations operations);
 
     /**
-     * Returns immutable set of intents currently in the system.
+     * Returns an iterable of intents currently in the system.
      *
      * @return set of intents
      */
-    Set<Intent> getIntents();
+    Iterable<Intent> getIntents();
+
+    /**
+     * Returns the number of intents currently in the system.
+     *
+     * @return number of intents
+     */
+    long getIntentCount();
 
     /**
      * Retrieves the intent specified by its identifier.
@@ -56,7 +62,8 @@
      * Retrieves the state of an intent by its identifier.
      *
      * @param id intent identifier
-     * @return the intent state or null if one with the given identifier is not found
+     * @return the intent state or null if one with the given identifier is not
+     * found
      */
     IntentState getIntentState(IntentId id);
 
@@ -65,12 +72,12 @@
      *
      * @param listener listener to be added
      */
-    void addListener(IntentEventListener listener);
+    void addListener(IntentListener listener);
 
     /**
      * Removes the specified listener for intent events.
      *
      * @param listener listener to be removed
      */
-    void removeListener(IntentEventListener listener);
+    void removeListener(IntentListener listener);
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentState.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentState.java
index 20476e5..bd140af 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/IntentState.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentState.java
@@ -1,55 +1,70 @@
 package org.onlab.onos.net.intent;
 
 /**
- * This class represents the states of an intent.
- *
- * <p>
- * Note: The state is expressed as enum, but there is possibility
- * in the future that we define specific class instead of enum to improve
- * the extensibility of state definition.
- * </p>
+ * Representation of the phases an intent may attain during its lifecycle.
  */
 public enum IntentState {
-    // FIXME: requires discussion on State vs. EventType and a solid state-transition diagram
-    // TODO: consider the impact of conflict detection
-    // TODO: consider the impact that external events affect an installed intent
+
     /**
-     * The beginning state.
-     *
+     * Signifies that the intent has been submitted and will start compiling
+     * shortly. However, this compilation may not necessarily occur on the
+     * local controller instance.
+     * <p/>
      * All intent in the runtime take this state first.
      */
     SUBMITTED,
 
     /**
-     * The intent compilation has been completed.
-     *
-     * An intent translation graph (tree) is completely created.
-     * Leaves of the graph are installable intent type.
+     * Signifies that the intent is being compiled into installable intents.
+     * This is a transitional state after which the intent will enter either
+     * {@link #FAILED} state or {@link #INSTALLING} state.
      */
-    COMPILED,
+    COMPILING,
 
     /**
-     * The intent has been successfully installed.
+     * Signifies that the resulting installable intents are being installed
+     * into the network environment. This is a transitional state after which
+     * the intent will enter either {@link #INSTALLED} state or
+     * {@link #RECOMPILING} state.
+     */
+    INSTALLING,
+
+    /**
+     * The intent has been successfully installed. This is a state where the
+     * intent may remain parked until it is withdrawn by the application or
+     * until the network environment changes in some way to make the original
+     * set of installable intents untenable.
      */
     INSTALLED,
 
     /**
-     * The intent is being withdrawn.
-     *
-     * When {@link IntentService#withdraw(Intent)} is called,
-     * the intent takes this state first.
+     * Signifies that the intent is being recompiled into installable intents
+     * as an attempt to adapt to an anomaly in the network environment.
+     * This is a transitional state after which the intent will enter either
+     * {@link #FAILED} state or {@link #INSTALLING} state.
+     * <p/>
+     * Exit to the {@link #FAILED} state may be caused by failure to compile
+     * or by compiling into the same set of installable intents which have
+     * previously failed to be installed.
+     */
+    RECOMPILING,
+
+    /**
+     * Indicates that the intent is being withdrawn. This is a transitional
+     * state, triggered by invocation of the
+     * {@link IntentService#withdraw(Intent)} but one with only one outcome,
+     * which is the the intent being placed in the {@link #WITHDRAWN} state.
      */
     WITHDRAWING,
 
     /**
-     * The intent has been successfully withdrawn.
+     * Indicates that the intent has been successfully withdrawn.
      */
     WITHDRAWN,
 
     /**
-     * The intent has failed to be compiled, installed, or withdrawn.
-     *
-     * When the intent failed to be withdrawn, it is still, at least partially installed.
+     * Signifies that the intent has failed compiling, installing or
+     * recompiling states.
      */
-    FAILED,
+    FAILED
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentStore.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentStore.java
new file mode 100644
index 0000000..fc023bb
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentStore.java
@@ -0,0 +1,101 @@
+package org.onlab.onos.net.intent;
+
+import org.onlab.onos.store.Store;
+
+import java.util.List;
+
+/**
+ * Manages inventory of end-station intents; not intended for direct use.
+ */
+public interface IntentStore extends Store<IntentEvent, IntentStoreDelegate> {
+
+    /**
+     * Submits a new intent into the store. If the returned event is not
+     * null, the manager is expected to dispatch the event and then to kick
+     * off intent compilation process. Otherwise, another node has been elected
+     * to perform the compilation process and the node will learn about
+     * the submittal and results of the intent compilation via the delegate
+     * mechanism.
+     *
+     * @param intent intent to be submitted
+     * @return event indicating the intent was submitted or null if no
+     * change resulted, e.g. duplicate intent
+     */
+    IntentEvent createIntent(Intent intent);
+
+    /**
+     * Removes the specified intent from the inventory.
+     *
+     * @param intentId intent identification
+     * @return removed state transition event or null if intent was not found
+     */
+    IntentEvent removeIntent(IntentId intentId);
+
+    /**
+     * Returns the number of intents in the store.
+     */
+    long getIntentCount();
+
+    /**
+     * Returns a collection of all intents in the store.
+     *
+     * @return iterable collection of all intents
+     */
+    Iterable<Intent> getIntents();
+
+    /**
+     * Returns the intent with the specified identifer.
+     *
+     * @param intentId intent identification
+     * @return intent or null if not found
+     */
+    Intent getIntent(IntentId intentId);
+
+    /**
+     * Returns the state of the specified intent.
+     *
+     * @param intentId intent identification
+     * @return current intent state
+     */
+    IntentState getIntentState(IntentId intentId);
+
+    /**
+     * Sets the state of the specified intent to the new state.
+     *
+     * @param intent   intent whose state is to be changed
+     * @param newState new state
+     * @return state transition event
+     */
+    IntentEvent setState(Intent intent, IntentState newState);
+
+    /**
+     * Adds the installable intents which resulted from compilation of the
+     * specified original intent.
+     *
+     * @param intentId           original intent identifier
+     * @param installableIntents compiled installable intents
+     */
+    void addInstallableIntents(IntentId intentId,
+                               List<InstallableIntent> installableIntents);
+
+    /**
+     * Returns the list of the installable events associated with the specified
+     * original intent.
+     *
+     * @param intentId original intent identifier
+     * @return compiled installable intents
+     */
+    List<InstallableIntent> getInstallableIntents(IntentId intentId);
+
+    // TODO: this should be triggered from with the store as a result of removeIntent call
+
+    /**
+     * Removes any installable intents which resulted from compilation of the
+     * specified original intent.
+     *
+     * @param intentId original intent identifier
+     * @return compiled state transition event
+     */
+    void removeInstalledIntents(IntentId intentId);
+
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentStoreDelegate.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentStoreDelegate.java
new file mode 100644
index 0000000..6c37db8
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentStoreDelegate.java
@@ -0,0 +1,9 @@
+package org.onlab.onos.net.intent;
+
+import org.onlab.onos.store.StoreDelegate;
+
+/**
+ * Intent store delegate abstraction.
+ */
+public interface IntentStoreDelegate extends StoreDelegate<IntentEvent> {
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/MultiPointToSinglePointIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/MultiPointToSinglePointIntent.java
index 1e421ab..be8d309 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/MultiPointToSinglePointIntent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/MultiPointToSinglePointIntent.java
@@ -1,25 +1,24 @@
 package org.onlab.onos.net.intent;
 
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import java.util.Objects;
-import java.util.Set;
-
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.Sets;
 import org.onlab.onos.net.ConnectPoint;
 import org.onlab.onos.net.flow.TrafficSelector;
 import org.onlab.onos.net.flow.TrafficTreatment;
 
-import com.google.common.base.MoreObjects;
-import com.google.common.collect.Sets;
+import java.util.Objects;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
  * Abstraction of multiple source to single destination connectivity intent.
  */
 public class MultiPointToSinglePointIntent extends ConnectivityIntent {
 
-    private final Set<ConnectPoint> ingressPorts;
-    private final ConnectPoint egressPort;
+    private final Set<ConnectPoint> ingressPoints;
+    private final ConnectPoint egressPoint;
 
     /**
      * Creates a new multi-to-single point connectivity intent for the specified
@@ -28,23 +27,25 @@
      * @param id           intent identifier
      * @param match        traffic match
      * @param action       action
-     * @param ingressPorts set of ports from which ingress traffic originates
-     * @param egressPort   port to which traffic will egress
-     * @throws NullPointerException if {@code ingressPorts} or
-     * {@code egressPort} is null.
-     * @throws IllegalArgumentException if the size of {@code ingressPorts} is
-     * not more than 1
+     * @param ingressPoints set of ports from which ingress traffic originates
+     * @param egressPoint   port to which traffic will egress
+     * @throws NullPointerException     if {@code ingressPoints} or
+     *                                  {@code egressPoint} is null.
+     * @throws IllegalArgumentException if the size of {@code ingressPoints} is
+     *                                  not more than 1
      */
-    public MultiPointToSinglePointIntent(IntentId id, TrafficSelector match, TrafficTreatment action,
-            Set<ConnectPoint> ingressPorts, ConnectPoint egressPort) {
+    public MultiPointToSinglePointIntent(IntentId id, TrafficSelector match,
+                                         TrafficTreatment action,
+                                         Set<ConnectPoint> ingressPoints,
+                                         ConnectPoint egressPoint) {
         super(id, match, action);
 
-        checkNotNull(ingressPorts);
-        checkArgument(!ingressPorts.isEmpty(),
-                "there should be at least one ingress port");
+        checkNotNull(ingressPoints);
+        checkArgument(!ingressPoints.isEmpty(),
+                      "there should be at least one ingress port");
 
-        this.ingressPorts = Sets.newHashSet(ingressPorts);
-        this.egressPort = checkNotNull(egressPort);
+        this.ingressPoints = Sets.newHashSet(ingressPoints);
+        this.egressPoint = checkNotNull(egressPoint);
     }
 
     /**
@@ -52,8 +53,8 @@
      */
     protected MultiPointToSinglePointIntent() {
         super();
-        this.ingressPorts = null;
-        this.egressPort = null;
+        this.ingressPoints = null;
+        this.egressPoint = null;
     }
 
     /**
@@ -62,8 +63,8 @@
      *
      * @return set of ingress ports
      */
-    public Set<ConnectPoint> getIngressPorts() {
-        return ingressPorts;
+    public Set<ConnectPoint> ingressPoints() {
+        return ingressPoints;
     }
 
     /**
@@ -71,8 +72,8 @@
      *
      * @return egress port
      */
-    public ConnectPoint getEgressPort() {
-        return egressPort;
+    public ConnectPoint egressPoint() {
+        return egressPoint;
     }
 
     @Override
@@ -88,23 +89,23 @@
         }
 
         MultiPointToSinglePointIntent that = (MultiPointToSinglePointIntent) o;
-        return Objects.equals(this.ingressPorts, that.ingressPorts)
-                && Objects.equals(this.egressPort, that.egressPort);
+        return Objects.equals(this.ingressPoints, that.ingressPoints)
+                && Objects.equals(this.egressPoint, that.egressPoint);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(super.hashCode(), ingressPorts, egressPort);
+        return Objects.hash(super.hashCode(), ingressPoints, egressPoint);
     }
 
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(getClass())
-                .add("id", getId())
-                .add("match", getTrafficSelector())
-                .add("action", getTrafficTreatment())
-                .add("ingressPorts", getIngressPorts())
-                .add("egressPort", getEgressPort())
+                .add("id", id())
+                .add("match", selector())
+                .add("action", treatment())
+                .add("ingressPoints", ingressPoints())
+                .add("egressPoint", egressPoint())
                 .toString();
     }
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/OpticalConnectivityIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/OpticalConnectivityIntent.java
index d11dc7c..b99eb70 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/OpticalConnectivityIntent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/OpticalConnectivityIntent.java
@@ -3,30 +3,30 @@
 import org.onlab.onos.net.ConnectPoint;
 
 // TODO: consider if this intent should be sub-class of ConnectivityIntent
+
 /**
  * An optical layer Intent for a connectivity from a transponder port to another
  * transponder port.
- * <p>
+ * <p/>
  * This class doesn't accepts lambda specifier. This class computes path between
  * ports and assign lambda automatically. The lambda can be specified using
  * OpticalPathFlow class.
  */
 public class OpticalConnectivityIntent extends AbstractIntent {
-    protected ConnectPoint srcConnectPoint;
-    protected ConnectPoint dstConnectPoint;
+    protected ConnectPoint src;
+    protected ConnectPoint dst;
 
     /**
      * Constructor.
      *
-     * @param id ID for this new Intent object.
-     * @param srcConnectPoint The source transponder port.
-     * @param dstConnectPoint The destination transponder port.
+     * @param id  ID for this new Intent object.
+     * @param src The source transponder port.
+     * @param dst The destination transponder port.
      */
-    public OpticalConnectivityIntent(IntentId id,
-            ConnectPoint srcConnectPoint, ConnectPoint dstConnectPoint) {
+    public OpticalConnectivityIntent(IntentId id, ConnectPoint src, ConnectPoint dst) {
         super(id);
-        this.srcConnectPoint = srcConnectPoint;
-        this.dstConnectPoint = dstConnectPoint;
+        this.src = src;
+        this.dst = dst;
     }
 
     /**
@@ -34,8 +34,8 @@
      */
     protected OpticalConnectivityIntent() {
         super();
-        this.srcConnectPoint = null;
-        this.dstConnectPoint = null;
+        this.src = null;
+        this.dst = null;
     }
 
     /**
@@ -44,7 +44,7 @@
      * @return The source transponder port.
      */
     public ConnectPoint getSrcConnectPoint() {
-        return srcConnectPoint;
+        return src;
     }
 
     /**
@@ -52,7 +52,7 @@
      *
      * @return The source transponder port.
      */
-    public ConnectPoint getDstConnectPoint() {
-        return dstConnectPoint;
+    public ConnectPoint getDst() {
+        return dst;
     }
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java
index 39ad011..ff2e917 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java
@@ -1,18 +1,19 @@
 package org.onlab.onos.net.intent;
 
-import java.util.Objects;
-
+import com.google.common.base.MoreObjects;
 import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.Link;
 import org.onlab.onos.net.Path;
 import org.onlab.onos.net.flow.TrafficSelector;
 import org.onlab.onos.net.flow.TrafficTreatment;
 
-import com.google.common.base.MoreObjects;
+import java.util.Collection;
+import java.util.Objects;
 
 /**
  * Abstraction of explicitly path specified connectivity intent.
  */
-public class PathIntent extends PointToPointIntent {
+public class PathIntent extends PointToPointIntent implements InstallableIntent {
 
     private final Path path;
 
@@ -45,7 +46,7 @@
      *
      * @return traversed links
      */
-    public Path getPath() {
+    public Path path() {
         return path;
     }
 
@@ -78,12 +79,18 @@
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(getClass())
-                .add("id", getId())
-                .add("match", getTrafficSelector())
-                .add("action", getTrafficTreatment())
-                .add("ingressPort", getIngressPort())
-                .add("egressPort", getEgressPort())
+                .add("id", id())
+                .add("match", selector())
+                .add("action", treatment())
+                .add("ingressPort", ingressPoint())
+                .add("egressPort", egressPoint())
                 .add("path", path)
                 .toString();
     }
+
+    @Override
+    public Collection<Link> requiredLinks() {
+        return path.links();
+    }
+
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/PointToPointIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/PointToPointIntent.java
index b1d18ee..7b7c18a 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/PointToPointIntent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/PointToPointIntent.java
@@ -1,39 +1,40 @@
 package org.onlab.onos.net.intent;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import java.util.Objects;
-
+import com.google.common.base.MoreObjects;
 import org.onlab.onos.net.ConnectPoint;
 import org.onlab.onos.net.flow.TrafficSelector;
 import org.onlab.onos.net.flow.TrafficTreatment;
 
-import com.google.common.base.MoreObjects;
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
  * Abstraction of point-to-point connectivity.
  */
 public class PointToPointIntent extends ConnectivityIntent {
 
-    private final ConnectPoint ingressPort;
-    private final ConnectPoint egressPort;
+    private final ConnectPoint ingressPoint;
+    private final ConnectPoint egressPoint;
 
     /**
      * Creates a new point-to-point intent with the supplied ingress/egress
      * ports.
      *
-     * @param id          intent identifier
-     * @param match       traffic match
-     * @param action      action
-     * @param ingressPort ingress port
-     * @param egressPort  egress port
-     * @throws NullPointerException if {@code ingressPort} or {@code egressPort} is null.
+     * @param id           intent identifier
+     * @param selector     traffic selector
+     * @param treatment    treatment
+     * @param ingressPoint ingress port
+     * @param egressPoint  egress port
+     * @throws NullPointerException if {@code ingressPoint} or {@code egressPoints} is null.
      */
-    public PointToPointIntent(IntentId id, TrafficSelector match, TrafficTreatment action,
-                              ConnectPoint ingressPort, ConnectPoint egressPort) {
-        super(id, match, action);
-        this.ingressPort = checkNotNull(ingressPort);
-        this.egressPort = checkNotNull(egressPort);
+    public PointToPointIntent(IntentId id, TrafficSelector selector,
+                              TrafficTreatment treatment,
+                              ConnectPoint ingressPoint,
+                              ConnectPoint egressPoint) {
+        super(id, selector, treatment);
+        this.ingressPoint = checkNotNull(ingressPoint);
+        this.egressPoint = checkNotNull(egressPoint);
     }
 
     /**
@@ -41,8 +42,8 @@
      */
     protected PointToPointIntent() {
         super();
-        this.ingressPort = null;
-        this.egressPort = null;
+        this.ingressPoint = null;
+        this.egressPoint = null;
     }
 
     /**
@@ -51,8 +52,8 @@
      *
      * @return ingress port
      */
-    public ConnectPoint getIngressPort() {
-        return ingressPort;
+    public ConnectPoint ingressPoint() {
+        return ingressPoint;
     }
 
     /**
@@ -60,8 +61,8 @@
      *
      * @return egress port
      */
-    public ConnectPoint getEgressPort() {
-        return egressPort;
+    public ConnectPoint egressPoint() {
+        return egressPoint;
     }
 
     @Override
@@ -77,23 +78,23 @@
         }
 
         PointToPointIntent that = (PointToPointIntent) o;
-        return Objects.equals(this.ingressPort, that.ingressPort)
-                && Objects.equals(this.egressPort, that.egressPort);
+        return Objects.equals(this.ingressPoint, that.ingressPoint)
+                && Objects.equals(this.egressPoint, that.egressPoint);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(super.hashCode(), ingressPort, egressPort);
+        return Objects.hash(super.hashCode(), ingressPoint, egressPoint);
     }
 
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(getClass())
-                .add("id", getId())
-                .add("match", getTrafficSelector())
-                .add("action", getTrafficTreatment())
-                .add("ingressPort", ingressPort)
-                .add("egressPort", egressPort)
+                .add("id", id())
+                .add("match", selector())
+                .add("action", treatment())
+                .add("ingressPoint", ingressPoint)
+                .add("egressPoints", egressPoint)
                 .toString();
     }
 
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/SinglePointToMultiPointIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/SinglePointToMultiPointIntent.java
index e69a740..2a17bfe 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/SinglePointToMultiPointIntent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/SinglePointToMultiPointIntent.java
@@ -1,50 +1,50 @@
 package org.onlab.onos.net.intent;
 
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import java.util.Objects;
-import java.util.Set;
-
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.Sets;
 import org.onlab.onos.net.ConnectPoint;
 import org.onlab.onos.net.flow.TrafficSelector;
 import org.onlab.onos.net.flow.TrafficTreatment;
 
-import com.google.common.base.MoreObjects;
-import com.google.common.collect.Sets;
+import java.util.Objects;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
  * Abstraction of single source, multiple destination connectivity intent.
  */
 public class SinglePointToMultiPointIntent extends ConnectivityIntent {
 
-    private final ConnectPoint ingressPort;
-    private final Set<ConnectPoint> egressPorts;
+    private final ConnectPoint ingressPoint;
+    private final Set<ConnectPoint> egressPoints;
 
     /**
      * Creates a new single-to-multi point connectivity intent.
      *
-     * @param id          intent identifier
-     * @param match       traffic match
-     * @param action      action
-     * @param ingressPort port on which traffic will ingress
-     * @param egressPorts set of ports on which traffic will egress
-     * @throws NullPointerException if {@code ingressPort} or
-     * {@code egressPorts} is null
-     * @throws IllegalArgumentException if the size of {@code egressPorts} is
-     * not more than 1
+     * @param id           intent identifier
+     * @param selector     traffic selector
+     * @param treatment    treatment
+     * @param ingressPoint port on which traffic will ingress
+     * @param egressPoints set of ports on which traffic will egress
+     * @throws NullPointerException     if {@code ingressPoint} or
+     *                                  {@code egressPoints} is null
+     * @throws IllegalArgumentException if the size of {@code egressPoints} is
+     *                                  not more than 1
      */
-    public SinglePointToMultiPointIntent(IntentId id, TrafficSelector match, TrafficTreatment action,
-                                         ConnectPoint ingressPort,
-                                         Set<ConnectPoint> egressPorts) {
-        super(id, match, action);
+    public SinglePointToMultiPointIntent(IntentId id, TrafficSelector selector,
+                                         TrafficTreatment treatment,
+                                         ConnectPoint ingressPoint,
+                                         Set<ConnectPoint> egressPoints) {
+        super(id, selector, treatment);
 
-        checkNotNull(egressPorts);
-        checkArgument(!egressPorts.isEmpty(),
-                "there should be at least one egress port");
+        checkNotNull(egressPoints);
+        checkArgument(!egressPoints.isEmpty(),
+                      "there should be at least one egress port");
 
-        this.ingressPort = checkNotNull(ingressPort);
-        this.egressPorts = Sets.newHashSet(egressPorts);
+        this.ingressPoint = checkNotNull(ingressPoint);
+        this.egressPoints = Sets.newHashSet(egressPoints);
     }
 
     /**
@@ -52,8 +52,8 @@
      */
     protected SinglePointToMultiPointIntent() {
         super();
-        this.ingressPort = null;
-        this.egressPorts = null;
+        this.ingressPoint = null;
+        this.egressPoints = null;
     }
 
     /**
@@ -61,8 +61,8 @@
      *
      * @return ingress port
      */
-    public ConnectPoint getIngressPort() {
-        return ingressPort;
+    public ConnectPoint ingressPoint() {
+        return ingressPoint;
     }
 
     /**
@@ -70,8 +70,8 @@
      *
      * @return set of egress ports
      */
-    public Set<ConnectPoint> getEgressPorts() {
-        return egressPorts;
+    public Set<ConnectPoint> egressPoints() {
+        return egressPoints;
     }
 
     @Override
@@ -87,23 +87,23 @@
         }
 
         SinglePointToMultiPointIntent that = (SinglePointToMultiPointIntent) o;
-        return Objects.equals(this.ingressPort, that.ingressPort)
-                && Objects.equals(this.egressPorts, that.egressPorts);
+        return Objects.equals(this.ingressPoint, that.ingressPoint)
+                && Objects.equals(this.egressPoints, that.egressPoints);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(super.hashCode(), ingressPort, egressPorts);
+        return Objects.hash(super.hashCode(), ingressPoint, egressPoints);
     }
 
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(getClass())
-                .add("id", getId())
-                .add("match", getTrafficSelector())
-                .add("action", getTrafficTreatment())
-                .add("ingressPort", ingressPort)
-                .add("egressPort", egressPorts)
+                .add("id", id())
+                .add("match", selector())
+                .add("action", treatment())
+                .add("ingressPoint", ingressPoint)
+                .add("egressPort", egressPoints)
                 .toString();
     }
 
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/package-info.java b/core/api/src/main/java/org/onlab/onos/net/intent/package-info.java
index e1e6782..ff97f5b 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/package-info.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/package-info.java
@@ -1,5 +1,56 @@
 /**
- * Intent Package. TODO
+ * Set of abstractions for conveying high-level intents for treatment of
+ * selected network traffic by allowing applications to express the
+ * <em>what</em> rather than the <em>how</em>. This makes such instructions
+ * largely independent of topology and device specifics, thus allowing them to
+ * survive topology mutations.
+ * <p/>
+ * The controller core provides a suite of built-in intents and their compilers
+ * and installers. However, the intent framework is extensible in that it allows
+ * additional intents and their compilers or installers to be added
+ * dynamically at run-time. This allows others to enhance the initial arsenal of
+ * connectivity and policy-based intents available in base controller software.
+ * <p/>
+ * The following diagram depicts the state transition diagram for each top-level intent:<br>
+ * <img src="doc-files/intent-states.png" alt="ONOS intent states">
+ * <p/>
+ * The controller core accepts the intent specifications and translates them, via a
+ * process referred to as intent compilation, to installable intents, which are
+ * essentially actionable operations on the network environment.
+ * These actions are carried out by intent installation process, which results
+ * in some changes to the environment, e.g. tunnel links being provisioned,
+ * flow rules being installed on the data-plane, optical lambdas being reserved.
+ * <p/>
+ * After an intent is submitted by an application, it will be sent immediately
+ * (but asynchronously) into a compiling phase, then to installing phase and if
+ * all goes according to plan into installed state. Once an application decides
+ * it no longer wishes the intent to hold, it can withdraw it. This describes
+ * the nominal flow. However, it may happen that some issue is encountered.
+ * For example, an application may ask for an objective that is not currently
+ * achievable, e.g. connectivity across to unconnected network segments.
+ * If this is the case, the compiling phase may fail to produce a set of
+ * installable intents and instead result in a failed compile. If this occurs,
+ * only a change in the environment can trigger a transition back to the
+ * compiling state.
+ * <p/>
+ * Similarly, an issue may be encountered during the installation phase in
+ * which case the framework will attempt to recompile the intent to see if an
+ * alternate approach is available. If so, the intent will be sent back to
+ * installing phase. Otherwise, it will be parked in the failed state. Another
+ * scenario that’s very likely to be encountered is where the intent is
+ * successfully compiled and installed, but due to some topology event, such
+ * as a downed or downgraded link, loss of throughput may occur or connectivity
+ * may be lost altogether, thus impacting the viability of a previously
+ * satisfied intent. If this occurs, the framework will attempt to recompile
+ * the intent, and if an alternate approach is available, its installation
+ * will be attempted. Otherwise, the original top-level intent will be parked
+ * in the failed state.
+ * <p/>
+ * Please note that all *ing states, depicted in orange, are transitional and
+ * are expected to last only a brief amount of time. The rest of the states
+ * are parking states where the intent may spent some time; except for the
+ * submitted state of course. There, the intent may pause, but only briefly,
+ * while the system determines where to perform the compilation or while it
+ * performs global recomputation/optimization across all prior intents.
  */
-
 package org.onlab.onos.net.intent;
\ No newline at end of file
diff --git a/core/api/src/main/java/org/onlab/onos/net/link/LinkDescription.java b/core/api/src/main/java/org/onlab/onos/net/link/LinkDescription.java
index b1be82c..4b34ad6 100644
--- a/core/api/src/main/java/org/onlab/onos/net/link/LinkDescription.java
+++ b/core/api/src/main/java/org/onlab/onos/net/link/LinkDescription.java
@@ -1,12 +1,13 @@
 package org.onlab.onos.net.link;
 
 import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.Description;
 import org.onlab.onos.net.Link;
 
 /**
  * Describes an infrastructure link.
  */
-public interface LinkDescription {
+public interface LinkDescription extends Description {
 
     /**
      * Returns the link source.
diff --git a/core/api/src/main/java/org/onlab/onos/net/packet/DefaultPacketContext.java b/core/api/src/main/java/org/onlab/onos/net/packet/DefaultPacketContext.java
index 6f1b708..75100c6 100644
--- a/core/api/src/main/java/org/onlab/onos/net/packet/DefaultPacketContext.java
+++ b/core/api/src/main/java/org/onlab/onos/net/packet/DefaultPacketContext.java
@@ -24,7 +24,7 @@
         this.inPkt = inPkt;
         this.outPkt = outPkt;
         this.block = new AtomicBoolean(block);
-        this.builder = new DefaultTrafficTreatment.Builder();
+        this.builder = DefaultTrafficTreatment.builder();
     }
 
     @Override
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 afaecbe..c2a3133 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
@@ -3,6 +3,7 @@
 import java.util.Objects;
 
 import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
  * External identity of a {@link org.onlab.onos.net.provider.Provider} family.
@@ -19,10 +20,22 @@
  */
 public class ProviderId {
 
+    /**
+     * Represents no provider ID.
+     */
+    public static final ProviderId NONE = new ProviderId();
+
     private final String scheme;
     private final String id;
     private final boolean ancillary;
 
+    // For serialization
+    private ProviderId() {
+        scheme = null;
+        id = null;
+        ancillary = false;
+    }
+
     /**
      * Creates a new primary provider identifier from the specified string.
      * The providers are expected to follow the reverse DNS convention, e.g.
@@ -45,8 +58,8 @@
      * @param ancillary ancillary provider indicator
      */
     public ProviderId(String scheme, String id, boolean ancillary) {
-        this.scheme = scheme;
-        this.id = id;
+        this.scheme = checkNotNull(scheme, "Scheme cannot be null");
+        this.id = checkNotNull(id, "ID cannot be null");
         this.ancillary = ancillary;
     }
 
diff --git a/core/api/src/main/java/org/onlab/onos/net/proxyarp/ProxyArpService.java b/core/api/src/main/java/org/onlab/onos/net/proxyarp/ProxyArpService.java
index 4ee43c7..77d1208 100644
--- a/core/api/src/main/java/org/onlab/onos/net/proxyarp/ProxyArpService.java
+++ b/core/api/src/main/java/org/onlab/onos/net/proxyarp/ProxyArpService.java
@@ -1,5 +1,6 @@
 package org.onlab.onos.net.proxyarp;
 
+import org.onlab.onos.net.packet.PacketContext;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.IpPrefix;
 
@@ -33,4 +34,12 @@
      */
     void forward(Ethernet eth);
 
+    /**
+     * Handles a arp packet.
+     * Replies to arp requests and forwards request to the  right place.
+     * @param context the packet context to handle
+     * @return true if handled, false otherwise.
+     */
+    boolean handleArp(PacketContext context);
+
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/topology/TopologyEvent.java b/core/api/src/main/java/org/onlab/onos/net/topology/TopologyEvent.java
index 0be5323..268b6ac 100644
--- a/core/api/src/main/java/org/onlab/onos/net/topology/TopologyEvent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/topology/TopologyEvent.java
@@ -1,12 +1,17 @@
 package org.onlab.onos.net.topology;
 
 import org.onlab.onos.event.AbstractEvent;
+import org.onlab.onos.event.Event;
+
+import java.util.List;
 
 /**
  * Describes network topology event.
  */
 public class TopologyEvent extends AbstractEvent<TopologyEvent.Type, Topology> {
 
+    private final List<Event> reasons;
+
     /**
      * Type of topology events.
      */
@@ -23,9 +28,11 @@
      *
      * @param type     topology event type
      * @param topology event topology subject
+     * @param reasons  list of events that triggered topology change
      */
-    public TopologyEvent(Type type, Topology topology) {
+    public TopologyEvent(Type type, Topology topology, List<Event> reasons) {
         super(type, topology);
+        this.reasons = reasons;
     }
 
     /**
@@ -33,10 +40,24 @@
      *
      * @param type     link event type
      * @param topology event topology subject
+     * @param reasons  list of events that triggered topology change
      * @param time     occurrence time
      */
-    public TopologyEvent(Type type, Topology topology, long time) {
+    public TopologyEvent(Type type, Topology topology, List<Event> reasons,
+                         long time) {
         super(type, topology, time);
+        this.reasons = reasons;
+    }
+
+
+    /**
+     * Returns the list of events that triggered the topology change.
+     *
+     * @return list of events responsible for change in topology; null if
+     * initial topology computation
+     */
+    public List<Event> reasons() {
+        return reasons;
     }
 
 }
diff --git a/core/api/src/main/java/org/onlab/onos/store/ClockProviderService.java b/core/api/src/main/java/org/onlab/onos/store/ClockProviderService.java
new file mode 100644
index 0000000..a5f81c7
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/store/ClockProviderService.java
@@ -0,0 +1,20 @@
+package org.onlab.onos.store;
+
+import org.onlab.onos.cluster.MastershipTerm;
+import org.onlab.onos.net.DeviceId;
+
+//TODO: Consider renaming to DeviceClockProviderService?
+/**
+* Interface for feeding term information to a logical clock service
+* that vends per device timestamps.
+*/
+public interface ClockProviderService {
+
+    /**
+     * Updates the mastership term for the specified deviceId.
+     *
+     * @param deviceId device identifier.
+     * @param term mastership term.
+     */
+    public void setMastershipTerm(DeviceId deviceId, MastershipTerm term);
+}
diff --git a/core/api/src/main/java/org/onlab/onos/store/ClockService.java b/core/api/src/main/java/org/onlab/onos/store/ClockService.java
index 2446ab7..20549e8 100644
--- a/core/api/src/main/java/org/onlab/onos/store/ClockService.java
+++ b/core/api/src/main/java/org/onlab/onos/store/ClockService.java
@@ -1,6 +1,5 @@
 package org.onlab.onos.store;
 
-import org.onlab.onos.cluster.MastershipTerm;
 import org.onlab.onos.net.DeviceId;
 
 // TODO: Consider renaming to DeviceClockService?
@@ -15,12 +14,4 @@
      * @return timestamp.
      */
     public Timestamp getTimestamp(DeviceId deviceId);
-
-    // TODO: Should this be here or separate as Admin service, etc.?
-    /**
-     * Updates the mastership term for the specified deviceId.
-     * @param deviceId device identifier.
-     * @param term mastership term.
-     */
-    public void setMastershipTerm(DeviceId deviceId, MastershipTerm term);
 }
diff --git a/core/api/src/main/java/org/onlab/onos/store/Timestamp.java b/core/api/src/main/java/org/onlab/onos/store/Timestamp.java
index b9d3648..b3caf85 100644
--- a/core/api/src/main/java/org/onlab/onos/store/Timestamp.java
+++ b/core/api/src/main/java/org/onlab/onos/store/Timestamp.java
@@ -2,7 +2,15 @@
 
 /**
  * Opaque version structure.
+ * <p>
+ * Classes implementing this interface must also implement
+ * {@link #hashCode()} and {@link #equals(Object)}.
  */
 public interface Timestamp extends Comparable<Timestamp> {
 
+    @Override
+    public abstract int hashCode();
+
+    @Override
+    public abstract boolean equals(Object obj);
 }
diff --git a/core/api/src/main/javadoc/org/onlab/onos/net/intent/doc-files/intent-states.png b/core/api/src/main/javadoc/org/onlab/onos/net/intent/doc-files/intent-states.png
new file mode 100644
index 0000000..9a3e1dc
--- /dev/null
+++ b/core/api/src/main/javadoc/org/onlab/onos/net/intent/doc-files/intent-states.png
Binary files differ
diff --git a/core/api/src/test/java/org/onlab/onos/VersionTest.java b/core/api/src/test/java/org/onlab/onos/VersionTest.java
new file mode 100644
index 0000000..e357f9d
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/VersionTest.java
@@ -0,0 +1,50 @@
+package org.onlab.onos;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+import static org.onlab.onos.Version.version;
+
+/**
+ * Tests of the version descriptor.
+ */
+public class VersionTest {
+
+    @Test
+    public void fromParts() {
+        Version v = version(1, 2, 3, "4321");
+        assertEquals("wrong major", 1, v.major());
+        assertEquals("wrong minor", 2, v.minor());
+        assertEquals("wrong patch", 3, v.patch());
+        assertEquals("wrong build", "4321", v.build());
+    }
+
+    @Test
+    public void fromString() {
+        Version v = version("1.2.3.4321");
+        assertEquals("wrong major", 1, v.major());
+        assertEquals("wrong minor", 2, v.minor());
+        assertEquals("wrong patch", 3, v.patch());
+        assertEquals("wrong build", "4321", v.build());
+    }
+
+    @Test
+    public void snapshot() {
+        Version v = version("1.2.3-SNAPSHOT");
+        assertEquals("wrong major", 1, v.major());
+        assertEquals("wrong minor", 2, v.minor());
+        assertEquals("wrong patch", 3, v.patch());
+        assertEquals("wrong build", "SNAPSHOT", v.build());
+    }
+
+    @Test
+    public void testEquals() {
+        new EqualsTester()
+                .addEqualityGroup(version("1.2.3.4321"), version(1, 2, 3, "4321"))
+                .addEqualityGroup(version("1.9.3.4321"), version(1, 9, 3, "4321"))
+                .addEqualityGroup(version("1.2.8.4321"), version(1, 2, 8, "4321"))
+                .addEqualityGroup(version("1.2.3.x"), version(1, 2, 3, "x"))
+                .testEquals();
+    }
+}
\ No newline at end of file
diff --git a/core/api/src/test/java/org/onlab/onos/net/ConnectPointTest.java b/core/api/src/test/java/org/onlab/onos/net/ConnectPointTest.java
index 6d3e793..6b9f777 100644
--- a/core/api/src/test/java/org/onlab/onos/net/ConnectPointTest.java
+++ b/core/api/src/test/java/org/onlab/onos/net/ConnectPointTest.java
@@ -33,4 +33,4 @@
                 .addEqualityGroup(new ConnectPoint(DID2, P1), new ConnectPoint(DID2, P1))
                 .testEquals();
     }
-}
\ No newline at end of file
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/DefaultAnnotationsTest.java b/core/api/src/test/java/org/onlab/onos/net/DefaultAnnotationsTest.java
index 274f4b8..9132126 100644
--- a/core/api/src/test/java/org/onlab/onos/net/DefaultAnnotationsTest.java
+++ b/core/api/src/test/java/org/onlab/onos/net/DefaultAnnotationsTest.java
@@ -36,6 +36,23 @@
     }
 
     @Test
+    public void union() {
+        annotations = builder().set("foo", "1").set("bar", "2").remove("buz").build();
+        assertEquals("incorrect keys", of("foo", "bar", "buz"), annotations.keys());
+
+        SparseAnnotations updates = builder().remove("foo").set("bar", "3").set("goo", "4").remove("fuzz").build();
+
+        SparseAnnotations result = DefaultAnnotations.union(annotations, updates);
+
+        assertTrue("remove instruction in original remains", result.isRemoved("buz"));
+        assertTrue("remove instruction in update remains", result.isRemoved("fuzz"));
+        assertEquals("incorrect keys", of("buz", "goo", "bar", "fuzz"), result.keys());
+        assertNull("incorrect value", result.value("foo"));
+        assertEquals("incorrect value", "3", result.value("bar"));
+        assertEquals("incorrect value", "4", result.value("goo"));
+    }
+
+    @Test
     public void merge() {
         annotations = builder().set("foo", "1").set("bar", "2").build();
         assertEquals("incorrect keys", of("foo", "bar"), annotations.keys());
@@ -65,4 +82,4 @@
         DefaultAnnotations.merge(null, null);
     }
 
-}
\ No newline at end of file
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/DefaultDeviceTest.java b/core/api/src/test/java/org/onlab/onos/net/DefaultDeviceTest.java
index b4018e3..329e128 100644
--- a/core/api/src/test/java/org/onlab/onos/net/DefaultDeviceTest.java
+++ b/core/api/src/test/java/org/onlab/onos/net/DefaultDeviceTest.java
@@ -58,7 +58,5 @@
         assertEquals("incorrect hw", HW, device.hwVersion());
         assertEquals("incorrect sw", SW, device.swVersion());
         assertEquals("incorrect serial", SN1, device.serialNumber());
-        assertEquals("incorrect serial", SN1, device.serialNumber());
     }
-
-}
\ No newline at end of file
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/DefaultEdgeLinkTest.java b/core/api/src/test/java/org/onlab/onos/net/DefaultEdgeLinkTest.java
index b3891f1..fd63797 100644
--- a/core/api/src/test/java/org/onlab/onos/net/DefaultEdgeLinkTest.java
+++ b/core/api/src/test/java/org/onlab/onos/net/DefaultEdgeLinkTest.java
@@ -5,6 +5,7 @@
 import org.onlab.onos.net.provider.ProviderId;
 
 import static org.junit.Assert.assertEquals;
+import static org.onlab.onos.net.DefaultEdgeLink.createEdgeLink;
 import static org.onlab.onos.net.DefaultLinkTest.cp;
 import static org.onlab.onos.net.DeviceId.deviceId;
 import static org.onlab.onos.net.HostId.hostId;
@@ -55,4 +56,24 @@
         assertEquals("incorrect time", 123L, link.hostLocation().time());
     }
 
+    @Test
+    public void phantomIngress() {
+        HostLocation hostLocation = new HostLocation(DID1, P1, 123L);
+        EdgeLink link = createEdgeLink(hostLocation, true);
+        assertEquals("incorrect dst", hostLocation, link.dst());
+        assertEquals("incorrect type", Link.Type.EDGE, link.type());
+        assertEquals("incorrect connect point", hostLocation, link.hostLocation());
+        assertEquals("incorrect time", 123L, link.hostLocation().time());
+    }
+
+    @Test
+    public void phantomEgress() {
+        ConnectPoint hostLocation = new ConnectPoint(DID1, P1);
+        EdgeLink link = createEdgeLink(hostLocation, false);
+        assertEquals("incorrect src", hostLocation, link.src());
+        assertEquals("incorrect type", Link.Type.EDGE, link.type());
+        assertEquals("incorrect connect point", hostLocation, link.hostLocation());
+        assertEquals("incorrect time", 0L, link.hostLocation().time());
+    }
+
 }
diff --git a/core/api/src/test/java/org/onlab/onos/net/device/DeviceEventTest.java b/core/api/src/test/java/org/onlab/onos/net/device/DeviceEventTest.java
index df19365..9c45b96 100644
--- a/core/api/src/test/java/org/onlab/onos/net/device/DeviceEventTest.java
+++ b/core/api/src/test/java/org/onlab/onos/net/device/DeviceEventTest.java
@@ -39,7 +39,7 @@
         Device device = createDevice();
         Port port = new DefaultPort(device, PortNumber.portNumber(123), true);
         long before = System.currentTimeMillis();
-        DeviceEvent event = new DeviceEvent(DeviceEvent.Type.DEVICE_ADDED, device);
+        DeviceEvent event = new DeviceEvent(DeviceEvent.Type.DEVICE_ADDED, device, port);
         long after = System.currentTimeMillis();
         validateEvent(event, DeviceEvent.Type.DEVICE_ADDED, device, before, after);
     }
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/ConnectivityIntentTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/ConnectivityIntentTest.java
index fb1efee..10a0069 100644
--- a/core/api/src/test/java/org/onlab/onos/net/intent/ConnectivityIntentTest.java
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/ConnectivityIntentTest.java
@@ -16,8 +16,8 @@
 public abstract class ConnectivityIntentTest extends IntentTest {
 
     public static final IntentId IID = new IntentId(123);
-    public static final TrafficSelector MATCH = (new DefaultTrafficSelector.Builder()).build();
-    public static final TrafficTreatment NOP = (new DefaultTrafficTreatment.Builder()).build();
+    public static final TrafficSelector MATCH = DefaultTrafficSelector.builder().build();
+    public static final TrafficTreatment NOP = DefaultTrafficTreatment.builder().build();
 
     public static final ConnectPoint P1 = new ConnectPoint(DeviceId.deviceId("111"), PortNumber.portNumber(0x1));
     public static final ConnectPoint P2 = new ConnectPoint(DeviceId.deviceId("222"), PortNumber.portNumber(0x2));
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/FakeIntentManager.java b/core/api/src/test/java/org/onlab/onos/net/intent/FakeIntentManager.java
index df46ec5..58c5a9c 100644
--- a/core/api/src/test/java/org/onlab/onos/net/intent/FakeIntentManager.java
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/FakeIntentManager.java
@@ -11,19 +11,19 @@
 import java.util.concurrent.Executors;
 
 /**
- * Fake implementation of the intent service to assist in developing tests
- * of the interface contract.
+ * Fake implementation of the intent service to assist in developing tests of
+ * the interface contract.
  */
 public class FakeIntentManager implements TestableIntentService {
 
     private final Map<IntentId, Intent> intents = new HashMap<>();
     private final Map<IntentId, IntentState> intentStates = new HashMap<>();
     private final Map<IntentId, List<InstallableIntent>> installables = new HashMap<>();
-    private final Set<IntentEventListener> listeners = new HashSet<>();
+    private final Set<IntentListener> listeners = new HashSet<>();
 
     private final Map<Class<? extends Intent>, IntentCompiler<? extends Intent>> compilers = new HashMap<>();
-    private final Map<Class<? extends InstallableIntent>,
-            IntentInstaller<? extends InstallableIntent>> installers = new HashMap<>();
+    private final Map<Class<? extends InstallableIntent>, IntentInstaller<? extends InstallableIntent>> installers
+        = new HashMap<>();
 
     private final ExecutorService executor = Executors.newSingleThreadExecutor();
     private final List<IntentException> exceptions = new ArrayList<>();
@@ -40,8 +40,7 @@
             @Override
             public void run() {
                 try {
-                    List<InstallableIntent> installable = compileIntent(intent);
-                    installIntents(intent, installable);
+                    executeCompilingPhase(intent);
                 } catch (IntentException e) {
                     exceptions.add(e);
                 }
@@ -55,8 +54,8 @@
             @Override
             public void run() {
                 try {
-                    List<InstallableIntent> installable = getInstallable(intent.getId());
-                    uninstallIntents(intent, installable);
+                    List<InstallableIntent> installable = getInstallable(intent.id());
+                    executeWithdrawingPhase(intent, installable);
                 } catch (IntentException e) {
                     exceptions.add(e);
                 }
@@ -76,61 +75,68 @@
 
     private <T extends InstallableIntent> IntentInstaller<T> getInstaller(T intent) {
         @SuppressWarnings("unchecked")
-        IntentInstaller<T> installer = (IntentInstaller<T>) installers.get(intent.getClass());
+        IntentInstaller<T> installer = (IntentInstaller<T>) installers.get(intent
+                .getClass());
         if (installer == null) {
             throw new IntentException("no installer for class " + intent.getClass());
         }
         return installer;
     }
 
-    private <T extends Intent> List<InstallableIntent> compileIntent(T intent) {
+    private <T extends Intent> void executeCompilingPhase(T intent) {
+        setState(intent, IntentState.COMPILING);
         try {
             // For the fake, we compile using a single level pass
             List<InstallableIntent> installable = new ArrayList<>();
             for (Intent compiled : getCompiler(intent).compile(intent)) {
                 installable.add((InstallableIntent) compiled);
             }
-            setState(intent, IntentState.COMPILED);
-            return installable;
+            executeInstallingPhase(intent, installable);
+
         } catch (IntentException e) {
             setState(intent, IntentState.FAILED);
-            throw e;
+            dispatch(new IntentEvent(IntentEvent.Type.FAILED, intent));
         }
     }
 
-    private void installIntents(Intent intent, List<InstallableIntent> installable) {
+    private void executeInstallingPhase(Intent intent,
+                                        List<InstallableIntent> installable) {
+        setState(intent, IntentState.INSTALLING);
         try {
             for (InstallableIntent ii : installable) {
                 registerSubclassInstallerIfNeeded(ii);
                 getInstaller(ii).install(ii);
             }
             setState(intent, IntentState.INSTALLED);
-            putInstallable(intent.getId(), installable);
+            putInstallable(intent.id(), installable);
+            dispatch(new IntentEvent(IntentEvent.Type.INSTALLED, intent));
+
         } catch (IntentException e) {
             setState(intent, IntentState.FAILED);
-            throw e;
+            dispatch(new IntentEvent(IntentEvent.Type.FAILED, intent));
         }
     }
 
-    private void uninstallIntents(Intent intent, List<InstallableIntent> installable) {
+    private void executeWithdrawingPhase(Intent intent,
+                                         List<InstallableIntent> installable) {
+        setState(intent, IntentState.WITHDRAWING);
         try {
             for (InstallableIntent ii : installable) {
                 getInstaller(ii).uninstall(ii);
             }
+            removeInstallable(intent.id());
             setState(intent, IntentState.WITHDRAWN);
-            removeInstallable(intent.getId());
+            dispatch(new IntentEvent(IntentEvent.Type.WITHDRAWN, intent));
         } catch (IntentException e) {
+            // FIXME: Rework this to always go from WITHDRAWING to WITHDRAWN!
             setState(intent, IntentState.FAILED);
-            throw e;
+            dispatch(new IntentEvent(IntentEvent.Type.FAILED, intent));
         }
     }
 
-
     // Sets the internal state for the given intent and dispatches an event
     private void setState(Intent intent, IntentState state) {
-        IntentState previous = intentStates.get(intent.getId());
-        intentStates.put(intent.getId(), state);
-        dispatch(new IntentEvent(intent, state, previous, System.currentTimeMillis()));
+        intentStates.put(intent.id(), state);
     }
 
     private void putInstallable(IntentId id, List<InstallableIntent> installable) {
@@ -152,15 +158,15 @@
 
     @Override
     public void submit(Intent intent) {
-        intents.put(intent.getId(), intent);
+        intents.put(intent.id(), intent);
         setState(intent, IntentState.SUBMITTED);
+        dispatch(new IntentEvent(IntentEvent.Type.SUBMITTED, intent));
         executeSubmit(intent);
     }
 
     @Override
     public void withdraw(Intent intent) {
-        intents.remove(intent.getId());
-        setState(intent, IntentState.WITHDRAWING);
+        intents.remove(intent.id());
         executeWithdraw(intent);
     }
 
@@ -175,6 +181,11 @@
     }
 
     @Override
+    public long getIntentCount() {
+        return intents.size();
+    }
+
+    @Override
     public Intent getIntent(IntentId id) {
         return intents.get(id);
     }
@@ -185,23 +196,24 @@
     }
 
     @Override
-    public void addListener(IntentEventListener listener) {
+    public void addListener(IntentListener listener) {
         listeners.add(listener);
     }
 
     @Override
-    public void removeListener(IntentEventListener listener) {
+    public void removeListener(IntentListener listener) {
         listeners.remove(listener);
     }
 
     private void dispatch(IntentEvent event) {
-        for (IntentEventListener listener : listeners) {
+        for (IntentListener listener : listeners) {
             listener.event(event);
         }
     }
 
     @Override
-    public <T extends Intent> void registerCompiler(Class<T> cls, IntentCompiler<T> compiler) {
+    public <T extends Intent> void registerCompiler(Class<T> cls,
+            IntentCompiler<T> compiler) {
         compilers.put(cls, compiler);
     }
 
@@ -216,7 +228,8 @@
     }
 
     @Override
-    public <T extends InstallableIntent> void registerInstaller(Class<T> cls, IntentInstaller<T> installer) {
+    public <T extends InstallableIntent> void registerInstaller(Class<T> cls,
+            IntentInstaller<T> installer) {
         installers.put(cls, installer);
     }
 
@@ -227,7 +240,7 @@
 
     @Override
     public Map<Class<? extends InstallableIntent>,
-            IntentInstaller<? extends InstallableIntent>> getInstallers() {
+    IntentInstaller<? extends InstallableIntent>> getInstallers() {
         return Collections.unmodifiableMap(installers);
     }
 
@@ -252,7 +265,8 @@
         if (!installers.containsKey(intent.getClass())) {
             Class<?> cls = intent.getClass();
             while (cls != Object.class) {
-                // As long as we're within the InstallableIntent class descendants
+                // As long as we're within the InstallableIntent class
+                // descendants
                 if (InstallableIntent.class.isAssignableFrom(cls)) {
                     IntentInstaller<?> installer = installers.get(cls);
                     if (installer != null) {
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/IntentServiceTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/IntentServiceTest.java
index c7682b1..7eb0e19 100644
--- a/core/api/src/test/java/org/onlab/onos/net/intent/IntentServiceTest.java
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/IntentServiceTest.java
@@ -10,11 +10,9 @@
 import java.util.Iterator;
 import java.util.List;
 
-import static org.onlab.onos.net.intent.IntentState.*;
 import static org.junit.Assert.*;
+import static org.onlab.onos.net.intent.IntentEvent.Type.*;
 
-// TODO: consider make it categorized as integration test when it become
-//   slow test or fragile test
 /**
  * Suite of tests for the intent service contract.
  */
@@ -51,7 +49,7 @@
     @Test
     public void basics() {
         // Make sure there are no intents
-        assertEquals("incorrect intent count", 0, service.getIntents().size());
+        assertEquals("incorrect intent count", 0, service.getIntentCount());
 
         // Register a compiler and an installer both setup for success.
         service.registerCompiler(TestIntent.class, new TestCompiler(new TestInstallableIntent(INSTALLABLE_IID)));
@@ -64,17 +62,16 @@
         TestTools.assertAfter(GRACE_MS, new Runnable() {
             @Override
             public void run() {
-                assertEquals("incorrect intent state", INSTALLED,
-                             service.getIntentState(intent.getId()));
+                assertEquals("incorrect intent state", IntentState.INSTALLED,
+                             service.getIntentState(intent.id()));
             }
         });
 
         // Make sure that all expected events have been emitted
-        validateEvents(intent, SUBMITTED, COMPILED, INSTALLED);
+        validateEvents(intent, SUBMITTED, INSTALLED);
 
         // Make sure there is just one intent (and is ours)
-        assertEquals("incorrect intent count", 1, service.getIntents().size());
-        assertEquals("incorrect intent", intent, service.getIntent(intent.getId()));
+        assertEquals("incorrect intent count", 1, service.getIntentCount());
 
         // Reset the listener events
         listener.events.clear();
@@ -86,19 +83,19 @@
         TestTools.assertAfter(GRACE_MS, new Runnable() {
             @Override
             public void run() {
-                assertEquals("incorrect intent state", WITHDRAWN,
-                             service.getIntentState(intent.getId()));
+                assertEquals("incorrect intent state", IntentState.WITHDRAWN,
+                             service.getIntentState(intent.id()));
             }
         });
 
         // Make sure that all expected events have been emitted
-        validateEvents(intent, WITHDRAWING, WITHDRAWN);
+        validateEvents(intent, WITHDRAWN);
 
         // TODO: discuss what is the fate of intents after they have been withdrawn
         // Make sure that the intent is no longer in the system
 //        assertEquals("incorrect intent count", 0, service.getIntents().size());
-//        assertNull("intent should not be found", service.getIntent(intent.getId()));
-//        assertNull("intent state should not be found", service.getIntentState(intent.getId()));
+//        assertNull("intent should not be found", service.getIntent(intent.id()));
+//        assertNull("intent state should not be found", service.getIntentState(intent.id()));
     }
 
     @Test
@@ -114,8 +111,8 @@
         TestTools.assertAfter(GRACE_MS, new Runnable() {
             @Override
             public void run() {
-                assertEquals("incorrect intent state", FAILED,
-                             service.getIntentState(intent.getId()));
+                assertEquals("incorrect intent state", IntentState.FAILED,
+                             service.getIntentState(intent.id()));
             }
         });
 
@@ -137,13 +134,13 @@
         TestTools.assertAfter(GRACE_MS, new Runnable() {
             @Override
             public void run() {
-                assertEquals("incorrect intent state", FAILED,
-                             service.getIntentState(intent.getId()));
+                assertEquals("incorrect intent state", IntentState.FAILED,
+                             service.getIntentState(intent.id()));
             }
         });
 
         // Make sure that all expected events have been emitted
-        validateEvents(intent, SUBMITTED, COMPILED, FAILED);
+        validateEvents(intent, SUBMITTED, FAILED);
     }
 
     /**
@@ -152,23 +149,23 @@
      * considered.
      *
      * @param intent intent subject
-     * @param states list of states for which events are expected
+     * @param types  list of event types for which events are expected
      */
-    protected void validateEvents(Intent intent, IntentState... states) {
+    protected void validateEvents(Intent intent, IntentEvent.Type... types) {
         Iterator<IntentEvent> events = listener.events.iterator();
-        for (IntentState state : states) {
+        for (IntentEvent.Type type : types) {
             IntentEvent event = events.hasNext() ? events.next() : null;
             if (event == null) {
-                fail("expected event not found: " + state);
-            } else if (intent.equals(event.getIntent())) {
-                assertEquals("incorrect state", state, event.getState());
+                fail("expected event not found: " + type);
+            } else if (intent.equals(event.subject())) {
+                assertEquals("incorrect state", type, event.type());
             }
         }
 
         // Remainder of events should not apply to this intent; make sure.
         while (events.hasNext()) {
             assertFalse("unexpected event for intent",
-                        intent.equals(events.next().getIntent()));
+                        intent.equals(events.next().subject()));
         }
     }
 
@@ -229,8 +226,8 @@
         TestTools.assertAfter(GRACE_MS, new Runnable() {
             @Override
             public void run() {
-                assertEquals("incorrect intent state", INSTALLED,
-                             service.getIntentState(intent.getId()));
+                assertEquals("incorrect intent state", IntentState.INSTALLED,
+                             service.getIntentState(intent.id()));
             }
         });
 
@@ -250,7 +247,7 @@
 
 
     // Fixture to track emitted intent events
-    protected class TestListener implements IntentEventListener {
+    protected class TestListener implements IntentListener {
         final List<IntentEvent> events = new ArrayList<>();
 
         @Override
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/MultiPointToSinglePointIntentTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/MultiPointToSinglePointIntentTest.java
index d971ba2..66d294a 100644
--- a/core/api/src/test/java/org/onlab/onos/net/intent/MultiPointToSinglePointIntentTest.java
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/MultiPointToSinglePointIntentTest.java
@@ -12,10 +12,10 @@
     @Test
     public void basics() {
         MultiPointToSinglePointIntent intent = createOne();
-        assertEquals("incorrect id", IID, intent.getId());
-        assertEquals("incorrect match", MATCH, intent.getTrafficSelector());
-        assertEquals("incorrect ingress", PS1, intent.getIngressPorts());
-        assertEquals("incorrect egress", P2, intent.getEgressPort());
+        assertEquals("incorrect id", IID, intent.id());
+        assertEquals("incorrect match", MATCH, intent.selector());
+        assertEquals("incorrect ingress", PS1, intent.ingressPoints());
+        assertEquals("incorrect egress", P2, intent.egressPoint());
     }
 
     @Override
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/PathIntentTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/PathIntentTest.java
index bd8dc08..7c15c37 100644
--- a/core/api/src/test/java/org/onlab/onos/net/intent/PathIntentTest.java
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/PathIntentTest.java
@@ -16,12 +16,12 @@
     @Test
     public void basics() {
         PathIntent intent = createOne();
-        assertEquals("incorrect id", IID, intent.getId());
-        assertEquals("incorrect match", MATCH, intent.getTrafficSelector());
-        assertEquals("incorrect action", NOP, intent.getTrafficTreatment());
-        assertEquals("incorrect ingress", P1, intent.getIngressPort());
-        assertEquals("incorrect egress", P2, intent.getEgressPort());
-        assertEquals("incorrect path", PATH1, intent.getPath());
+        assertEquals("incorrect id", IID, intent.id());
+        assertEquals("incorrect match", MATCH, intent.selector());
+        assertEquals("incorrect action", NOP, intent.treatment());
+        assertEquals("incorrect ingress", P1, intent.ingressPoint());
+        assertEquals("incorrect egress", P2, intent.egressPoint());
+        assertEquals("incorrect path", PATH1, intent.path());
     }
 
     @Override
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/PointToPointIntentTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/PointToPointIntentTest.java
index 426a3d9..e0c5562 100644
--- a/core/api/src/test/java/org/onlab/onos/net/intent/PointToPointIntentTest.java
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/PointToPointIntentTest.java
@@ -12,10 +12,10 @@
     @Test
     public void basics() {
         PointToPointIntent intent = createOne();
-        assertEquals("incorrect id", IID, intent.getId());
-        assertEquals("incorrect match", MATCH, intent.getTrafficSelector());
-        assertEquals("incorrect ingress", P1, intent.getIngressPort());
-        assertEquals("incorrect egress", P2, intent.getEgressPort());
+        assertEquals("incorrect id", IID, intent.id());
+        assertEquals("incorrect match", MATCH, intent.selector());
+        assertEquals("incorrect ingress", P1, intent.ingressPoint());
+        assertEquals("incorrect egress", P2, intent.egressPoint());
     }
 
     @Override
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/SinglePointToMultiPointIntentTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/SinglePointToMultiPointIntentTest.java
index 0561a87..64c9292 100644
--- a/core/api/src/test/java/org/onlab/onos/net/intent/SinglePointToMultiPointIntentTest.java
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/SinglePointToMultiPointIntentTest.java
@@ -12,10 +12,10 @@
     @Test
     public void basics() {
         SinglePointToMultiPointIntent intent = createOne();
-        assertEquals("incorrect id", IID, intent.getId());
-        assertEquals("incorrect match", MATCH, intent.getTrafficSelector());
-        assertEquals("incorrect ingress", P1, intent.getIngressPort());
-        assertEquals("incorrect egress", PS2, intent.getEgressPorts());
+        assertEquals("incorrect id", IID, intent.id());
+        assertEquals("incorrect match", MATCH, intent.selector());
+        assertEquals("incorrect ingress", P1, intent.ingressPoint());
+        assertEquals("incorrect egress", PS2, intent.egressPoints());
     }
 
     @Override
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/TestInstallableIntent.java b/core/api/src/test/java/org/onlab/onos/net/intent/TestInstallableIntent.java
index a6ce52e..3265925 100644
--- a/core/api/src/test/java/org/onlab/onos/net/intent/TestInstallableIntent.java
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/TestInstallableIntent.java
@@ -1,6 +1,10 @@
 package org.onlab.onos.net.intent;
 //TODO is this the right package?
 
+import org.onlab.onos.net.Link;
+
+import java.util.Collection;
+
 /**
  * An installable intent used in the unit test.
  *
@@ -25,4 +29,8 @@
         super();
     }
 
+    @Override
+    public Collection<Link> requiredLinks() {
+        return null;
+    }
 }
diff --git a/core/api/src/test/java/org/onlab/onos/net/topology/DefaultGraphDescriptionTest.java b/core/api/src/test/java/org/onlab/onos/net/topology/DefaultGraphDescriptionTest.java
index a968abf..5f7d47b 100644
--- a/core/api/src/test/java/org/onlab/onos/net/topology/DefaultGraphDescriptionTest.java
+++ b/core/api/src/test/java/org/onlab/onos/net/topology/DefaultGraphDescriptionTest.java
@@ -37,5 +37,4 @@
         new DefaultGraphDescription(4321L, ImmutableSet.of(DEV1, DEV3),
                                     ImmutableSet.of(L1, L2));
     }
-
-}
\ No newline at end of file
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/topology/DefaultTopologyEdgeTest.java b/core/api/src/test/java/org/onlab/onos/net/topology/DefaultTopologyEdgeTest.java
index 6c3c112..5d64c83 100644
--- a/core/api/src/test/java/org/onlab/onos/net/topology/DefaultTopologyEdgeTest.java
+++ b/core/api/src/test/java/org/onlab/onos/net/topology/DefaultTopologyEdgeTest.java
@@ -50,5 +50,4 @@
                                   new DefaultTopologyEdge(V2, V1, L2))
                 .testEquals();
     }
-
-}
\ No newline at end of file
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/topology/DefaultTopologyVertexTest.java b/core/api/src/test/java/org/onlab/onos/net/topology/DefaultTopologyVertexTest.java
index 1284624..7210ff3 100644
--- a/core/api/src/test/java/org/onlab/onos/net/topology/DefaultTopologyVertexTest.java
+++ b/core/api/src/test/java/org/onlab/onos/net/topology/DefaultTopologyVertexTest.java
@@ -26,5 +26,4 @@
                 .addEqualityGroup(new DefaultTopologyVertex(D2),
                                   new DefaultTopologyVertex(D2)).testEquals();
     }
-
-}
\ No newline at end of file
+}
diff --git a/core/net/pom.xml b/core/net/pom.xml
index c075147..6518068 100644
--- a/core/net/pom.xml
+++ b/core/net/pom.xml
@@ -36,6 +36,12 @@
             <scope>test</scope>
         </dependency>
 
+	<dependency>
+	    <groupId>org.easymock</groupId>
+	    <artifactId>easymock</artifactId>
+	    <scope>test</scope>
+	</dependency>
+
         <!-- TODO Consider removing store dependency.
               Currently required for DistributedDeviceManagerTest. -->
         <dependency>
diff --git a/core/net/src/main/java/org/onlab/onos/cluster/impl/CoreManager.java b/core/net/src/main/java/org/onlab/onos/cluster/impl/CoreManager.java
new file mode 100644
index 0000000..4b1191f
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/cluster/impl/CoreManager.java
@@ -0,0 +1,38 @@
+package org.onlab.onos.cluster.impl;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.onos.CoreService;
+import org.onlab.onos.Version;
+import org.onlab.util.Tools;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Core service implementation.
+ */
+@Component
+@Service
+public class CoreManager implements CoreService {
+
+    private static final File VERSION_FILE = new File("../VERSION");
+    private static Version version = Version.version("1.0.0-SNAPSHOT");
+
+    // TODO: work in progress
+
+    @Activate
+    public void activate() {
+        List<String> versionLines = Tools.slurp(VERSION_FILE);
+        if (versionLines != null && !versionLines.isEmpty()) {
+            version = Version.version(versionLines.get(0));
+        }
+    }
+
+    @Override
+    public Version version() {
+        return version;
+    }
+
+}
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 5400fb0..a8d63c1 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
@@ -18,6 +18,7 @@
 import org.onlab.onos.cluster.MastershipService;
 import org.onlab.onos.cluster.MastershipTermService;
 import org.onlab.onos.cluster.MastershipTerm;
+import org.onlab.onos.cluster.NodeId;
 import org.onlab.onos.event.AbstractListenerRegistry;
 import org.onlab.onos.event.EventDeliveryService;
 import org.onlab.onos.net.Device;
@@ -38,7 +39,7 @@
 import org.onlab.onos.net.device.PortDescription;
 import org.onlab.onos.net.provider.AbstractProviderRegistry;
 import org.onlab.onos.net.provider.AbstractProviderService;
-import org.onlab.onos.store.ClockService;
+import org.onlab.onos.store.ClockProviderService;
 import org.slf4j.Logger;
 
 /**
@@ -80,7 +81,7 @@
     protected MastershipTermService termService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected ClockService clockService;
+    protected ClockProviderService clockProviderService;
 
     @Activate
     public void activate() {
@@ -144,6 +145,10 @@
     private void applyRole(DeviceId deviceId, MastershipRole newRole) {
         if (newRole.equals(MastershipRole.NONE)) {
             Device device = store.getDevice(deviceId);
+            // FIXME: Device might not be there yet. (eventual consistent)
+            if (device == null) {
+                return;
+            }
             DeviceProvider provider = getProvider(device.providerId());
             if (provider != null) {
                 provider.roleChanged(device, newRole);
@@ -193,13 +198,50 @@
             checkNotNull(deviceId, DEVICE_ID_NULL);
             checkNotNull(deviceDescription, DEVICE_DESCRIPTION_NULL);
             checkValidity();
+
+            log.info("Device {} connected", deviceId);
+            // check my Role
+            MastershipRole role = mastershipService.requestRoleFor(deviceId);
+
+            if (role != MastershipRole.MASTER) {
+                // TODO: Do we need to explicitly tell the Provider that
+                // this instance is no longer the MASTER? probably not
+                return;
+            }
+
+            MastershipTerm term = mastershipService.requestTermService()
+                    .getMastershipTerm(deviceId);
+            if (!term.master().equals(clusterService.getLocalNode().id())) {
+                // lost mastership after requestRole told this instance was MASTER.
+                return;
+            }
+            // tell clock provider if this instance is the master
+            clockProviderService.setMastershipTerm(deviceId, term);
+
             DeviceEvent event = store.createOrUpdateDevice(provider().id(),
                     deviceId, deviceDescription);
 
+            // If there was a change of any kind, tell the provider
+            // that this instance is the master.
+            // Note: event can be null, if mastership was lost between
+            // roleRequest and store update calls.
             if (event != null) {
-                log.info("Device {} connected", deviceId);
-                provider().roleChanged(event.subject(),
-                        mastershipService.requestRoleFor(deviceId));
+                // TODO: Check switch reconnected case. Is it assured that
+                //       event will never be null?
+                //       Could there be a situation MastershipService told this
+                //       instance is the new Master, but
+                //       event returned from the store is null?
+
+                // TODO: Confirm: Mastership could be lost after requestRole
+                //       and createOrUpdateDevice call.
+                //       In that case STANDBY node can
+                //       claim itself to be master against the Device.
+                //       Will the Node, chosen by the MastershipService, retry
+                //       to get the MASTER role when that happen?
+
+                // FIXME: 1st argument should be deviceId, to allow setting
+                //        certain roles even if the store returned null.
+                provider().roleChanged(event.subject(), role);
                 post(event);
             }
         }
@@ -208,6 +250,15 @@
         public void deviceDisconnected(DeviceId deviceId) {
             checkNotNull(deviceId, DEVICE_ID_NULL);
             checkValidity();
+
+            // FIXME: only the MASTER should be marking off-line in normal cases,
+            // but if I was the last STANDBY connection, etc. and no one else
+            // was there to mark the device offline, this instance may need to
+            // temporarily request for Master Role and mark offline.
+            if (!mastershipService.getLocalRole(deviceId).equals(MastershipRole.MASTER)) {
+                log.debug("Device {} disconnected, but I am not the master", deviceId);
+                return;
+            }
             DeviceEvent event = store.markOffline(deviceId);
             //we're no longer capable of being master or a candidate.
             mastershipService.relinquishMastership(deviceId);
@@ -253,6 +304,9 @@
             // FIXME: implement response to this notification
             log.warn("Failed to assert role [{}] onto Device {}", role,
                     deviceId);
+            if (role == MastershipRole.MASTER) {
+                mastershipService.relinquishMastership(deviceId);
+            }
         }
     }
 
@@ -268,11 +322,17 @@
 
         @Override
         public void event(MastershipEvent event) {
-            DeviceId did = event.subject();
+            final DeviceId did = event.subject();
             if (isAvailable(did)) {
-                if (event.master().equals(clusterService.getLocalNode().id())) {
+                final NodeId myNodeId = clusterService.getLocalNode().id();
+
+                if (myNodeId.equals(event.master())) {
                     MastershipTerm term = termService.getMastershipTerm(did);
-                    clockService.setMastershipTerm(did, term);
+
+                    if (term.master().equals(myNodeId)) {
+                        // only set the new term if I am the master
+                        clockProviderService.setMastershipTerm(did, term);
+                    }
                     applyRole(did, MastershipRole.MASTER);
                 } else {
                     applyRole(did, MastershipRole.STANDBY);
diff --git a/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java b/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java
index 00619b3..a9eddd8 100644
--- a/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java
@@ -5,9 +5,10 @@
 
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
@@ -21,7 +22,11 @@
 import org.onlab.onos.net.Device;
 import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.net.device.DeviceService;
+import org.onlab.onos.net.flow.CompletedBatchOperation;
+import org.onlab.onos.net.flow.FlowEntry;
 import org.onlab.onos.net.flow.FlowRule;
+import org.onlab.onos.net.flow.FlowRuleBatchEntry;
+import org.onlab.onos.net.flow.FlowRuleBatchOperation;
 import org.onlab.onos.net.flow.FlowRuleEvent;
 import org.onlab.onos.net.flow.FlowRuleListener;
 import org.onlab.onos.net.flow.FlowRuleProvider;
@@ -34,7 +39,9 @@
 import org.onlab.onos.net.provider.AbstractProviderService;
 import org.slf4j.Logger;
 
+import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
 
 /**
  * Provides implementation of the flow NB &amp; SB APIs.
@@ -42,14 +49,14 @@
 @Component(immediate = true)
 @Service
 public class FlowRuleManager
-extends AbstractProviderRegistry<FlowRuleProvider, FlowRuleProviderService>
-implements FlowRuleService, FlowRuleProviderRegistry {
+        extends AbstractProviderRegistry<FlowRuleProvider, FlowRuleProviderService>
+        implements FlowRuleService, FlowRuleProviderRegistry {
 
     public static final String FLOW_RULE_NULL = "FlowRule cannot be null";
     private final Logger log = getLogger(getClass());
 
     private final AbstractListenerRegistry<FlowRuleEvent, FlowRuleListener>
-    listenerRegistry = new AbstractListenerRegistry<>();
+            listenerRegistry = new AbstractListenerRegistry<>();
 
     private final FlowRuleStoreDelegate delegate = new InternalStoreDelegate();
 
@@ -62,8 +69,6 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected DeviceService deviceService;
 
-    private final Map<FlowRule, AtomicInteger> deadRounds = new ConcurrentHashMap<>();
-
     @Activate
     public void activate() {
         store.setDelegate(delegate);
@@ -79,7 +84,12 @@
     }
 
     @Override
-    public Iterable<FlowRule> getFlowEntries(DeviceId deviceId) {
+    public int getFlowRuleCount() {
+        return store.getFlowRuleCount();
+    }
+
+    @Override
+    public Iterable<FlowEntry> getFlowEntries(DeviceId deviceId) {
         return store.getFlowEntries(deviceId);
     }
 
@@ -89,7 +99,6 @@
             FlowRule f = flowRules[i];
             final Device device = deviceService.getDevice(f.deviceId());
             final FlowRuleProvider frp = getProvider(device.providerId());
-            deadRounds.put(f, new AtomicInteger(0));
             store.storeFlowRule(f);
             frp.applyFlowRule(f);
         }
@@ -103,16 +112,17 @@
         for (int i = 0; i < flowRules.length; i++) {
             f = flowRules[i];
             device = deviceService.getDevice(f.deviceId());
-            frp = getProvider(device.providerId());
-            deadRounds.remove(f);
             store.deleteFlowRule(f);
-            frp.removeFlowRule(f);
+            if (device != null) {
+                frp = getProvider(device.providerId());
+                frp.removeFlowRule(f);
+            }
         }
     }
 
     @Override
     public void removeFlowRulesById(ApplicationId id) {
-        Iterable<FlowRule> rules =  getFlowRulesById(id);
+        Iterable<FlowRule> rules = getFlowRulesById(id);
         FlowRuleProvider frp;
         Device device;
 
@@ -126,7 +136,39 @@
 
     @Override
     public Iterable<FlowRule> getFlowRulesById(ApplicationId id) {
-        return store.getFlowEntriesByAppId(id);
+        return store.getFlowRulesByAppId(id);
+    }
+
+    @Override
+    public Future<CompletedBatchOperation> applyBatch(
+            FlowRuleBatchOperation batch) {
+        Multimap<FlowRuleProvider, FlowRuleBatchEntry> batches =
+                ArrayListMultimap.create();
+        List<Future<Void>> futures = Lists.newArrayList();
+        for (FlowRuleBatchEntry fbe : batch.getOperations()) {
+            final FlowRule f = fbe.getTarget();
+            final Device device = deviceService.getDevice(f.deviceId());
+            final FlowRuleProvider frp = getProvider(device.providerId());
+            batches.put(frp, fbe);
+            switch (fbe.getOperator()) {
+                case ADD:
+                    store.storeFlowRule(f);
+                    break;
+                case REMOVE:
+                    store.deleteFlowRule(f);
+                    break;
+                case MODIFY:
+                default:
+                    log.error("Batch operation type {} unsupported.", fbe.getOperator());
+            }
+        }
+        for (FlowRuleProvider provider : batches.keySet()) {
+            FlowRuleBatchOperation b =
+                    new FlowRuleBatchOperation(batches.get(provider));
+            Future<Void> future = provider.executeBatch(b);
+            futures.add(future);
+        }
+        return new FlowRuleBatchFuture(futures);
     }
 
     @Override
@@ -146,63 +188,63 @@
     }
 
     private class InternalFlowRuleProviderService
-    extends AbstractProviderService<FlowRuleProvider>
-    implements FlowRuleProviderService {
+            extends AbstractProviderService<FlowRuleProvider>
+            implements FlowRuleProviderService {
 
         protected InternalFlowRuleProviderService(FlowRuleProvider provider) {
             super(provider);
         }
 
         @Override
-        public void flowRemoved(FlowRule flowRule) {
-            checkNotNull(flowRule, FLOW_RULE_NULL);
+        public void flowRemoved(FlowEntry flowEntry) {
+            checkNotNull(flowEntry, FLOW_RULE_NULL);
             checkValidity();
-            FlowRule stored = store.getFlowRule(flowRule);
+            FlowEntry stored = store.getFlowEntry(flowEntry);
             if (stored == null) {
-                log.debug("Rule already evicted from store: {}", flowRule);
+                log.info("Rule already evicted from store: {}", flowEntry);
                 return;
             }
-            Device device = deviceService.getDevice(flowRule.deviceId());
+            Device device = deviceService.getDevice(flowEntry.deviceId());
             FlowRuleProvider frp = getProvider(device.providerId());
             FlowRuleEvent event = null;
             switch (stored.state()) {
-            case ADDED:
-            case PENDING_ADD:
+                case ADDED:
+                case PENDING_ADD:
                     frp.applyFlowRule(stored);
-                break;
-            case PENDING_REMOVE:
-            case REMOVED:
-                event = store.removeFlowRule(flowRule);
-                break;
-            default:
-                break;
+                    break;
+                case PENDING_REMOVE:
+                case REMOVED:
+                    event = store.removeFlowRule(stored);
+                    break;
+                default:
+                    break;
 
             }
             if (event != null) {
-                log.debug("Flow {} removed", flowRule);
+                log.debug("Flow {} removed", flowEntry);
                 post(event);
             }
         }
 
 
-        private void flowMissing(FlowRule flowRule) {
+        private void flowMissing(FlowEntry flowRule) {
             checkNotNull(flowRule, FLOW_RULE_NULL);
             checkValidity();
             Device device = deviceService.getDevice(flowRule.deviceId());
             FlowRuleProvider frp = getProvider(device.providerId());
             FlowRuleEvent event = null;
             switch (flowRule.state()) {
-            case PENDING_REMOVE:
-            case REMOVED:
-                event = store.removeFlowRule(flowRule);
-                frp.removeFlowRule(flowRule);
-                break;
-            case ADDED:
-            case PENDING_ADD:
-                frp.applyFlowRule(flowRule);
-                break;
-            default:
-                log.debug("Flow {} has not been installed.", flowRule);
+                case PENDING_REMOVE:
+                case REMOVED:
+                    event = store.removeFlowRule(flowRule);
+                    frp.removeFlowRule(flowRule);
+                    break;
+                case ADDED:
+                case PENDING_ADD:
+                    frp.applyFlowRule(flowRule);
+                    break;
+                default:
+                    log.debug("Flow {} has not been installed.", flowRule);
             }
 
             if (event != null) {
@@ -221,36 +263,40 @@
         }
 
 
-        private void flowAdded(FlowRule flowRule) {
-            checkNotNull(flowRule, FLOW_RULE_NULL);
+        private void flowAdded(FlowEntry flowEntry) {
+            checkNotNull(flowEntry, FLOW_RULE_NULL);
             checkValidity();
 
-            if (deadRounds.containsKey(flowRule) &&
-                    checkRuleLiveness(flowRule, store.getFlowRule(flowRule))) {
+            if (checkRuleLiveness(flowEntry, store.getFlowEntry(flowEntry))) {
 
-                FlowRuleEvent event = store.addOrUpdateFlowRule(flowRule);
+                FlowRuleEvent event = store.addOrUpdateFlowRule(flowEntry);
                 if (event == null) {
                     log.debug("No flow store event generated.");
                 } else {
-                    log.debug("Flow {} {}", flowRule, event.type());
+                    log.debug("Flow {} {}", flowEntry, event.type());
                     post(event);
                 }
             } else {
-                removeFlowRules(flowRule);
+                removeFlowRules(flowEntry);
             }
 
         }
 
-        private boolean checkRuleLiveness(FlowRule swRule, FlowRule storedRule) {
-            int timeout = storedRule.timeout();
+        private boolean checkRuleLiveness(FlowEntry swRule, FlowEntry storedRule) {
+            if (storedRule == null) {
+                return false;
+            }
+            long timeout = storedRule.timeout() * 1000;
+            Long currentTime = System.currentTimeMillis();
             if (storedRule.packets() != swRule.packets()) {
-                deadRounds.get(swRule).set(0);
+                storedRule.setLastSeen();
                 return true;
             }
 
-            return (deadRounds.get(swRule).getAndIncrement() *
-                    FlowRuleProvider.POLL_INTERVAL) <= timeout;
-
+            if ((currentTime - storedRule.lastSeen()) <= timeout) {
+                return true;
+            }
+            return false;
         }
 
         // Posts the specified event to the local event dispatcher.
@@ -261,13 +307,13 @@
         }
 
         @Override
-        public void pushFlowMetrics(DeviceId deviceId, Iterable<FlowRule> flowEntries) {
-            List<FlowRule> storedRules = Lists.newLinkedList(store.getFlowEntries(deviceId));
+        public void pushFlowMetrics(DeviceId deviceId, Iterable<FlowEntry> flowEntries) {
+            List<FlowEntry> storedRules = Lists.newLinkedList(store.getFlowEntries(deviceId));
 
-            Iterator<FlowRule> switchRulesIterator = flowEntries.iterator();
+            Iterator<FlowEntry> switchRulesIterator = flowEntries.iterator();
 
             while (switchRulesIterator.hasNext()) {
-                FlowRule rule = switchRulesIterator.next();
+                FlowEntry rule = switchRulesIterator.next();
                 if (storedRules.remove(rule)) {
                     // we both have the rule, let's update some info then.
                     flowAdded(rule);
@@ -276,7 +322,7 @@
                     extraneousFlow(rule);
                 }
             }
-            for (FlowRule rule : storedRules) {
+            for (FlowEntry rule : storedRules) {
                 // there are rules in the store that aren't on the switch
                 flowMissing(rule);
 
@@ -291,4 +337,63 @@
             eventDispatcher.post(event);
         }
     }
+
+    private class FlowRuleBatchFuture
+        implements Future<CompletedBatchOperation> {
+
+        private final List<Future<Void>> futures;
+
+        public FlowRuleBatchFuture(List<Future<Void>> futures) {
+            this.futures = futures;
+        }
+
+        @Override
+        public boolean cancel(boolean mayInterruptIfRunning) {
+            // TODO Auto-generated method stub
+            return false;
+        }
+
+        @Override
+        public boolean isCancelled() {
+            // TODO Auto-generated method stub
+            return false;
+        }
+
+        @Override
+        public boolean isDone() {
+            boolean isDone = true;
+            for (Future<Void> future : futures) {
+                isDone &= future.isDone();
+            }
+            return isDone;
+        }
+
+        @Override
+        public CompletedBatchOperation get() throws InterruptedException,
+        ExecutionException {
+            // TODO Auto-generated method stub
+            for (Future<Void> future : futures) {
+                future.get();
+            }
+            return new CompletedBatchOperation();
+        }
+
+        @Override
+        public CompletedBatchOperation get(long timeout, TimeUnit unit)
+                throws InterruptedException, ExecutionException,
+                TimeoutException {
+            // TODO we should decrement the timeout
+            long start = System.nanoTime();
+            long end = start + unit.toNanos(timeout);
+            for (Future<Void> future : futures) {
+                long now = System.nanoTime();
+                long thisTimeout = end - now;
+                future.get(thisTimeout, TimeUnit.NANOSECONDS);
+            }
+            return new CompletedBatchOperation();
+        }
+
+    }
+
+
 }
diff --git a/core/net/src/main/java/org/onlab/onos/net/host/impl/HostManager.java b/core/net/src/main/java/org/onlab/onos/net/host/impl/HostManager.java
index 88b6923..29a0f18 100644
--- a/core/net/src/main/java/org/onlab/onos/net/host/impl/HostManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/host/impl/HostManager.java
@@ -76,7 +76,7 @@
         eventDispatcher.addSink(HostEvent.class, listenerRegistry);
 
         monitor = new HostMonitor(deviceService,  packetService, this);
-
+        monitor.start();
     }
 
     @Deactivate
diff --git a/core/net/src/main/java/org/onlab/onos/net/host/impl/HostMonitor.java b/core/net/src/main/java/org/onlab/onos/net/host/impl/HostMonitor.java
index 9f8dd48..e6e348f 100644
--- a/core/net/src/main/java/org/onlab/onos/net/host/impl/HostMonitor.java
+++ b/core/net/src/main/java/org/onlab/onos/net/host/impl/HostMonitor.java
@@ -2,11 +2,11 @@
 
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
-import java.util.HashSet;
+import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.TimeUnit;
 
 import org.jboss.netty.util.Timeout;
@@ -33,8 +33,6 @@
 import org.onlab.packet.IpPrefix;
 import org.onlab.packet.MacAddress;
 import org.onlab.util.Timer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * Monitors hosts on the dataplane to detect changes in host data.
@@ -44,70 +42,91 @@
  * probe for hosts that have not yet been detected (specified by IP address).
  */
 public class HostMonitor implements TimerTask {
-    private static final Logger log = LoggerFactory.getLogger(HostMonitor.class);
-
-    private static final byte[] ZERO_MAC_ADDRESS =
-            MacAddress.valueOf("00:00:00:00:00:00").getAddress();
-
-    // TODO put on Ethernet
-    private static final byte[] BROADCAST_MAC =
-            MacAddress.valueOf("ff:ff:ff:ff:ff:ff").getAddress();
-
     private DeviceService deviceService;
     private PacketService packetService;
     private HostManager hostManager;
 
     private final Set<IpAddress> monitoredAddresses;
 
-    private final Map<ProviderId, HostProvider> hostProviders;
+    private final ConcurrentMap<ProviderId, HostProvider> hostProviders;
 
-    private final long probeRate;
+    private static final long DEFAULT_PROBE_RATE = 30000; // milliseconds
+    private long probeRate = DEFAULT_PROBE_RATE;
 
-    private final Timeout timeout;
+    private Timeout timeout;
 
-    public HostMonitor(
-            DeviceService deviceService,
-            PacketService packetService,
-            HostManager hostService) {
+    /**
+     * Creates a new host monitor.
+     *
+     * @param deviceService device service used to find edge ports
+     * @param packetService packet service used to send packets on the data plane
+     * @param hostManager host manager used to look up host information and
+     * probe existing hosts
+     */
+    public HostMonitor(DeviceService deviceService, PacketService packetService,
+            HostManager hostManager) {
 
         this.deviceService = deviceService;
         this.packetService = packetService;
-        this.hostManager = hostService;
+        this.hostManager = hostManager;
 
-        monitoredAddresses = new HashSet<>();
+        monitoredAddresses = Collections.newSetFromMap(
+                new ConcurrentHashMap<IpAddress, Boolean>());
         hostProviders = new ConcurrentHashMap<>();
 
-        probeRate = 30000; // milliseconds
-
         timeout = Timer.getTimer().newTimeout(this, 0, TimeUnit.MILLISECONDS);
-
-        addDefaultAddresses();
     }
 
-    private void addDefaultAddresses() {
-        //monitoredAddresses.add(IpAddress.valueOf("10.0.0.1"));
-    }
-
+    /**
+     * Adds an IP address to be monitored by the host monitor. The monitor will
+     * periodically probe the host to detect changes.
+     *
+     * @param ip IP address of the host to monitor
+     */
     void addMonitoringFor(IpAddress ip) {
         monitoredAddresses.add(ip);
     }
 
+    /**
+     * Stops monitoring the given IP address.
+     *
+     * @param ip IP address to stop monitoring on
+     */
     void stopMonitoring(IpAddress ip) {
         monitoredAddresses.remove(ip);
     }
 
-    void shutdown() {
-        timeout.cancel();
+    /**
+     * Starts the host monitor. Does nothing if the monitor is already running.
+     */
+    void start() {
+        synchronized (this) {
+            if (timeout == null) {
+                timeout = Timer.getTimer().newTimeout(this, 0, TimeUnit.MILLISECONDS);
+            }
+        }
     }
 
+    /**
+     * Stops the host monitor.
+     */
+    void shutdown() {
+        synchronized (this) {
+            timeout.cancel();
+            timeout = null;
+        }
+    }
+
+    /**
+     * Registers a host provider with the host monitor. The monitor can use the
+     * provider to probe hosts.
+     *
+     * @param provider the host provider to register
+     */
     void registerHostProvider(HostProvider provider) {
         hostProviders.put(provider.id(), provider);
     }
 
-    void unregisterHostProvider(HostProvider provider) {
-        // TODO find out how to call this
-    }
-
     @Override
     public void run(Timeout timeout) throws Exception {
         for (IpAddress ip : monitoredAddresses) {
@@ -121,14 +140,16 @@
             } else {
                 for (Host host : hosts) {
                     HostProvider provider = hostProviders.get(host.providerId());
-                    if (provider != null) {
+                    if (provider == null) {
+                        hostProviders.remove(host.providerId(), null);
+                    } else {
                         provider.triggerProbe(host);
                     }
                 }
             }
         }
 
-        timeout = Timer.getTimer().newTimeout(this, probeRate, TimeUnit.MILLISECONDS);
+        this.timeout = Timer.getTimer().newTimeout(this, probeRate, TimeUnit.MILLISECONDS);
     }
 
     /**
@@ -161,7 +182,7 @@
         List<Instruction> instructions = new ArrayList<>();
         instructions.add(Instructions.createOutput(port.number()));
 
-        TrafficTreatment treatment = new DefaultTrafficTreatment.Builder()
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
         .setOutput(port.number())
         .build();
 
@@ -184,12 +205,12 @@
 
         arp.setSenderHardwareAddress(sourceMac.getAddress())
            .setSenderProtocolAddress(sourceIp.toOctets())
-           .setTargetHardwareAddress(ZERO_MAC_ADDRESS)
+           .setTargetHardwareAddress(MacAddress.ZERO_MAC_ADDRESS)
            .setTargetProtocolAddress(targetIp.toOctets());
 
         Ethernet ethernet = new Ethernet();
         ethernet.setEtherType(Ethernet.TYPE_ARP)
-                .setDestinationMACAddress(BROADCAST_MAC)
+                .setDestinationMACAddress(MacAddress.BROADCAST_MAC)
                 .setSourceMACAddress(sourceMac.getAddress())
                 .setPayload(arp);
 
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/AbstractBlockAllocatorBasedIdGenerator.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/AbstractBlockAllocatorBasedIdGenerator.java
new file mode 100644
index 0000000..00b64da
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/AbstractBlockAllocatorBasedIdGenerator.java
@@ -0,0 +1,42 @@
+package org.onlab.onos.net.intent.impl;
+
+import org.onlab.onos.net.intent.IdGenerator;
+
+/**
+ * Base class of {@link IdGenerator} implementations which use {@link IdBlockAllocator} as
+ * backend.
+ *
+ * @param <T> the type of ID
+ */
+public abstract class AbstractBlockAllocatorBasedIdGenerator<T> implements IdGenerator<T> {
+    protected final IdBlockAllocator allocator;
+    protected IdBlock idBlock;
+
+    /**
+     * Constructs an ID generator which use {@link IdBlockAllocator} as backend.
+     *
+     * @param allocator
+     */
+    protected AbstractBlockAllocatorBasedIdGenerator(IdBlockAllocator allocator) {
+        this.allocator = allocator;
+        this.idBlock = allocator.allocateUniqueIdBlock();
+    }
+
+    @Override
+    public synchronized T getNewId() {
+        try {
+            return convertFrom(idBlock.getNextId());
+        } catch (UnavailableIdException e) {
+            idBlock = allocator.allocateUniqueIdBlock();
+            return convertFrom(idBlock.getNextId());
+        }
+    }
+
+    /**
+     * Returns an ID instance of {@code T} type from the long value.
+     *
+     * @param value original long value
+     * @return ID instance
+     */
+    protected abstract T convertFrom(long value);
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/DummyIdBlockAllocator.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/DummyIdBlockAllocator.java
new file mode 100644
index 0000000..f331aa2
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/DummyIdBlockAllocator.java
@@ -0,0 +1,31 @@
+package org.onlab.onos.net.intent.impl;
+
+public class DummyIdBlockAllocator implements IdBlockAllocator {
+    private long blockTop;
+    private static final long BLOCK_SIZE = 0x1000000L;
+
+    /**
+     * Returns a block of IDs which are unique and unused.
+     * Range of IDs is fixed size and is assigned incrementally as this method
+     * called.
+     *
+     * @return an IdBlock containing a set of unique IDs
+     */
+    @Override
+    public IdBlock allocateUniqueIdBlock() {
+        synchronized (this)  {
+            long blockHead = blockTop;
+            long blockTail = blockTop + BLOCK_SIZE;
+
+            IdBlock block = new IdBlock(blockHead, BLOCK_SIZE);
+            blockTop = blockTail;
+
+            return block;
+        }
+    }
+
+    @Override
+    public IdBlock allocateUniqueIdBlock(long range) {
+        throw new UnsupportedOperationException("Not supported yet");
+    }
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/HostToHostIntentCompiler.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/HostToHostIntentCompiler.java
new file mode 100644
index 0000000..de61e8e
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/HostToHostIntentCompiler.java
@@ -0,0 +1,90 @@
+package org.onlab.onos.net.intent.impl;
+
+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.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.onos.net.Host;
+import org.onlab.onos.net.HostId;
+import org.onlab.onos.net.Path;
+import org.onlab.onos.net.flow.TrafficSelector;
+import org.onlab.onos.net.host.HostService;
+import org.onlab.onos.net.intent.HostToHostIntent;
+import org.onlab.onos.net.intent.IdGenerator;
+import org.onlab.onos.net.intent.Intent;
+import org.onlab.onos.net.intent.IntentCompiler;
+import org.onlab.onos.net.intent.IntentExtensionService;
+import org.onlab.onos.net.intent.IntentId;
+import org.onlab.onos.net.intent.PathIntent;
+import org.onlab.onos.net.topology.PathService;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import static org.onlab.onos.net.flow.DefaultTrafficSelector.builder;
+
+/**
+ * A intent compiler for {@link HostToHostIntent}.
+ */
+@Component(immediate = true)
+public class HostToHostIntentCompiler
+        implements IntentCompiler<HostToHostIntent> {
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentExtensionService intentManager;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PathService pathService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    private IdGenerator<IntentId> intentIdGenerator;
+
+    @Activate
+    public void activate() {
+        IdBlockAllocator idBlockAllocator = new DummyIdBlockAllocator();
+        intentIdGenerator = new IdBlockAllocatorBasedIntentIdGenerator(idBlockAllocator);
+        intentManager.registerCompiler(HostToHostIntent.class, this);
+    }
+
+    @Deactivate
+    public void deactivate() {
+        intentManager.unregisterCompiler(HostToHostIntent.class);
+    }
+
+    @Override
+    public List<Intent> compile(HostToHostIntent intent) {
+        Path pathOne = getPath(intent.one(), intent.two());
+        Path pathTwo = getPath(intent.two(), intent.one());
+
+        Host one = hostService.getHost(intent.one());
+        Host two = hostService.getHost(intent.two());
+
+        return Arrays.asList(createPathIntent(pathOne, one, two, intent),
+                             createPathIntent(pathTwo, two, one, intent));
+    }
+
+    // Creates a path intent from the specified path and original connectivity intent.
+    private Intent createPathIntent(Path path, Host src, Host dst,
+                                    HostToHostIntent intent) {
+
+        TrafficSelector selector = builder(intent.selector())
+                .matchEthSrc(src.mac()).matchEthDst(dst.mac()).build();
+
+        return new PathIntent(intentIdGenerator.getNewId(),
+                              selector, intent.treatment(),
+                              path.src(), path.dst(), path);
+    }
+
+    private Path getPath(HostId one, HostId two) {
+        Set<Path> paths = pathService.getPaths(one, two);
+        if (paths.isEmpty()) {
+            throw new PathNotFoundException("No path from host " + one + " to " + two);
+        }
+        // TODO: let's be more intelligent about this eventually
+        return paths.iterator().next();
+    }
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/IdBlock.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IdBlock.java
new file mode 100644
index 0000000..ce418ea
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IdBlock.java
@@ -0,0 +1,111 @@
+package org.onlab.onos.net.intent.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicLong;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * A class representing an ID space.
+ */
+public final class IdBlock {
+    private final long start;
+    private final long size;
+
+    private final AtomicLong currentId;
+
+    /**
+     * Constructs a new ID block with the specified size and initial value.
+     *
+     * @param start initial value of the block
+     * @param size size of the block
+     * @throws IllegalArgumentException if the size is less than or equal to 0
+     */
+    public IdBlock(long start, long size) {
+        checkArgument(size > 0, "size should be more than 0, but %s", size);
+
+        this.start = start;
+        this.size = size;
+
+        this.currentId = new AtomicLong(start);
+    }
+
+    // TODO: consider if this method is needed or not
+    /**
+     * Returns the initial value.
+     *
+     * @return initial value
+     */
+    public long getStart() {
+        return start;
+    }
+
+    // TODO: consider if this method is needed or not
+    /**
+     * Returns the last value.
+     *
+     * @return last value
+     */
+    public long getEnd() {
+        return start + size - 1;
+    }
+
+    /**
+     * Returns the block size.
+     *
+     * @return block size
+     */
+    public long getSize() {
+        return size;
+    }
+
+    /**
+     * Returns the next ID in the block.
+     *
+     * @return next ID
+     * @throws UnavailableIdException if there is no available ID in the block.
+     */
+    public long getNextId() {
+        final long id = currentId.getAndIncrement();
+        if (id > getEnd()) {
+            throw new UnavailableIdException(String.format(
+                    "used all IDs in allocated space (size: %d, end: %d, current: %d)",
+                    size, getEnd(), id
+            ));
+        }
+
+        return id;
+    }
+
+    // TODO: Do we really need equals and hashCode? Should it contain currentId?
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        IdBlock that = (IdBlock) o;
+        return Objects.equals(this.start, that.start)
+                && Objects.equals(this.size, that.size)
+                && Objects.equals(this.currentId.get(), that.currentId.get());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(start, size, currentId);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                .add("start", start)
+                .add("size", size)
+                .add("currentId", currentId)
+                .toString();
+    }
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/IdBlockAllocator.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IdBlockAllocator.java
new file mode 100644
index 0000000..1adac02
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IdBlockAllocator.java
@@ -0,0 +1,21 @@
+package org.onlab.onos.net.intent.impl;
+
+/**
+ * An interface that gives unique ID spaces.
+ */
+public interface IdBlockAllocator {
+    /**
+     * Allocates a unique Id Block.
+     *
+     * @return Id Block.
+     */
+    IdBlock allocateUniqueIdBlock();
+
+    /**
+     * Allocates next unique id and retrieve a new range of ids if needed.
+     *
+     * @param range range to use for the identifier
+     * @return Id Block.
+     */
+    IdBlock allocateUniqueIdBlock(long range);
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/IdBlockAllocatorBasedIntentIdGenerator.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IdBlockAllocatorBasedIntentIdGenerator.java
new file mode 100644
index 0000000..9620e59
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IdBlockAllocatorBasedIntentIdGenerator.java
@@ -0,0 +1,25 @@
+package org.onlab.onos.net.intent.impl;
+
+import org.onlab.onos.net.intent.IntentId;
+
+/**
+ * An implementation of {@link org.onlab.onos.net.intent.IdGenerator} of intent ID,
+ * which uses {@link IdBlockAllocator}.
+ */
+public class IdBlockAllocatorBasedIntentIdGenerator extends AbstractBlockAllocatorBasedIdGenerator<IntentId> {
+
+    /**
+     * Constructs an intent ID generator, which uses the specified ID block allocator
+     * to generate a global unique intent ID.
+     *
+     * @param allocator the ID block allocator to use for generating intent IDs
+     */
+    public IdBlockAllocatorBasedIntentIdGenerator(IdBlockAllocator allocator) {
+        super(allocator);
+    }
+
+    @Override
+    protected IntentId convertFrom(long value) {
+        return new IntentId(value);
+    }
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentCompilationException.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentCompilationException.java
new file mode 100644
index 0000000..bf739df
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentCompilationException.java
@@ -0,0 +1,22 @@
+package org.onlab.onos.net.intent.impl;
+
+import org.onlab.onos.net.intent.IntentException;
+
+/**
+ * An exception thrown when a intent compilation fails.
+ */
+public class IntentCompilationException extends IntentException {
+    private static final long serialVersionUID = 235237603018210810L;
+
+    public IntentCompilationException() {
+        super();
+    }
+
+    public IntentCompilationException(String message) {
+        super(message);
+    }
+
+    public IntentCompilationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentInstallationException.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentInstallationException.java
new file mode 100644
index 0000000..3b17cf1
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentInstallationException.java
@@ -0,0 +1,22 @@
+package org.onlab.onos.net.intent.impl;
+
+import org.onlab.onos.net.intent.IntentException;
+
+/**
+ * An exception thrown when intent installation fails.
+ */
+public class IntentInstallationException extends IntentException {
+    private static final long serialVersionUID = 3720268258616014168L;
+
+    public IntentInstallationException() {
+        super();
+    }
+
+    public IntentInstallationException(String message) {
+        super(message);
+    }
+
+    public IntentInstallationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentManager.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentManager.java
new file mode 100644
index 0000000..16b75f2
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentManager.java
@@ -0,0 +1,463 @@
+package org.onlab.onos.net.intent.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.onos.net.intent.IntentState.COMPILING;
+import static org.onlab.onos.net.intent.IntentState.FAILED;
+import static org.onlab.onos.net.intent.IntentState.INSTALLED;
+import static org.onlab.onos.net.intent.IntentState.INSTALLING;
+import static org.onlab.onos.net.intent.IntentState.RECOMPILING;
+import static org.onlab.onos.net.intent.IntentState.WITHDRAWING;
+import static org.onlab.onos.net.intent.IntentState.WITHDRAWN;
+import static org.onlab.util.Tools.namedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutorService;
+
+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.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.onos.event.AbstractListenerRegistry;
+import org.onlab.onos.event.EventDeliveryService;
+import org.onlab.onos.net.intent.InstallableIntent;
+import org.onlab.onos.net.intent.Intent;
+import org.onlab.onos.net.intent.IntentCompiler;
+import org.onlab.onos.net.intent.IntentEvent;
+import org.onlab.onos.net.intent.IntentException;
+import org.onlab.onos.net.intent.IntentExtensionService;
+import org.onlab.onos.net.intent.IntentId;
+import org.onlab.onos.net.intent.IntentInstaller;
+import org.onlab.onos.net.intent.IntentListener;
+import org.onlab.onos.net.intent.IntentOperations;
+import org.onlab.onos.net.intent.IntentService;
+import org.onlab.onos.net.intent.IntentState;
+import org.onlab.onos.net.intent.IntentStore;
+import org.onlab.onos.net.intent.IntentStoreDelegate;
+import org.slf4j.Logger;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * An implementation of Intent Manager.
+ */
+@Component(immediate = true)
+@Service
+public class IntentManager
+        implements IntentService, IntentExtensionService {
+    private final Logger log = getLogger(getClass());
+
+    public static final String INTENT_NULL = "Intent cannot be null";
+    public static final String INTENT_ID_NULL = "Intent ID cannot be null";
+
+    // Collections for compiler, installer, and listener are ONOS instance local
+    private final ConcurrentMap<Class<? extends Intent>,
+            IntentCompiler<? extends Intent>> compilers = new ConcurrentHashMap<>();
+    private final ConcurrentMap<Class<? extends InstallableIntent>,
+            IntentInstaller<? extends InstallableIntent>> installers = new ConcurrentHashMap<>();
+
+    private final AbstractListenerRegistry<IntentEvent, IntentListener>
+            listenerRegistry = new AbstractListenerRegistry<>();
+
+    private final ExecutorService executor = newSingleThreadExecutor(namedThreads("onos-intents"));
+
+    private final IntentStoreDelegate delegate = new InternalStoreDelegate();
+    private final TopologyChangeDelegate topoDelegate = new InternalTopoChangeDelegate();
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentStore store;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ObjectiveTrackerService trackerService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected EventDeliveryService eventDispatcher;
+
+    @Activate
+    public void activate() {
+        store.setDelegate(delegate);
+        trackerService.setDelegate(topoDelegate);
+        eventDispatcher.addSink(IntentEvent.class, listenerRegistry);
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        store.unsetDelegate(delegate);
+        trackerService.unsetDelegate(topoDelegate);
+        eventDispatcher.removeSink(IntentEvent.class);
+        log.info("Stopped");
+    }
+
+    @Override
+    public void submit(Intent intent) {
+        checkNotNull(intent, INTENT_NULL);
+        registerSubclassCompilerIfNeeded(intent);
+        IntentEvent event = store.createIntent(intent);
+        if (event != null) {
+            eventDispatcher.post(event);
+            executor.execute(new IntentTask(COMPILING, intent));
+        }
+    }
+
+    @Override
+    public void withdraw(Intent intent) {
+        checkNotNull(intent, INTENT_NULL);
+        executor.execute(new IntentTask(WITHDRAWING, intent));
+    }
+
+    // FIXME: implement this method
+    @Override
+    public void execute(IntentOperations operations) {
+        throw new UnsupportedOperationException("execute() is not implemented yet");
+    }
+
+    @Override
+    public Iterable<Intent> getIntents() {
+        return store.getIntents();
+    }
+
+    @Override
+    public long getIntentCount() {
+        return store.getIntentCount();
+    }
+
+    @Override
+    public Intent getIntent(IntentId id) {
+        checkNotNull(id, INTENT_ID_NULL);
+        return store.getIntent(id);
+    }
+
+    @Override
+    public IntentState getIntentState(IntentId id) {
+        checkNotNull(id, INTENT_ID_NULL);
+        return store.getIntentState(id);
+    }
+
+    @Override
+    public void addListener(IntentListener listener) {
+        listenerRegistry.addListener(listener);
+    }
+
+    @Override
+    public void removeListener(IntentListener listener) {
+        listenerRegistry.removeListener(listener);
+    }
+
+    @Override
+    public <T extends Intent> void registerCompiler(Class<T> cls, IntentCompiler<T> compiler) {
+        compilers.put(cls, compiler);
+    }
+
+    @Override
+    public <T extends Intent> void unregisterCompiler(Class<T> cls) {
+        compilers.remove(cls);
+    }
+
+    @Override
+    public Map<Class<? extends Intent>, IntentCompiler<? extends Intent>> getCompilers() {
+        return ImmutableMap.copyOf(compilers);
+    }
+
+    @Override
+    public <T extends InstallableIntent> void registerInstaller(Class<T> cls, IntentInstaller<T> installer) {
+        installers.put(cls, installer);
+    }
+
+    @Override
+    public <T extends InstallableIntent> void unregisterInstaller(Class<T> cls) {
+        installers.remove(cls);
+    }
+
+    @Override
+    public Map<Class<? extends InstallableIntent>, IntentInstaller<? extends InstallableIntent>> getInstallers() {
+        return ImmutableMap.copyOf(installers);
+    }
+
+    /**
+     * Returns the corresponding intent compiler to the specified intent.
+     *
+     * @param intent intent
+     * @param <T>    the type of intent
+     * @return intent compiler corresponding to the specified intent
+     */
+    private <T extends Intent> IntentCompiler<T> getCompiler(T intent) {
+        @SuppressWarnings("unchecked")
+        IntentCompiler<T> compiler = (IntentCompiler<T>) compilers.get(intent.getClass());
+        if (compiler == null) {
+            throw new IntentException("no compiler for class " + intent.getClass());
+        }
+        return compiler;
+    }
+
+    /**
+     * Returns the corresponding intent installer to the specified installable intent.
+     *
+     * @param intent intent
+     * @param <T>    the type of installable intent
+     * @return intent installer corresponding to the specified installable intent
+     */
+    private <T extends InstallableIntent> IntentInstaller<T> getInstaller(T intent) {
+        @SuppressWarnings("unchecked")
+        IntentInstaller<T> installer = (IntentInstaller<T>) installers.get(intent.getClass());
+        if (installer == null) {
+            throw new IntentException("no installer for class " + intent.getClass());
+        }
+        return installer;
+    }
+
+    /**
+     * Compiles the specified intent.
+     *
+     * @param intent intent to be compiled
+     */
+    private void executeCompilingPhase(Intent intent) {
+        // Indicate that the intent is entering the compiling phase.
+        store.setState(intent, COMPILING);
+
+        try {
+            // Compile the intent into installable derivatives.
+            List<InstallableIntent> installable = compileIntent(intent);
+
+            // If all went well, associate the resulting list of installable
+            // intents with the top-level intent and proceed to install.
+            store.addInstallableIntents(intent.id(), installable);
+            executeInstallingPhase(intent);
+
+        } catch (Exception e) {
+            log.warn("Unable to compile intent {} due to: {}", intent.id(), e);
+
+            // If compilation failed, mark the intent as failed.
+            store.setState(intent, FAILED);
+        }
+    }
+
+    // FIXME: To make SDN-IP workable ASAP, only single level compilation is implemented
+    // TODO: implement compilation traversing tree structure
+    private List<InstallableIntent> compileIntent(Intent intent) {
+        List<InstallableIntent> installable = new ArrayList<>();
+        for (Intent compiled : getCompiler(intent).compile(intent)) {
+            InstallableIntent installableIntent = (InstallableIntent) compiled;
+            installable.add(installableIntent);
+        }
+        return installable;
+    }
+
+    /**
+     * Installs all installable intents associated with the specified top-level
+     * intent.
+     *
+     * @param intent intent to be installed
+     */
+    private void executeInstallingPhase(Intent intent) {
+        // Indicate that the intent is entering the installing phase.
+        store.setState(intent, INSTALLING);
+
+        try {
+            List<InstallableIntent> installables = store.getInstallableIntents(intent.id());
+            if (installables != null) {
+                for (InstallableIntent installable : installables) {
+                    registerSubclassInstallerIfNeeded(installable);
+                    trackerService.addTrackedResources(intent.id(),
+                                                       installable.requiredLinks());
+                    getInstaller(installable).install(installable);
+                }
+            }
+            eventDispatcher.post(store.setState(intent, INSTALLED));
+
+        } catch (Exception e) {
+            log.warn("Unable to install intent {} due to: {}", intent.id(), e);
+            uninstallIntent(intent);
+
+            // If compilation failed, kick off the recompiling phase.
+            executeRecompilingPhase(intent);
+        }
+    }
+
+    /**
+     * Recompiles the specified intent.
+     *
+     * @param intent intent to be recompiled
+     */
+    private void executeRecompilingPhase(Intent intent) {
+        // Indicate that the intent is entering the recompiling phase.
+        store.setState(intent, RECOMPILING);
+
+        try {
+            // Compile the intent into installable derivatives.
+            List<InstallableIntent> installable = compileIntent(intent);
+
+            // If all went well, compare the existing list of installable
+            // intents with the newly compiled list. If they are the same,
+            // bail, out since the previous approach was determined not to
+            // be viable.
+            List<InstallableIntent> originalInstallable =
+                    store.getInstallableIntents(intent.id());
+
+            if (Objects.equals(originalInstallable, installable)) {
+                eventDispatcher.post(store.setState(intent, FAILED));
+            } else {
+                // Otherwise, re-associate the newly compiled installable intents
+                // with the top-level intent and kick off installing phase.
+                store.addInstallableIntents(intent.id(), installable);
+                executeInstallingPhase(intent);
+            }
+        } catch (Exception e) {
+            log.warn("Unable to recompile intent {} due to: {}", intent.id(), e);
+
+            // If compilation failed, mark the intent as failed.
+            eventDispatcher.post(store.setState(intent, FAILED));
+        }
+    }
+
+    /**
+     * Uninstalls the specified intent by uninstalling all of its associated
+     * installable derivatives.
+     *
+     * @param intent intent to be installed
+     */
+    private void executeWithdrawingPhase(Intent intent) {
+        // Indicate that the intent is being withdrawn.
+        store.setState(intent, WITHDRAWING);
+        uninstallIntent(intent);
+
+        // If all went well, disassociate the top-level intent with its
+        // installable derivatives and mark it as withdrawn.
+        store.removeInstalledIntents(intent.id());
+        eventDispatcher.post(store.setState(intent, WITHDRAWN));
+    }
+
+    /**
+     * Uninstalls all installable intents associated with the given intent.
+     *
+     * @param intent intent to be uninstalled
+     */
+    private void uninstallIntent(Intent intent) {
+        try {
+            List<InstallableIntent> installables = store.getInstallableIntents(intent.id());
+            if (installables != null) {
+                for (InstallableIntent installable : installables) {
+                    getInstaller(installable).uninstall(installable);
+                }
+            }
+        } catch (IntentException e) {
+            log.warn("Unable to uninstall intent {} due to: {}", intent.id(), e);
+        }
+    }
+
+    /**
+     * Registers an intent compiler of the specified intent if an intent compiler
+     * for the intent is not registered. This method traverses the class hierarchy of
+     * the intent. Once an intent compiler for a parent type is found, this method
+     * registers the found intent compiler.
+     *
+     * @param intent intent
+     */
+    private void registerSubclassCompilerIfNeeded(Intent intent) {
+        if (!compilers.containsKey(intent.getClass())) {
+            Class<?> cls = intent.getClass();
+            while (cls != Object.class) {
+                // As long as we're within the Intent class descendants
+                if (Intent.class.isAssignableFrom(cls)) {
+                    IntentCompiler<?> compiler = compilers.get(cls);
+                    if (compiler != null) {
+                        compilers.put(intent.getClass(), compiler);
+                        return;
+                    }
+                }
+                cls = cls.getSuperclass();
+            }
+        }
+    }
+
+    /**
+     * Registers an intent installer of the specified intent if an intent installer
+     * for the intent is not registered. This method traverses the class hierarchy of
+     * the intent. Once an intent installer for a parent type is found, this method
+     * registers the found intent installer.
+     *
+     * @param intent intent
+     */
+    private void registerSubclassInstallerIfNeeded(InstallableIntent intent) {
+        if (!installers.containsKey(intent.getClass())) {
+            Class<?> cls = intent.getClass();
+            while (cls != Object.class) {
+                // As long as we're within the InstallableIntent class descendants
+                if (InstallableIntent.class.isAssignableFrom(cls)) {
+                    IntentInstaller<?> installer = installers.get(cls);
+                    if (installer != null) {
+                        installers.put(intent.getClass(), installer);
+                        return;
+                    }
+                }
+                cls = cls.getSuperclass();
+            }
+        }
+    }
+
+    // Store delegate to re-post events emitted from the store.
+    private class InternalStoreDelegate implements IntentStoreDelegate {
+        @Override
+        public void notify(IntentEvent event) {
+            eventDispatcher.post(event);
+            if (event.type() == IntentEvent.Type.SUBMITTED) {
+                executor.execute(new IntentTask(COMPILING, event.subject()));
+            }
+        }
+    }
+
+    // Topology change delegate
+    private class InternalTopoChangeDelegate implements TopologyChangeDelegate {
+        @Override
+        public void triggerCompile(Iterable<IntentId> intentIds,
+                                   boolean compileAllFailed) {
+            // Attempt recompilation of the specified intents first.
+            for (IntentId intentId : intentIds) {
+                Intent intent = getIntent(intentId);
+                uninstallIntent(intent);
+
+                executeRecompilingPhase(intent);
+            }
+
+            if (compileAllFailed) {
+                // If required, compile all currently failed intents.
+                for (Intent intent : getIntents()) {
+                    if (getIntentState(intent.id()) == FAILED) {
+                        executeCompilingPhase(intent);
+                    }
+                }
+            }
+        }
+    }
+
+    // Auxiliary runnable to perform asynchronous tasks.
+    private class IntentTask implements Runnable {
+        private final IntentState state;
+        private final Intent intent;
+
+        public IntentTask(IntentState state, Intent intent) {
+            this.state = state;
+            this.intent = intent;
+        }
+
+        @Override
+        public void run() {
+            if (state == COMPILING) {
+                executeCompilingPhase(intent);
+            } else if (state == RECOMPILING) {
+                executeRecompilingPhase(intent);
+            } else if (state == WITHDRAWING) {
+                executeWithdrawingPhase(intent);
+            }
+        }
+    }
+
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentRemovalException.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentRemovalException.java
new file mode 100644
index 0000000..5ee4ee4
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentRemovalException.java
@@ -0,0 +1,22 @@
+package org.onlab.onos.net.intent.impl;
+
+import org.onlab.onos.net.intent.IntentException;
+
+/**
+ * An exception thrown when intent removal failed.
+ */
+public class IntentRemovalException extends IntentException {
+    private static final long serialVersionUID = -5259226322037891951L;
+
+    public IntentRemovalException() {
+        super();
+    }
+
+    public IntentRemovalException(String message) {
+        super(message);
+    }
+
+    public IntentRemovalException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/ObjectiveTracker.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/ObjectiveTracker.java
new file mode 100644
index 0000000..d84c367
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/ObjectiveTracker.java
@@ -0,0 +1,140 @@
+package org.onlab.onos.net.intent.impl;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.SetMultimap;
+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.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.onos.event.Event;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.LinkKey;
+import org.onlab.onos.net.intent.IntentId;
+import org.onlab.onos.net.link.LinkEvent;
+import org.onlab.onos.net.topology.TopologyEvent;
+import org.onlab.onos.net.topology.TopologyListener;
+import org.onlab.onos.net.topology.TopologyService;
+import org.slf4j.Logger;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Multimaps.synchronizedSetMultimap;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.onos.net.link.LinkEvent.Type.LINK_REMOVED;
+import static org.onlab.util.Tools.namedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Entity responsible for tracking installed flows and for monitoring topology
+ * events to determine what flows are affected by topology changes.
+ */
+@Component
+@Service
+public class ObjectiveTracker implements ObjectiveTrackerService {
+
+    private final Logger log = getLogger(getClass());
+
+    private final SetMultimap<LinkKey, IntentId> intentsByLink =
+            synchronizedSetMultimap(HashMultimap.<LinkKey, IntentId>create());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected TopologyService topologyService;
+
+    private ExecutorService executorService =
+            newSingleThreadExecutor(namedThreads("onos-flowtracker"));
+
+    private TopologyListener listener = new InternalTopologyListener();
+    private TopologyChangeDelegate delegate;
+
+    @Activate
+    public void activate() {
+        topologyService.addListener(listener);
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        topologyService.removeListener(listener);
+        log.info("Stopped");
+    }
+
+    @Override
+    public void setDelegate(TopologyChangeDelegate delegate) {
+        checkNotNull(delegate, "Delegate cannot be null");
+        checkArgument(this.delegate == null || this.delegate == delegate,
+                      "Another delegate already set");
+        this.delegate = delegate;
+    }
+
+    @Override
+    public void unsetDelegate(TopologyChangeDelegate delegate) {
+        checkArgument(this.delegate == delegate, "Not the current delegate");
+        this.delegate = null;
+    }
+
+    @Override
+    public void addTrackedResources(IntentId intentId, Collection<Link> resources) {
+        for (Link link : resources) {
+            intentsByLink.put(new LinkKey(link), intentId);
+        }
+    }
+
+    @Override
+    public void removeTrackedResources(IntentId intentId, Collection<Link> resources) {
+        for (Link link : resources) {
+            intentsByLink.remove(new LinkKey(link), intentId);
+        }
+    }
+
+    // Internal re-actor to topology change events.
+    private class InternalTopologyListener implements TopologyListener {
+        @Override
+        public void event(TopologyEvent event) {
+            executorService.execute(new TopologyChangeHandler(event));
+        }
+    }
+
+    // Re-dispatcher of topology change events.
+    private class TopologyChangeHandler implements Runnable {
+
+        private final TopologyEvent event;
+
+        TopologyChangeHandler(TopologyEvent event) {
+            this.event = event;
+        }
+
+        @Override
+        public void run() {
+            if (event.reasons() == null) {
+                delegate.triggerCompile(new HashSet<IntentId>(), true);
+
+            } else {
+                Set<IntentId> toBeRecompiled = new HashSet<>();
+                boolean recompileOnly = true;
+
+                // Scan through the list of reasons and keep accruing all
+                // intents that need to be recompiled.
+                for (Event reason : event.reasons()) {
+                    if (reason instanceof LinkEvent) {
+                        LinkEvent linkEvent = (LinkEvent) reason;
+                        if (linkEvent.type() == LINK_REMOVED) {
+                            Set<IntentId> intentIds = intentsByLink.get(new LinkKey(linkEvent.subject()));
+                            toBeRecompiled.addAll(intentIds);
+                        }
+                        recompileOnly = recompileOnly && linkEvent.type() == LINK_REMOVED;
+                    }
+                }
+
+                delegate.triggerCompile(toBeRecompiled, !recompileOnly);
+            }
+        }
+    }
+
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/ObjectiveTrackerService.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/ObjectiveTrackerService.java
new file mode 100644
index 0000000..15496ff
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/ObjectiveTrackerService.java
@@ -0,0 +1,44 @@
+package org.onlab.onos.net.intent.impl;
+
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.intent.IntentId;
+
+import java.util.Collection;
+
+/**
+ * Auxiliary service for tracking intent path flows and for notifying the
+ * intent service of environment changes via topology change delegate.
+ */
+public interface ObjectiveTrackerService {
+
+    /**
+     * Sets a topology change delegate.
+     *
+     * @param delegate topology change delegate
+     */
+    void setDelegate(TopologyChangeDelegate delegate);
+
+    /**
+     * Unsets topology change delegate.
+     *
+     * @param delegate topology change delegate
+     */
+    void unsetDelegate(TopologyChangeDelegate delegate);
+
+    /**
+     * Adds a path flow to be tracked.
+     *
+     * @param intentId intent identity on whose behalf the path is being tracked
+     * @param resources resources to track
+     */
+    public void addTrackedResources(IntentId intentId, Collection<Link> resources);
+
+    /**
+     * Removes a path flow to be tracked.
+     *
+     * @param intentId intent identity on whose behalf the path is being tracked
+     * @param resources resources to stop tracking
+     */
+    public void removeTrackedResources(IntentId intentId, Collection<Link> resources);
+
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathIntentInstaller.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathIntentInstaller.java
new file mode 100644
index 0000000..0ca75c2
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathIntentInstaller.java
@@ -0,0 +1,117 @@
+package org.onlab.onos.net.intent.impl;
+
+import static org.onlab.onos.net.flow.DefaultTrafficTreatment.builder;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+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.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.onos.ApplicationId;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.flow.DefaultFlowRule;
+import org.onlab.onos.net.flow.DefaultTrafficSelector;
+import org.onlab.onos.net.flow.FlowRule;
+import org.onlab.onos.net.flow.FlowRuleBatchEntry;
+import org.onlab.onos.net.flow.FlowRuleBatchEntry.FlowRuleOperation;
+import org.onlab.onos.net.flow.FlowRuleBatchOperation;
+import org.onlab.onos.net.flow.FlowRuleService;
+import org.onlab.onos.net.flow.TrafficSelector;
+import org.onlab.onos.net.flow.TrafficTreatment;
+import org.onlab.onos.net.intent.IntentExtensionService;
+import org.onlab.onos.net.intent.IntentInstaller;
+import org.onlab.onos.net.intent.PathIntent;
+import org.slf4j.Logger;
+
+import com.google.common.collect.Lists;
+
+/**
+ * Installer for {@link PathIntent path connectivity intents}.
+ */
+@Component(immediate = true)
+public class PathIntentInstaller implements IntentInstaller<PathIntent> {
+
+    private final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentExtensionService intentManager;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowRuleService flowRuleService;
+
+    private final ApplicationId appId = ApplicationId.getAppId();
+
+    @Activate
+    public void activate() {
+        intentManager.registerInstaller(PathIntent.class, this);
+    }
+
+    @Deactivate
+    public void deactivate() {
+        intentManager.unregisterInstaller(PathIntent.class);
+    }
+
+    @Override
+    public void install(PathIntent intent) {
+        TrafficSelector.Builder builder =
+                DefaultTrafficSelector.builder(intent.selector());
+        Iterator<Link> links = intent.path().links().iterator();
+        ConnectPoint prev = links.next().dst();
+        List<FlowRuleBatchEntry> rules = Lists.newLinkedList();
+        while (links.hasNext()) {
+            builder.matchInport(prev.port());
+            Link link = links.next();
+            TrafficTreatment treatment = builder()
+                    .setOutput(link.src().port()).build();
+
+            FlowRule rule = new DefaultFlowRule(link.src().deviceId(),
+                    builder.build(), treatment,
+                    123, appId, 600);
+            rules.add(new FlowRuleBatchEntry(FlowRuleOperation.ADD, rule));
+            //flowRuleService.applyFlowRules(rule);
+            prev = link.dst();
+        }
+        FlowRuleBatchOperation batch = new FlowRuleBatchOperation(rules);
+        try {
+            flowRuleService.applyBatch(batch).get();
+        } catch (InterruptedException | ExecutionException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void uninstall(PathIntent intent) {
+        TrafficSelector.Builder builder =
+                DefaultTrafficSelector.builder(intent.selector());
+        Iterator<Link> links = intent.path().links().iterator();
+        ConnectPoint prev = links.next().dst();
+        List<FlowRuleBatchEntry> rules = Lists.newLinkedList();
+
+        while (links.hasNext()) {
+            builder.matchInport(prev.port());
+            Link link = links.next();
+            TrafficTreatment treatment = builder()
+                    .setOutput(link.src().port()).build();
+            FlowRule rule = new DefaultFlowRule(link.src().deviceId(),
+                    builder.build(), treatment,
+                    123, appId, 600);
+            rules.add(new FlowRuleBatchEntry(FlowRuleOperation.REMOVE, rule));
+            //flowRuleService.removeFlowRules(rule);
+            prev = link.dst();
+        }
+        FlowRuleBatchOperation batch = new FlowRuleBatchOperation(rules);
+        try {
+            flowRuleService.applyBatch(batch).get();
+        } catch (InterruptedException | ExecutionException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathNotFoundException.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathNotFoundException.java
new file mode 100644
index 0000000..a1fd63a
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathNotFoundException.java
@@ -0,0 +1,22 @@
+package org.onlab.onos.net.intent.impl;
+
+import org.onlab.onos.net.intent.IntentException;
+
+/**
+ * An exception thrown when a path is not found.
+ */
+public class PathNotFoundException extends IntentException {
+    private static final long serialVersionUID = -2087045731049914733L;
+
+    public PathNotFoundException() {
+        super();
+    }
+
+    public PathNotFoundException(String message) {
+        super(message);
+    }
+
+    public PathNotFoundException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/PointToPointIntentCompiler.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PointToPointIntentCompiler.java
new file mode 100644
index 0000000..0bd1703
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PointToPointIntentCompiler.java
@@ -0,0 +1,105 @@
+package org.onlab.onos.net.intent.impl;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+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.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DefaultEdgeLink;
+import org.onlab.onos.net.DefaultPath;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.Path;
+import org.onlab.onos.net.host.HostService;
+import org.onlab.onos.net.intent.IdGenerator;
+import org.onlab.onos.net.intent.Intent;
+import org.onlab.onos.net.intent.IntentCompiler;
+import org.onlab.onos.net.intent.IntentExtensionService;
+import org.onlab.onos.net.intent.IntentId;
+import org.onlab.onos.net.intent.PathIntent;
+import org.onlab.onos.net.intent.PointToPointIntent;
+import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.onos.net.topology.PathService;
+
+/**
+ * A intent compiler for {@link org.onlab.onos.net.intent.HostToHostIntent}.
+ */
+@Component(immediate = true)
+public class PointToPointIntentCompiler
+        implements IntentCompiler<PointToPointIntent> {
+
+    private static final ProviderId PID = new ProviderId("core", "org.onlab.onos.core", true);
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentExtensionService intentManager;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PathService pathService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    private IdGenerator<IntentId> intentIdGenerator;
+
+    @Activate
+    public void activate() {
+        IdBlockAllocator idBlockAllocator = new DummyIdBlockAllocator();
+        intentIdGenerator = new IdBlockAllocatorBasedIntentIdGenerator(idBlockAllocator);
+        intentManager.registerCompiler(PointToPointIntent.class, this);
+    }
+
+    @Deactivate
+    public void deactivate() {
+        intentManager.unregisterCompiler(PointToPointIntent.class);
+    }
+
+    @Override
+    public List<Intent> compile(PointToPointIntent intent) {
+        Path path = getPath(intent.ingressPoint(), intent.egressPoint());
+
+        List<Link> links = new ArrayList<>();
+        links.add(DefaultEdgeLink.createEdgeLink(intent.ingressPoint(), true));
+        links.addAll(path.links());
+        links.add(DefaultEdgeLink.createEdgeLink(intent.egressPoint(), false));
+
+        return Arrays.asList(createPathIntent(new DefaultPath(PID, links, path.cost() + 2,
+                                              path.annotations()),
+                             intent));
+    }
+
+    /**
+     * Creates a path intent from the specified path and original
+     * connectivity intent.
+     *
+     * @param path path to create an intent for
+     * @param intent original intent
+     */
+    private Intent createPathIntent(Path path,
+                                    PointToPointIntent intent) {
+
+        return new PathIntent(intentIdGenerator.getNewId(),
+                              intent.selector(), intent.treatment(),
+                              path.src(), path.dst(), path);
+    }
+
+    /**
+     * Computes a path between two ConnectPoints.
+     *
+     * @param one start of the path
+     * @param two end of the path
+     * @return Path between the two
+     * @throws PathNotFoundException if a path cannot be found
+     */
+    private Path getPath(ConnectPoint one, ConnectPoint two) {
+        Set<Path> paths = pathService.getPaths(one.deviceId(), two.deviceId());
+        if (paths.isEmpty()) {
+            throw new PathNotFoundException("No path from " + one + " to " + two);
+        }
+        // TODO: let's be more intelligent about this eventually
+        return paths.iterator().next();
+    }
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/TopologyChangeDelegate.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/TopologyChangeDelegate.java
new file mode 100644
index 0000000..30e6899
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/TopologyChangeDelegate.java
@@ -0,0 +1,22 @@
+package org.onlab.onos.net.intent.impl;
+
+import org.onlab.onos.net.intent.IntentId;
+
+/**
+ * Auxiliary delegate for integration of intent manager and flow trackerService.
+ */
+public interface TopologyChangeDelegate {
+
+    /**
+     * Notifies that topology has changed in such a way that the specified
+     * intents should be recompiled. If the {@code compileAllFailed} parameter
+     * is true, then all intents in {@link org.onlab.onos.net.intent.IntentState#FAILED}
+     * state should be compiled as well.
+     *
+     * @param intentIds intents that should be recompiled
+     * @param compileAllFailed true implies full compile of all failed intents
+     *                         is required; false for selective recompile only
+     */
+    void triggerCompile(Iterable<IntentId> intentIds, boolean compileAllFailed);
+
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/UnavailableIdException.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/UnavailableIdException.java
new file mode 100644
index 0000000..fd4f122
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/UnavailableIdException.java
@@ -0,0 +1,34 @@
+package org.onlab.onos.net.intent.impl;
+
+/**
+ * Represents that there is no available IDs.
+ */
+public class UnavailableIdException extends RuntimeException {
+
+    private static final long serialVersionUID = -2287403908433720122L;
+
+    /**
+     * Constructs an exception with no message and no underlying cause.
+     */
+    public UnavailableIdException() {
+    }
+
+    /**
+     * Constructs an exception with the specified message.
+     *
+     * @param message the message describing the specific nature of the error
+     */
+    public UnavailableIdException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs an exception with the specified message and the underlying cause.
+     *
+     * @param message the message describing the specific nature of the error
+     * @param cause the underlying cause of this error
+     */
+    public UnavailableIdException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/package-info.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/package-info.java
new file mode 100644
index 0000000..3f00271
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Core subsystem for tracking high-level intents for treatment of selected
+ * network traffic.
+ */
+package org.onlab.onos.net.intent.impl;
\ No newline at end of file
diff --git a/core/net/src/main/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManager.java b/core/net/src/main/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManager.java
index 6e07c3e..4933322 100644
--- a/core/net/src/main/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManager.java
@@ -31,6 +31,8 @@
 import org.onlab.onos.net.link.LinkListener;
 import org.onlab.onos.net.link.LinkService;
 import org.onlab.onos.net.packet.DefaultOutboundPacket;
+import org.onlab.onos.net.packet.InboundPacket;
+import org.onlab.onos.net.packet.PacketContext;
 import org.onlab.onos.net.packet.PacketService;
 import org.onlab.onos.net.proxyarp.ProxyArpService;
 import org.onlab.packet.ARP;
@@ -43,7 +45,6 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Multimap;
 
-
 @Component(immediate = true)
 @Service
 public class ProxyArpManager implements ProxyArpService {
@@ -93,14 +94,14 @@
 
     @Override
     public boolean known(IpPrefix addr) {
-        checkNotNull(MAC_ADDR_NULL, addr);
+        checkNotNull(addr, MAC_ADDR_NULL);
         Set<Host> hosts = hostService.getHostsByIp(addr);
         return !hosts.isEmpty();
     }
 
     @Override
     public void reply(Ethernet eth) {
-        checkNotNull(REQUEST_NULL, eth);
+        checkNotNull(eth, REQUEST_NULL);
         checkArgument(eth.getEtherType() == Ethernet.TYPE_ARP,
                 REQUEST_NOT_ARP);
         ARP arp = (ARP) eth.getPayload();
@@ -128,7 +129,7 @@
 
         Ethernet arpReply = buildArpReply(dst, eth);
         // TODO: check send status with host service.
-        TrafficTreatment.Builder builder = new DefaultTrafficTreatment.Builder();
+        TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder();
         builder.setOutput(src.location().port());
         packetService.emit(new DefaultOutboundPacket(src.location().deviceId(),
                 builder.build(), ByteBuffer.wrap(arpReply.serialize())));
@@ -136,7 +137,7 @@
 
     @Override
     public void forward(Ethernet eth) {
-        checkNotNull(REQUEST_NULL, eth);
+        checkNotNull(eth, REQUEST_NULL);
         checkArgument(eth.getEtherType() == Ethernet.TYPE_ARP,
                 REQUEST_NOT_ARP);
         ARP arp = (ARP) eth.getPayload();
@@ -148,7 +149,7 @@
         if (h == null) {
             flood(eth);
         } else {
-            TrafficTreatment.Builder builder = new DefaultTrafficTreatment.Builder();
+            TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder();
             builder.setOutput(h.location().port());
             packetService.emit(new DefaultOutboundPacket(h.location().deviceId(),
                     builder.build(), ByteBuffer.wrap(eth.serialize())));
@@ -156,6 +157,23 @@
 
     }
 
+    @Override
+    public boolean handleArp(PacketContext context) {
+        InboundPacket pkt = context.inPacket();
+        Ethernet ethPkt = pkt.parsed();
+        if (ethPkt.getEtherType() == Ethernet.TYPE_ARP) {
+            ARP arp = (ARP) ethPkt.getPayload();
+            if (arp.getOpCode() == ARP.OP_REPLY) {
+                forward(ethPkt);
+            } else if (arp.getOpCode() == ARP.OP_REQUEST) {
+                reply(ethPkt);
+            }
+            context.block();
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Flood the arp request at all edges in the network.
      * @param request the arp request.
@@ -166,7 +184,7 @@
 
         synchronized (externalPorts) {
             for (Entry<Device, PortNumber> entry : externalPorts.entries()) {
-                builder = new DefaultTrafficTreatment.Builder();
+                builder = DefaultTrafficTreatment.builder();
                 builder.setOutput(entry.getValue());
                 packetService.emit(new DefaultOutboundPacket(entry.getKey().id(),
                         builder.build(), buf));
@@ -188,12 +206,12 @@
             for (Link l : links) {
                 // for each link, mark the concerned ports as internal
                 // and the remaining ports are therefore external.
-                if (l.src().deviceId().equals(d)
+                if (l.src().deviceId().equals(d.id())
                         && ports.contains(l.src().port())) {
                     ports.remove(l.src().port());
                     internalPorts.put(d, l.src().port());
                 }
-                if (l.dst().deviceId().equals(d)
+                if (l.dst().deviceId().equals(d.id())
                         && ports.contains(l.dst().port())) {
                     ports.remove(l.dst().port());
                     internalPorts.put(d, l.dst().port());
@@ -322,7 +340,6 @@
 
         }
 
-}
-
+    }
 
 }
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 ea47b0c..e833b4a 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
@@ -6,8 +6,15 @@
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
+import org.onlab.onos.cluster.ClusterEventListener;
+import org.onlab.onos.cluster.ClusterService;
+import org.onlab.onos.cluster.ControllerNode;
+import org.onlab.onos.cluster.DefaultControllerNode;
 import org.onlab.onos.cluster.MastershipServiceAdapter;
+import org.onlab.onos.cluster.MastershipTerm;
+import org.onlab.onos.cluster.MastershipTermService;
 import org.onlab.onos.cluster.NodeId;
+import org.onlab.onos.cluster.ControllerNode.State;
 import org.onlab.onos.event.Event;
 import org.onlab.onos.event.impl.TestEventDispatcher;
 import org.onlab.onos.net.Device;
@@ -28,7 +35,9 @@
 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.store.ClockProviderService;
 import org.onlab.onos.store.trivial.impl.SimpleDeviceStore;
+import org.onlab.packet.IpPrefix;
 
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -57,6 +66,8 @@
     private static final PortNumber P1 = PortNumber.portNumber(1);
     private static final PortNumber P2 = PortNumber.portNumber(2);
     private static final PortNumber P3 = PortNumber.portNumber(3);
+    private static final NodeId NID_LOCAL = new NodeId("local");
+    private static final IpPrefix LOCALHOST = IpPrefix.valueOf("127.0.0.1");
 
     private DeviceManager mgr;
 
@@ -76,6 +87,8 @@
         mgr.store = new SimpleDeviceStore();
         mgr.eventDispatcher = new TestEventDispatcher();
         mgr.mastershipService = new TestMastershipService();
+        mgr.clusterService = new TestClusterService();
+        mgr.clockProviderService = new TestClockProviderService();
         mgr.activate();
 
         service.addListener(listener);
@@ -275,6 +288,59 @@
         public MastershipRole requestRoleFor(DeviceId deviceId) {
             return MastershipRole.MASTER;
         }
+
+        @Override
+        public MastershipTermService requestTermService() {
+            return new MastershipTermService() {
+                @Override
+                public MastershipTerm getMastershipTerm(DeviceId deviceId) {
+                    // FIXME: just returning something not null
+                    return MastershipTerm.of(NID_LOCAL, 1);
+                }
+            };
+        }
     }
 
+    // code clone
+    private final class TestClusterService implements ClusterService {
+
+        ControllerNode local = new DefaultControllerNode(NID_LOCAL, LOCALHOST);
+
+        @Override
+        public ControllerNode getLocalNode() {
+            return local;
+        }
+
+        @Override
+        public Set<ControllerNode> getNodes() {
+            return null;
+        }
+
+        @Override
+        public ControllerNode getNode(NodeId nodeId) {
+            return null;
+        }
+
+        @Override
+        public State getState(NodeId nodeId) {
+            return null;
+        }
+
+        @Override
+        public void addListener(ClusterEventListener listener) {
+        }
+
+        @Override
+        public void removeListener(ClusterEventListener listener) {
+        }
+    }
+
+    private final class TestClockProviderService implements
+            ClockProviderService {
+
+        @Override
+        public void setMastershipTerm(DeviceId deviceId, MastershipTerm term) {
+            // TODO Auto-generated method stub
+        }
+    }
 }
diff --git a/core/net/src/test/java/org/onlab/onos/net/device/impl/DistributedDeviceManagerTest.java b/core/net/src/test/java/org/onlab/onos/net/device/impl/DistributedDeviceManagerTest.java
deleted file mode 100644
index 10e9b39..0000000
--- a/core/net/src/test/java/org/onlab/onos/net/device/impl/DistributedDeviceManagerTest.java
+++ /dev/null
@@ -1,355 +0,0 @@
-package org.onlab.onos.net.device.impl;
-
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
-import com.hazelcast.config.Config;
-import com.hazelcast.core.Hazelcast;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.onlab.onos.cluster.DefaultControllerNode;
-import org.onlab.onos.cluster.MastershipServiceAdapter;
-import org.onlab.onos.cluster.NodeId;
-import org.onlab.onos.event.Event;
-import org.onlab.onos.event.EventDeliveryService;
-import org.onlab.onos.event.impl.TestEventDispatcher;
-import org.onlab.onos.net.Device;
-import org.onlab.onos.net.DeviceId;
-import org.onlab.onos.net.MastershipRole;
-import org.onlab.onos.net.Port;
-import org.onlab.onos.net.PortNumber;
-import org.onlab.onos.net.device.DefaultDeviceDescription;
-import org.onlab.onos.net.device.DefaultPortDescription;
-import org.onlab.onos.net.device.DeviceAdminService;
-import org.onlab.onos.net.device.DeviceDescription;
-import org.onlab.onos.net.device.DeviceEvent;
-import org.onlab.onos.net.device.DeviceListener;
-import org.onlab.onos.net.device.DeviceProvider;
-import org.onlab.onos.net.device.DeviceProviderRegistry;
-import org.onlab.onos.net.device.DeviceProviderService;
-import org.onlab.onos.net.device.DeviceService;
-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.store.common.StoreManager;
-import org.onlab.onos.store.common.StoreService;
-import org.onlab.onos.store.common.TestStoreManager;
-import org.onlab.onos.store.device.impl.DistributedDeviceStore;
-import org.onlab.onos.store.serializers.KryoSerializationManager;
-import org.onlab.onos.store.serializers.KryoSerializationService;
-import org.onlab.packet.IpPrefix;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-import static org.junit.Assert.*;
-import static org.onlab.onos.net.Device.Type.SWITCH;
-import static org.onlab.onos.net.DeviceId.deviceId;
-import static org.onlab.onos.net.device.DeviceEvent.Type.*;
-
-// FIXME This test is slow starting up Hazelcast on each test cases.
-// FIXME DistributedDeviceStore should have it's own test cases.
-
-/**
- * Test codifying the device service & device provider service contracts.
- */
-public class DistributedDeviceManagerTest {
-
-    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 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 PortNumber P1 = PortNumber.portNumber(1);
-    private static final PortNumber P2 = PortNumber.portNumber(2);
-    private static final PortNumber P3 = PortNumber.portNumber(3);
-
-    private static final DefaultControllerNode SELF
-        = new DefaultControllerNode(new NodeId("foobar"),
-                        IpPrefix.valueOf("127.0.0.1"));
-
-
-    private DeviceManager mgr;
-
-    protected StoreManager storeManager;
-    protected DeviceService service;
-    protected DeviceAdminService admin;
-    protected DeviceProviderRegistry registry;
-    protected DeviceProviderService providerService;
-    protected TestProvider provider;
-    protected TestListener listener = new TestListener();
-    private DistributedDeviceStore dstore;
-    private TestMastershipManager masterManager;
-    private EventDeliveryService eventService;
-    private KryoSerializationManager serializationMgr;
-
-    @Before
-    public void setUp() {
-        mgr = new DeviceManager();
-        service = mgr;
-        admin = mgr;
-        registry = mgr;
-        // TODO should find a way to clean Hazelcast instance without shutdown.
-        Config config = TestStoreManager.getTestConfig();
-
-        masterManager = new TestMastershipManager();
-
-        storeManager = new TestStoreManager(Hazelcast.newHazelcastInstance(config));
-        storeManager.activate();
-
-        serializationMgr = new KryoSerializationManager();
-        serializationMgr.activate();
-
-        dstore = new TestDistributedDeviceStore(storeManager, serializationMgr);
-        dstore.activate();
-
-        mgr.store = dstore;
-        eventService = new TestEventDispatcher();
-        mgr.eventDispatcher = eventService;
-        mgr.mastershipService = masterManager;
-        mgr.activate();
-
-        service.addListener(listener);
-
-        provider = new TestProvider();
-        providerService = registry.register(provider);
-        assertTrue("provider should be registered",
-                   registry.getProviders().contains(provider.id()));
-    }
-
-    @After
-    public void tearDown() {
-        registry.unregister(provider);
-        assertFalse("provider should not be registered",
-                    registry.getProviders().contains(provider.id()));
-        service.removeListener(listener);
-        mgr.deactivate();
-
-        dstore.deactivate();
-        serializationMgr.deactivate();
-        storeManager.deactivate();
-    }
-
-    private void connectDevice(DeviceId deviceId, String swVersion) {
-        DeviceDescription description =
-                new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR,
-                                             HW, swVersion, SN);
-        providerService.deviceConnected(deviceId, description);
-        assertNotNull("device should be found", service.getDevice(DID1));
-    }
-
-    @Test
-    public void deviceConnected() {
-        assertNull("device should not be found", service.getDevice(DID1));
-        connectDevice(DID1, SW1);
-        validateEvents(DEVICE_ADDED);
-
-        assertEquals("only one device expected", 1, Iterables.size(service.getDevices()));
-        Iterator<Device> it = service.getDevices().iterator();
-        assertNotNull("one device expected", it.next());
-        assertFalse("only one device expected", it.hasNext());
-
-        assertEquals("incorrect device count", 1, service.getDeviceCount());
-        assertTrue("device should be available", service.isAvailable(DID1));
-    }
-
-    @Test
-    public void deviceDisconnected() {
-        connectDevice(DID1, SW1);
-        connectDevice(DID2, SW1);
-        validateEvents(DEVICE_ADDED, DEVICE_ADDED);
-        assertTrue("device should be available", service.isAvailable(DID1));
-
-        // Disconnect
-        providerService.deviceDisconnected(DID1);
-        assertNotNull("device should not be found", service.getDevice(DID1));
-        assertFalse("device should not be available", service.isAvailable(DID1));
-        validateEvents(DEVICE_AVAILABILITY_CHANGED);
-
-        // Reconnect
-        connectDevice(DID1, SW1);
-        validateEvents(DEVICE_AVAILABILITY_CHANGED);
-
-        assertEquals("incorrect device count", 2, service.getDeviceCount());
-    }
-
-    @Test
-    public void deviceUpdated() {
-        connectDevice(DID1, SW1);
-        validateEvents(DEVICE_ADDED);
-
-        connectDevice(DID1, SW2);
-        validateEvents(DEVICE_UPDATED);
-    }
-
-    @Test
-    public void getRole() {
-        connectDevice(DID1, SW1);
-        assertEquals("incorrect role", MastershipRole.MASTER, service.getRole(DID1));
-    }
-
-    @Test
-    public void updatePorts() {
-        connectDevice(DID1, SW1);
-        List<PortDescription> pds = new ArrayList<>();
-        pds.add(new DefaultPortDescription(P1, true));
-        pds.add(new DefaultPortDescription(P2, true));
-        pds.add(new DefaultPortDescription(P3, true));
-        providerService.updatePorts(DID1, pds);
-        validateEvents(DEVICE_ADDED, PORT_ADDED, PORT_ADDED, PORT_ADDED);
-        pds.clear();
-
-        pds.add(new DefaultPortDescription(P1, false));
-        pds.add(new DefaultPortDescription(P3, true));
-        providerService.updatePorts(DID1, pds);
-        validateEvents(PORT_UPDATED, PORT_REMOVED);
-    }
-
-    @Test
-    public void updatePortStatus() {
-        connectDevice(DID1, SW1);
-        List<PortDescription> pds = new ArrayList<>();
-        pds.add(new DefaultPortDescription(P1, true));
-        pds.add(new DefaultPortDescription(P2, true));
-        providerService.updatePorts(DID1, pds);
-        validateEvents(DEVICE_ADDED, PORT_ADDED, PORT_ADDED);
-
-        providerService.portStatusChanged(DID1, new DefaultPortDescription(P1, false));
-        validateEvents(PORT_UPDATED);
-        providerService.portStatusChanged(DID1, new DefaultPortDescription(P1, false));
-        assertTrue("no events expected", listener.events.isEmpty());
-    }
-
-    @Test
-    public void getPorts() {
-        connectDevice(DID1, SW1);
-        List<PortDescription> pds = new ArrayList<>();
-        pds.add(new DefaultPortDescription(P1, true));
-        pds.add(new DefaultPortDescription(P2, true));
-        providerService.updatePorts(DID1, pds);
-        validateEvents(DEVICE_ADDED, PORT_ADDED, PORT_ADDED);
-        assertEquals("wrong port count", 2, service.getPorts(DID1).size());
-
-        Port port = service.getPort(DID1, P1);
-        assertEquals("incorrect port", P1, service.getPort(DID1, P1).number());
-        assertEquals("incorrect state", true, service.getPort(DID1, P1).isEnabled());
-    }
-
-    @Test
-    public void removeDevice() {
-        connectDevice(DID1, SW1);
-        connectDevice(DID2, SW2);
-        assertEquals("incorrect device count", 2, service.getDeviceCount());
-        admin.removeDevice(DID1);
-        validateEvents(DEVICE_ADDED, DEVICE_ADDED, DEVICE_REMOVED);
-        assertNull("device should not be found", service.getDevice(DID1));
-        assertNotNull("device should be found", service.getDevice(DID2));
-        assertEquals("incorrect device count", 1, service.getDeviceCount());
-    }
-
-    protected void validateEvents(Enum... types) {
-        for (Enum type : types) {
-            try {
-                Event event = listener.events.poll(1, TimeUnit.SECONDS);
-                assertNotNull("Timed out waiting for " + event, event);
-                assertEquals("incorrect event type", type, event.type());
-            } catch (InterruptedException e) {
-                fail("Unexpected interrupt");
-            }
-        }
-        assertTrue("Unexpected events left", listener.events.isEmpty());
-        listener.events.clear();
-    }
-
-
-    private class TestProvider extends AbstractProvider implements DeviceProvider {
-        private Device deviceReceived;
-        private MastershipRole roleReceived;
-
-        public TestProvider() {
-            super(PID);
-        }
-
-        @Override
-        public void triggerProbe(Device device) {
-        }
-
-        @Override
-        public void roleChanged(Device device, MastershipRole newRole) {
-            deviceReceived = device;
-            roleReceived = newRole;
-        }
-    }
-
-    private static class TestListener implements DeviceListener {
-        final BlockingQueue<DeviceEvent> events = new LinkedBlockingQueue<>();
-
-        @Override
-        public void event(DeviceEvent event) {
-            events.add(event);
-        }
-    }
-
-    private class TestDistributedDeviceStore extends DistributedDeviceStore {
-
-        public TestDistributedDeviceStore(StoreService storeService,
-                                    KryoSerializationService kryoSerializationService) {
-            this.storeService = storeService;
-            this.kryoSerializationService = kryoSerializationService;
-        }
-    }
-
-    private static class TestMastershipManager extends MastershipServiceAdapter {
-
-        private ConcurrentMap<DeviceId, NodeId> masters = new ConcurrentHashMap<>();
-
-        public TestMastershipManager() {
-            // SELF master of all initially
-            masters.put(DID1, SELF.id());
-            masters.put(DID1, SELF.id());
-        }
-        @Override
-        public MastershipRole getLocalRole(DeviceId deviceId) {
-            return MastershipRole.MASTER;
-        }
-
-        @Override
-        public Set<DeviceId> getDevicesOf(NodeId nodeId) {
-            HashSet<DeviceId> set = Sets.newHashSet();
-            for (Entry<DeviceId, NodeId> e : masters.entrySet()) {
-                if (e.getValue().equals(nodeId)) {
-                    set.add(e.getKey());
-                }
-            }
-            return set;
-        }
-
-        @Override
-        public MastershipRole requestRoleFor(DeviceId deviceId) {
-            if (SELF.id().equals(masters.get(deviceId))) {
-                return MastershipRole.MASTER;
-            } else {
-                return MastershipRole.STANDBY;
-            }
-        }
-
-        @Override
-        public void relinquishMastership(DeviceId deviceId) {
-            masters.remove(deviceId, SELF.id());
-        }
-    }
-}
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 0b451c0..86f3ddc 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
@@ -12,6 +12,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.Future;
 
 import org.junit.After;
 import org.junit.Before;
@@ -27,9 +28,12 @@
 import org.onlab.onos.net.PortNumber;
 import org.onlab.onos.net.device.DeviceListener;
 import org.onlab.onos.net.device.DeviceService;
+import org.onlab.onos.net.flow.DefaultFlowEntry;
 import org.onlab.onos.net.flow.DefaultFlowRule;
+import org.onlab.onos.net.flow.FlowEntry;
+import org.onlab.onos.net.flow.FlowEntry.FlowEntryState;
 import org.onlab.onos.net.flow.FlowRule;
-import org.onlab.onos.net.flow.FlowRule.FlowRuleState;
+import org.onlab.onos.net.flow.FlowRuleBatchEntry;
 import org.onlab.onos.net.flow.FlowRuleEvent;
 import org.onlab.onos.net.flow.FlowRuleListener;
 import org.onlab.onos.net.flow.FlowRuleProvider;
@@ -40,6 +44,7 @@
 import org.onlab.onos.net.flow.TrafficTreatment;
 import org.onlab.onos.net.flow.criteria.Criterion;
 import org.onlab.onos.net.flow.instructions.Instruction;
+import org.onlab.onos.net.intent.BatchOperation;
 import org.onlab.onos.net.provider.AbstractProvider;
 import org.onlab.onos.net.provider.ProviderId;
 import org.onlab.onos.store.trivial.impl.SimpleFlowRuleStore;
@@ -100,12 +105,9 @@
     private FlowRule flowRule(int tsval, int trval) {
         TestSelector ts = new TestSelector(tsval);
         TestTreatment tr = new TestTreatment(trval);
-        return new DefaultFlowRule(DID, ts, tr, 0, appId, TIMEOUT);
+        return new DefaultFlowRule(DID, ts, tr, 10, appId, TIMEOUT);
     }
 
-    private FlowRule flowRule(FlowRule rule, FlowRuleState state) {
-        return new DefaultFlowRule(rule, state);
-    }
 
     private FlowRule addFlowRule(int hval) {
         FlowRule rule = flowRule(hval, hval);
@@ -143,24 +145,26 @@
         FlowRule f1 = addFlowRule(1);
         FlowRule f2 = addFlowRule(2);
 
+        FlowEntry fe1 = new DefaultFlowEntry(f1);
+        FlowEntry fe2 = new DefaultFlowEntry(f2);
         assertEquals("2 rules should exist", 2, flowCount());
 
-        providerService.pushFlowMetrics(DID, ImmutableList.of(f1, f2));
+        providerService.pushFlowMetrics(DID, ImmutableList.of(fe1, fe2));
         validateEvents(RULE_ADDED, RULE_ADDED);
 
         addFlowRule(1);
         assertEquals("should still be 2 rules", 2, flowCount());
 
-        providerService.pushFlowMetrics(DID, ImmutableList.of(f1));
+        providerService.pushFlowMetrics(DID, ImmutableList.of(fe1));
         validateEvents(RULE_UPDATED);
     }
 
 
     //backing store is sensitive to the order of additions/removals
-    private boolean validateState(FlowRuleState... state) {
-        Iterable<FlowRule> rules = service.getFlowEntries(DID);
+    private boolean validateState(FlowEntryState... state) {
+        Iterable<FlowEntry> rules = service.getFlowEntries(DID);
         int i = 0;
-        for (FlowRule f : rules) {
+        for (FlowEntry f : rules) {
             if (f.state() != state[i]) {
                 return false;
             }
@@ -181,8 +185,8 @@
         mgr.applyFlowRules(r1, r2, r3);
         assertEquals("3 rules should exist", 3, flowCount());
         assertTrue("Entries should be pending add.",
-                validateState(FlowRuleState.PENDING_ADD, FlowRuleState.PENDING_ADD,
-                        FlowRuleState.PENDING_ADD));
+                validateState(FlowEntryState.PENDING_ADD, FlowEntryState.PENDING_ADD,
+                        FlowEntryState.PENDING_ADD));
     }
 
     @Test
@@ -192,20 +196,21 @@
         FlowRule f3 = addFlowRule(3);
         assertEquals("3 rules should exist", 3, flowCount());
 
-        providerService.pushFlowMetrics(DID, ImmutableList.of(f1, f2, f3));
+        FlowEntry fe1 = new DefaultFlowEntry(f1);
+        FlowEntry fe2 = new DefaultFlowEntry(f2);
+        FlowEntry fe3 = new DefaultFlowEntry(f3);
+        providerService.pushFlowMetrics(DID, ImmutableList.of(fe1, fe2, fe3));
         validateEvents(RULE_ADDED, RULE_ADDED, RULE_ADDED);
 
-        FlowRule rem1 = flowRule(f1, FlowRuleState.REMOVED);
-        FlowRule rem2 = flowRule(f2, FlowRuleState.REMOVED);
-        mgr.removeFlowRules(rem1, rem2);
+        mgr.removeFlowRules(f1, f2);
         //removing from north, so no events generated
         validateEvents();
         assertEquals("3 rule should exist", 3, flowCount());
         assertTrue("Entries should be pending remove.",
-                validateState(FlowRuleState.CREATED, FlowRuleState.PENDING_REMOVE,
-                        FlowRuleState.PENDING_REMOVE));
+                validateState(FlowEntryState.PENDING_REMOVE, FlowEntryState.PENDING_REMOVE,
+                        FlowEntryState.ADDED));
 
-        mgr.removeFlowRules(rem1);
+        mgr.removeFlowRules(f1);
         assertEquals("3 rule should still exist", 3, flowCount());
     }
 
@@ -213,21 +218,24 @@
     public void flowRemoved() {
         FlowRule f1 = addFlowRule(1);
         FlowRule f2 = addFlowRule(2);
-        providerService.pushFlowMetrics(f1.deviceId(), ImmutableList.of(f1, f2));
+        FlowEntry fe1 = new DefaultFlowEntry(f1);
+        FlowEntry fe2 = new DefaultFlowEntry(f2);
+        providerService.pushFlowMetrics(DID, ImmutableList.of(fe1, fe2));
         service.removeFlowRules(f1);
-        FlowRule rem1 = flowRule(f1, FlowRuleState.REMOVED);
-        providerService.flowRemoved(rem1);
+        fe1.setState(FlowEntryState.REMOVED);
+        providerService.flowRemoved(fe1);
         validateEvents(RULE_ADDED, RULE_ADDED, RULE_REMOVED);
 
-        providerService.flowRemoved(rem1);
+        providerService.flowRemoved(fe1);
         validateEvents();
 
         FlowRule f3 = flowRule(3, 3);
+        FlowEntry fe3 = new DefaultFlowEntry(f3);
         service.applyFlowRules(f3);
-        providerService.pushFlowMetrics(f3.deviceId(), Collections.singletonList(f3));
+        providerService.pushFlowMetrics(DID, Collections.singletonList(fe3));
         validateEvents(RULE_ADDED);
 
-        providerService.flowRemoved(f3);
+        providerService.flowRemoved(fe3);
         validateEvents();
     }
 
@@ -237,17 +245,20 @@
         FlowRule f2 = flowRule(2, 2);
         FlowRule f3 = flowRule(3, 3);
 
-
-
         mgr.applyFlowRules(f1, f2, f3);
-        FlowRule updatedF1 = flowRule(f1, FlowRuleState.ADDED);
-        FlowRule updatedF2 = flowRule(f2, FlowRuleState.ADDED);
 
-        providerService.pushFlowMetrics(DID, Lists.newArrayList(updatedF1, updatedF2));
+        FlowEntry fe1 = new DefaultFlowEntry(f1);
+        FlowEntry fe2 = new DefaultFlowEntry(f2);
+
+
+        //FlowRule updatedF1 = flowRule(f1, FlowRuleState.ADDED);
+        //FlowRule updatedF2 = flowRule(f2, FlowRuleState.ADDED);
+
+        providerService.pushFlowMetrics(DID, Lists.newArrayList(fe1, fe2));
 
         assertTrue("Entries should be added.",
-                validateState(FlowRuleState.PENDING_ADD, FlowRuleState.ADDED,
-                        FlowRuleState.ADDED));
+                validateState(FlowEntryState.ADDED, FlowEntryState.ADDED,
+                        FlowEntryState.PENDING_ADD));
 
         validateEvents(RULE_ADDED, RULE_ADDED);
     }
@@ -259,11 +270,15 @@
         FlowRule f3 = flowRule(3, 3);
         mgr.applyFlowRules(f1, f2);
 
-        FlowRule updatedF1 = flowRule(f1, FlowRuleState.ADDED);
-        FlowRule updatedF2 = flowRule(f2, FlowRuleState.ADDED);
-        FlowRule updatedF3 = flowRule(f3, FlowRuleState.ADDED);
+//        FlowRule updatedF1 = flowRule(f1, FlowRuleState.ADDED);
+//        FlowRule updatedF2 = flowRule(f2, FlowRuleState.ADDED);
+//        FlowRule updatedF3 = flowRule(f3, FlowRuleState.ADDED);
+        FlowEntry fe1 = new DefaultFlowEntry(f1);
+        FlowEntry fe2 = new DefaultFlowEntry(f2);
+        FlowEntry fe3 = new DefaultFlowEntry(f3);
 
-        providerService.pushFlowMetrics(DID, Lists.newArrayList(updatedF1, updatedF2, updatedF3));
+
+        providerService.pushFlowMetrics(DID, Lists.newArrayList(fe1, fe2, fe3));
 
         validateEvents(RULE_ADDED, RULE_ADDED);
 
@@ -279,13 +294,16 @@
         FlowRule f2 = flowRule(2, 2);
         FlowRule f3 = flowRule(3, 3);
 
-        FlowRule updatedF1 = flowRule(f1, FlowRuleState.ADDED);
-        FlowRule updatedF2 = flowRule(f2, FlowRuleState.ADDED);
+//        FlowRule updatedF1 = flowRule(f1, FlowRuleState.ADDED);
+//        FlowRule updatedF2 = flowRule(f2, FlowRuleState.ADDED);
+
+        FlowEntry fe1 = new DefaultFlowEntry(f1);
+        FlowEntry fe2 = new DefaultFlowEntry(f2);
         mgr.applyFlowRules(f1, f2, f3);
 
         mgr.removeFlowRules(f3);
 
-        providerService.pushFlowMetrics(DID, Lists.newArrayList(updatedF1, updatedF2));
+        providerService.pushFlowMetrics(DID, Lists.newArrayList(fe1, fe2));
 
         validateEvents(RULE_ADDED, RULE_ADDED, RULE_REMOVED);
 
@@ -312,7 +330,7 @@
 
         //only check that we are in pending remove. Events and actual remove state will
         // be set by flowRemoved call.
-        validateState(FlowRuleState.PENDING_REMOVE, FlowRuleState.PENDING_REMOVE);
+        validateState(FlowEntryState.PENDING_REMOVE, FlowEntryState.PENDING_REMOVE);
     }
 
     private static class TestListener implements FlowRuleListener {
@@ -389,6 +407,13 @@
         public void removeRulesById(ApplicationId id, FlowRule... flowRules) {
         }
 
+        @Override
+        public Future<Void> executeBatch(
+                BatchOperation<FlowRuleBatchEntry> batch) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
 
     }
 
diff --git a/core/net/src/test/java/org/onlab/onos/net/host/impl/HostMonitorTest.java b/core/net/src/test/java/org/onlab/onos/net/host/impl/HostMonitorTest.java
new file mode 100644
index 0000000..d8ddfbe
--- /dev/null
+++ b/core/net/src/test/java/org/onlab/onos/net/host/impl/HostMonitorTest.java
@@ -0,0 +1,223 @@
+package org.onlab.onos.net.host.impl;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.Device;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.Host;
+import org.onlab.onos.net.MastershipRole;
+import org.onlab.onos.net.Port;
+import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.device.DeviceListener;
+import org.onlab.onos.net.device.DeviceService;
+import org.onlab.onos.net.flow.instructions.Instruction;
+import org.onlab.onos.net.flow.instructions.Instructions.OutputInstruction;
+import org.onlab.onos.net.host.HostProvider;
+import org.onlab.onos.net.host.PortAddresses;
+import org.onlab.onos.net.packet.OutboundPacket;
+import org.onlab.onos.net.packet.PacketProcessor;
+import org.onlab.onos.net.packet.PacketService;
+import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.packet.ARP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+
+public class HostMonitorTest {
+
+    private IpAddress targetIpAddress = IpAddress.valueOf("10.0.0.1");
+    private IpPrefix targetIpPrefix = IpPrefix.valueOf(targetIpAddress.toOctets());
+
+    private IpPrefix sourcePrefix = IpPrefix.valueOf("10.0.0.99/24");
+    private MacAddress sourceMac = MacAddress.valueOf(1L);
+
+    private HostMonitor hostMonitor;
+
+    @Test
+    @Ignore
+    public void testMonitorHostExists() throws Exception {
+        ProviderId id = new ProviderId("fake://", "id");
+
+        Host host = createMock(Host.class);
+        expect(host.providerId()).andReturn(id);
+        replay(host);
+
+        HostManager hostManager = createMock(HostManager.class);
+        expect(hostManager.getHostsByIp(targetIpPrefix))
+                .andReturn(Collections.singleton(host));
+        replay(hostManager);
+
+        HostProvider hostProvider = createMock(HostProvider.class);
+        expect(hostProvider.id()).andReturn(id).anyTimes();
+        hostProvider.triggerProbe(host);
+        expectLastCall().once();
+        replay(hostProvider);
+
+        hostMonitor = new HostMonitor(null, null, hostManager);
+
+        hostMonitor.registerHostProvider(hostProvider);
+        hostMonitor.addMonitoringFor(targetIpAddress);
+
+        hostMonitor.run(null);
+
+        verify(hostProvider);
+    }
+
+    @Test
+    @Ignore
+    public void testMonitorHostDoesNotExist() throws Exception {
+        HostManager hostManager = createMock(HostManager.class);
+
+        DeviceId devId = DeviceId.deviceId("fake");
+
+        Device device = createMock(Device.class);
+        expect(device.id()).andReturn(devId).anyTimes();
+        replay(device);
+
+        PortNumber portNum = PortNumber.portNumber(1L);
+
+        Port port = createMock(Port.class);
+        expect(port.number()).andReturn(portNum).anyTimes();
+        replay(port);
+
+        TestDeviceService deviceService = new TestDeviceService();
+        deviceService.addDevice(device, Collections.singleton(port));
+
+        ConnectPoint cp = new ConnectPoint(devId, portNum);
+        PortAddresses pa = new PortAddresses(cp, Collections.singleton(sourcePrefix),
+                sourceMac);
+
+        expect(hostManager.getHostsByIp(targetIpPrefix))
+                .andReturn(Collections.<Host>emptySet()).anyTimes();
+        expect(hostManager.getAddressBindingsForPort(cp))
+                .andReturn(pa).anyTimes();
+        replay(hostManager);
+
+        TestPacketService packetService = new TestPacketService();
+
+
+        // Run the test
+        hostMonitor = new HostMonitor(deviceService, packetService, hostManager);
+
+        hostMonitor.addMonitoringFor(targetIpAddress);
+        hostMonitor.run(null);
+
+
+        // Check that a packet was sent to our PacketService and that it has
+        // the properties we expect
+        assertTrue(packetService.packets.size() == 1);
+        OutboundPacket packet = packetService.packets.get(0);
+
+        // Check the output port is correct
+        assertTrue(packet.treatment().instructions().size() == 1);
+        Instruction instruction = packet.treatment().instructions().get(0);
+        assertTrue(instruction instanceof OutputInstruction);
+        OutputInstruction oi = (OutputInstruction) instruction;
+        assertTrue(oi.port().equals(portNum));
+
+        // Check the output packet is correct (well the important bits anyway)
+        Ethernet eth = new Ethernet();
+        eth.deserialize(packet.data().array(), 0, packet.data().array().length);
+        ARP arp = (ARP) eth.getPayload();
+        assertTrue(Arrays.equals(arp.getSenderProtocolAddress(), sourcePrefix.toOctets()));
+        assertTrue(Arrays.equals(arp.getSenderHardwareAddress(), sourceMac.toBytes()));
+        assertTrue(Arrays.equals(arp.getTargetProtocolAddress(), targetIpPrefix.toOctets()));
+    }
+
+    class TestPacketService implements PacketService {
+
+        List<OutboundPacket> packets = new ArrayList<>();
+
+        @Override
+        public void addProcessor(PacketProcessor processor, int priority) {
+        }
+
+        @Override
+        public void removeProcessor(PacketProcessor processor) {
+        }
+
+        @Override
+        public void emit(OutboundPacket packet) {
+            packets.add(packet);
+        }
+    }
+
+    class TestDeviceService implements DeviceService {
+
+        List<Device> devices = Lists.newArrayList();
+        Multimap<DeviceId, Port> devicePorts = HashMultimap.create();
+
+        void addDevice(Device device, Set<Port> ports) {
+            devices.add(device);
+            for (Port p : ports) {
+                devicePorts.put(device.id(), p);
+            }
+        }
+
+        @Override
+        public int getDeviceCount() {
+            return 0;
+        }
+
+        @Override
+        public Iterable<Device> getDevices() {
+            return devices;
+        }
+
+        @Override
+        public Device getDevice(DeviceId deviceId) {
+            return null;
+        }
+
+        @Override
+        public MastershipRole getRole(DeviceId deviceId) {
+            return null;
+        }
+
+        @Override
+        public List<Port> getPorts(DeviceId deviceId) {
+            List<Port> ports = Lists.newArrayList();
+            for (Port p : devicePorts.get(deviceId)) {
+                ports.add(p);
+            }
+            return ports;
+        }
+
+        @Override
+        public Port getPort(DeviceId deviceId, PortNumber portNumber) {
+            return null;
+        }
+
+        @Override
+        public boolean isAvailable(DeviceId deviceId) {
+            return false;
+        }
+
+        @Override
+        public void addListener(DeviceListener listener) {
+        }
+
+        @Override
+        public void removeListener(DeviceListener listener) {
+        }
+    }
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/ClusterManagementMessageSubjects.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/ClusterManagementMessageSubjects.java
new file mode 100644
index 0000000..74c22f1
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/ClusterManagementMessageSubjects.java
@@ -0,0 +1,10 @@
+package org.onlab.onos.store.cluster.impl;
+
+import org.onlab.onos.store.cluster.messaging.MessageSubject;
+
+public final class ClusterManagementMessageSubjects {
+    // avoid instantiation
+    private ClusterManagementMessageSubjects() {}
+
+    public static final MessageSubject CLUSTER_MEMBERSHIP_EVENT = new MessageSubject("CLUSTER_MEMBERSHIP_EVENT");
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterMembershipEvent.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/ClusterMembershipEvent.java
similarity index 91%
rename from core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterMembershipEvent.java
rename to core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/ClusterMembershipEvent.java
index 961ed4f..30b847f 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterMembershipEvent.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/ClusterMembershipEvent.java
@@ -1,4 +1,4 @@
-package org.onlab.onos.store.cluster.messaging.impl;
+package org.onlab.onos.store.cluster.impl;
 
 import org.onlab.onos.cluster.ControllerNode;
 
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterMembershipEventType.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/ClusterMembershipEventType.java
similarity index 69%
rename from core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterMembershipEventType.java
rename to core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/ClusterMembershipEventType.java
index 1f5fd3f..cdfd145 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterMembershipEventType.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/ClusterMembershipEventType.java
@@ -1,4 +1,4 @@
-package org.onlab.onos.store.cluster.messaging.impl;
+package org.onlab.onos.store.cluster.impl;
 
 public enum ClusterMembershipEventType {
     NEW_MEMBER,
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/DistributedClusterStore.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/DistributedClusterStore.java
index 9408cc9..5e64a39 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/DistributedClusterStore.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/DistributedClusterStore.java
@@ -7,11 +7,9 @@
 import com.google.common.collect.ImmutableSet;
 
 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.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.ClusterStore;
 import org.onlab.onos.cluster.ClusterStoreDelegate;
@@ -37,8 +35,8 @@
 /**
  * Distributed implementation of the cluster nodes store.
  */
-@Component(immediate = true)
-@Service
+//@Component(immediate = true)
+//@Service
 public class DistributedClusterStore
         extends AbstractStore<ClusterEvent, ClusterStoreDelegate>
         implements ClusterStore {
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/MessageSerializer.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/MessageSerializer.java
deleted file mode 100644
index 98e80f7..0000000
--- a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/MessageSerializer.java
+++ /dev/null
@@ -1,117 +0,0 @@
-package org.onlab.onos.store.cluster.impl;
-
-import de.javakaffee.kryoserializers.URISerializer;
-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.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.Element;
-import org.onlab.onos.net.Link;
-import org.onlab.onos.net.LinkKey;
-import org.onlab.onos.net.MastershipRole;
-import org.onlab.onos.net.Port;
-import org.onlab.onos.net.PortNumber;
-import org.onlab.onos.net.provider.ProviderId;
-import org.onlab.onos.store.cluster.messaging.MessageSubject;
-import org.onlab.onos.store.cluster.messaging.SerializationService;
-import org.onlab.onos.store.serializers.ConnectPointSerializer;
-import org.onlab.onos.store.serializers.DefaultLinkSerializer;
-import org.onlab.onos.store.serializers.DefaultPortSerializer;
-import org.onlab.onos.store.serializers.DeviceIdSerializer;
-import org.onlab.onos.store.serializers.IpPrefixSerializer;
-import org.onlab.onos.store.serializers.LinkKeySerializer;
-import org.onlab.onos.store.serializers.NodeIdSerializer;
-import org.onlab.onos.store.serializers.PortNumberSerializer;
-import org.onlab.onos.store.serializers.ProviderIdSerializer;
-import org.onlab.packet.IpPrefix;
-import org.onlab.util.KryoPool;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.HashMap;
-
-/**
- * Factory for parsing messages sent between cluster members.
- */
-@Component(immediate = true)
-@Service
-public class MessageSerializer implements SerializationService {
-
-    private final Logger log = LoggerFactory.getLogger(getClass());
-
-    private static final int METADATA_LENGTH = 12; // 8 + 4
-    private static final int LENGTH_OFFSET = 8;
-
-    private static final long MARKER = 0xfeedcafebeaddeadL;
-
-    private KryoPool serializerPool;
-
-    @Activate
-    public void activate() {
-        setupKryoPool();
-        log.info("Started");
-    }
-
-    @Deactivate
-    public void deactivate() {
-        log.info("Stopped");
-    }
-
-    /**
-     * Sets up the common serialzers pool.
-     */
-    protected void setupKryoPool() {
-        // FIXME Slice out types used in common to separate pool/namespace.
-        serializerPool = KryoPool.newBuilder()
-                .register(ArrayList.class,
-                          HashMap.class,
-
-                          ControllerNode.State.class,
-                          Device.Type.class,
-
-                          DefaultControllerNode.class,
-                          DefaultDevice.class,
-                          MastershipRole.class,
-                          Port.class,
-                          Element.class,
-
-                          Link.Type.class,
-
-                          MessageSubject.class
-                )
-                .register(IpPrefix.class, new IpPrefixSerializer())
-                .register(URI.class, new URISerializer())
-                .register(NodeId.class, new NodeIdSerializer())
-                .register(ProviderId.class, new ProviderIdSerializer())
-                .register(DeviceId.class, new DeviceIdSerializer())
-                .register(PortNumber.class, new PortNumberSerializer())
-                .register(DefaultPort.class, new DefaultPortSerializer())
-                .register(LinkKey.class, new LinkKeySerializer())
-                .register(ConnectPoint.class, new ConnectPointSerializer())
-                .register(DefaultLink.class, new DefaultLinkSerializer())
-                .build()
-                .populate(1);
-    }
-
-
-    @Override
-    public Object decode(byte[] data) {
-        return serializerPool.deserialize(data);
-    }
-
-    @Override
-    public byte[] encode(Object payload) {
-        return serializerPool.serialize(payload);
-    }
-}
\ No newline at end of file
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterCommunicationAdminService.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterCommunicationAdminService.java
index 5966f12..0bc31fa 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterCommunicationAdminService.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterCommunicationAdminService.java
@@ -3,6 +3,8 @@
 import org.onlab.onos.cluster.ControllerNode;
 import org.onlab.onos.store.cluster.impl.ClusterNodesDelegate;
 
+// TODO: This service interface can be removed, once we properly start
+// using ClusterService
 /**
  * Service for administering communications manager.
  */
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterMessage.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterMessage.java
index ee558dd..b74f887 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterMessage.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterMessage.java
@@ -2,6 +2,7 @@
 
 import org.onlab.onos.cluster.NodeId;
 
+// TODO: Should payload type be ByteBuffer?
 /**
  * Base message for cluster-wide communications.
  */
@@ -9,14 +10,14 @@
 
     private final NodeId sender;
     private final MessageSubject subject;
-    private final Object payload;
+    private final byte[] payload;
 
     /**
      * Creates a cluster message.
      *
      * @param subject message subject
      */
-    public ClusterMessage(NodeId sender, MessageSubject subject, Object payload) {
+    public ClusterMessage(NodeId sender, MessageSubject subject, byte[] payload) {
         this.sender = sender;
         this.subject = subject;
         this.payload = payload;
@@ -45,7 +46,7 @@
      *
      * @return message payload.
      */
-    public Object payload() {
+    public byte[] payload() {
         return payload;
     }
 }
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterMessageHandler.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterMessageHandler.java
index 7ec27ec..4dd7bc2 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterMessageHandler.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterMessageHandler.java
@@ -10,4 +10,4 @@
      * @param message cluster message.
      */
     public void handle(ClusterMessage message);
-}
\ No newline at end of file
+}
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 ee8d9c1..43df15f 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
@@ -1,5 +1,9 @@
 package org.onlab.onos.store.cluster.messaging;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Objects;
+
 /**
  * Representation of a message subject.
  * Cluster messages have associated subjects that dictate how they get handled
@@ -10,7 +14,7 @@
     private final String value;
 
     public MessageSubject(String value) {
-        this.value = value;
+        this.value = checkNotNull(value);
     }
 
     public String value() {
@@ -21,4 +25,29 @@
     public String toString() {
         return value;
     }
+
+    @Override
+    public int hashCode() {
+        return value.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        MessageSubject that = (MessageSubject) obj;
+        return Objects.equals(this.value, that.value);
+    }
+
+    // for serializer
+    protected MessageSubject() {
+        this.value = "";
+    }
 }
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/SerializationService.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/SerializationService.java
index d85f488..4d76ce3 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/SerializationService.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/SerializationService.java
@@ -1,7 +1,7 @@
 package org.onlab.onos.store.cluster.messaging;
 
 /**
- * Service for encoding &amp; decoding intra-cluster messages.
+ * Service for encoding &amp; decoding intra-cluster message payload.
  */
 public interface SerializationService {
 
@@ -11,7 +11,7 @@
      * @param buffer byte buffer with message(s)
      * @return parsed message
      */
-    Object decode(byte[] data);
+    <T> T decode(byte[] data);
 
     /**
      * Encodes the specified message into the given byte buffer.
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterCommunicationManager.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterCommunicationManager.java
index d4fd9c0..1b11873 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterCommunicationManager.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterCommunicationManager.java
@@ -3,30 +3,36 @@
 import static com.google.common.base.Preconditions.checkArgument;
 
 import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
 import java.util.Set;
 import java.util.Timer;
 import java.util.TimerTask;
-
 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.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
+import org.onlab.onos.cluster.ClusterService;
 import org.onlab.onos.cluster.ControllerNode;
 import org.onlab.onos.cluster.NodeId;
+import org.onlab.onos.store.cluster.impl.ClusterMembershipEvent;
+import org.onlab.onos.store.cluster.impl.ClusterMembershipEventType;
 import org.onlab.onos.store.cluster.impl.ClusterNodesDelegate;
 import org.onlab.onos.store.cluster.messaging.ClusterCommunicationAdminService;
 import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService;
 import org.onlab.onos.store.cluster.messaging.ClusterMessage;
 import org.onlab.onos.store.cluster.messaging.ClusterMessageHandler;
 import org.onlab.onos.store.cluster.messaging.MessageSubject;
+import org.onlab.onos.store.serializers.ClusterMessageSerializer;
+import org.onlab.onos.store.serializers.KryoPoolUtil;
+import org.onlab.onos.store.serializers.KryoSerializer;
+import org.onlab.onos.store.serializers.MessageSubjectSerializer;
+import org.onlab.util.KryoPool;
 import org.onlab.netty.Endpoint;
 import org.onlab.netty.Message;
 import org.onlab.netty.MessageHandler;
 import org.onlab.netty.MessagingService;
+import org.onlab.netty.NettyMessagingService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -38,28 +44,57 @@
     private final Logger log = LoggerFactory.getLogger(getClass());
 
     private ControllerNode localNode;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private ClusterService clusterService;
+
     private ClusterNodesDelegate nodesDelegate;
-    private Map<NodeId, ControllerNode> members = new HashMap<>();
     private final Timer timer = new Timer("onos-controller-heatbeats");
     public static final long HEART_BEAT_INTERVAL_MILLIS = 1000L;
 
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    // TODO: This probably should not be a OSGi service.
     private MessagingService messagingService;
 
+    private static final KryoSerializer SERIALIZER = new KryoSerializer() {
+        @Override
+        protected void setupKryoPool() {
+            serializerPool = KryoPool.newBuilder()
+                    .register(KryoPoolUtil.API)
+                    .register(ClusterMessage.class, new ClusterMessageSerializer())
+                    .register(ClusterMembershipEvent.class)
+                    .register(byte[].class)
+                    .register(MessageSubject.class, new MessageSubjectSerializer())
+                    .build()
+                    .populate(1);
+        }
+
+    };
+
     @Activate
     public void activate() {
+        localNode = clusterService.getLocalNode();
+        NettyMessagingService netty = new NettyMessagingService(localNode.tcpPort());
+        // FIXME: workaround until it becomes a service.
+        try {
+            netty.activate();
+        } catch (Exception e) {
+            // TODO Auto-generated catch block
+            log.error("NettyMessagingService#activate", e);
+        }
+        messagingService = netty;
         log.info("Started");
     }
 
     @Deactivate
     public void deactivate() {
+        // TODO: cleanup messageingService if needed.
         log.info("Stopped");
     }
 
     @Override
     public boolean broadcast(ClusterMessage message) {
         boolean ok = true;
-        for (ControllerNode node : members.values()) {
+        for (ControllerNode node : clusterService.getNodes()) {
             if (!node.equals(localNode)) {
                 ok = unicast(message, node.id()) && ok;
             }
@@ -80,11 +115,12 @@
 
     @Override
     public boolean unicast(ClusterMessage message, NodeId toNodeId) {
-        ControllerNode node = members.get(toNodeId);
+        ControllerNode node = clusterService.getNode(toNodeId);
         checkArgument(node != null, "Unknown nodeId: %s", toNodeId);
         Endpoint nodeEp = new Endpoint(node.ip().toString(), node.tcpPort());
         try {
-            messagingService.sendAsync(nodeEp, message.subject().value(), message);
+            messagingService.sendAsync(nodeEp,
+                    message.subject().value(), SERIALIZER.encode(message));
             return true;
         } catch (IOException e) {
             log.error("Failed to send cluster message to nodeId: " + toNodeId, e);
@@ -110,7 +146,7 @@
 
     @Override
     public void addNode(ControllerNode node) {
-        members.put(node.id(), node);
+        //members.put(node.id(), node);
     }
 
     @Override
@@ -118,8 +154,8 @@
         broadcast(new ClusterMessage(
                 localNode.id(),
                 new MessageSubject("CLUSTER_MEMBERSHIP_EVENT"),
-                new ClusterMembershipEvent(ClusterMembershipEventType.LEAVING_MEMBER, node)));
-        members.remove(node.id());
+                SERIALIZER.encode(new ClusterMembershipEvent(ClusterMembershipEventType.LEAVING_MEMBER, node))));
+        //members.remove(node.id());
     }
 
     // Sends a heart beat to all peers.
@@ -130,7 +166,7 @@
             broadcast(new ClusterMessage(
                 localNode.id(),
                 new MessageSubject("CLUSTER_MEMBERSHIP_EVENT"),
-                new ClusterMembershipEvent(ClusterMembershipEventType.HEART_BEAT, localNode)));
+                SERIALIZER.encode(new ClusterMembershipEvent(ClusterMembershipEventType.HEART_BEAT, localNode))));
         }
     }
 
@@ -139,7 +175,7 @@
         @Override
         public void handle(ClusterMessage message) {
 
-            ClusterMembershipEvent event = (ClusterMembershipEvent) message.payload();
+            ClusterMembershipEvent event = SERIALIZER.decode(message.payload());
             ControllerNode node = event.node();
             if (event.type() == ClusterMembershipEventType.HEART_BEAT) {
                 log.info("Node {} sent a hearbeat", node.id());
@@ -154,7 +190,7 @@
         }
     }
 
-    private static class InternalClusterMessageHandler implements MessageHandler {
+    private final class InternalClusterMessageHandler implements MessageHandler {
 
         private final ClusterMessageHandler handler;
 
@@ -164,7 +200,13 @@
 
         @Override
         public void handle(Message message) {
-            handler.handle((ClusterMessage) message.payload());
+            try {
+                ClusterMessage clusterMessage = SERIALIZER.decode(message.payload());
+                handler.handle(clusterMessage);
+            } catch (Exception e) {
+                log.error("Exception caught during ClusterMessageHandler", e);
+                throw e;
+            }
         }
     }
 }
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterMessageSubjects.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterMessageSubjects.java
deleted file mode 100644
index d1f75ae..0000000
--- a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterMessageSubjects.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.onlab.onos.store.cluster.messaging.impl;
-
-import org.onlab.onos.store.cluster.messaging.MessageSubject;
-
-public final class ClusterMessageSubjects {
-    private ClusterMessageSubjects() {}
-    public static final MessageSubject CLUSTER_MEMBERSHIP_EVENT = new MessageSubject("CLUSTER_MEMBERSHIP_EVENT");
-}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/MessageSerializer.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/MessageSerializer.java
new file mode 100644
index 0000000..bf47f49
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/MessageSerializer.java
@@ -0,0 +1,63 @@
+package org.onlab.onos.store.cluster.messaging.impl;
+
+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.store.cluster.messaging.MessageSubject;
+import org.onlab.onos.store.cluster.messaging.SerializationService;
+import org.onlab.onos.store.serializers.KryoPoolUtil;
+import org.onlab.util.KryoPool;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Factory for parsing messages sent between cluster members.
+ */
+@Component(immediate = true)
+@Service
+public class MessageSerializer implements SerializationService {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final int METADATA_LENGTH = 12; // 8 + 4
+    private static final int LENGTH_OFFSET = 8;
+
+    private static final long MARKER = 0xfeedcafebeaddeadL;
+
+    private KryoPool serializerPool;
+
+    @Activate
+    public void activate() {
+        setupKryoPool();
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        log.info("Stopped");
+    }
+
+    /**
+     * Sets up the common serialzers pool.
+     */
+    protected void setupKryoPool() {
+        serializerPool = KryoPool.newBuilder()
+                .register(KryoPoolUtil.API)
+                // TODO: Should MessageSubject be in API bundle?
+                .register(MessageSubject.class)
+                .build()
+                .populate(1);
+    }
+
+
+    @Override
+    public <T> T decode(byte[] data) {
+        return serializerPool.deserialize(data);
+    }
+
+    @Override
+    public byte[] encode(Object payload) {
+        return serializerPool.serialize(payload);
+    }
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/package-info.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/package-info.java
new file mode 100644
index 0000000..6c1e71b
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Implementation of the cluster messaging mechanism.
+ */
+package org.onlab.onos.store.cluster.messaging.impl;
\ No newline at end of file
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/common/impl/AntiEntropyAdvertisement.java
similarity index 95%
rename from core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/AntiEntropyAdvertisement.java
rename to core/store/dist/src/main/java/org/onlab/onos/store/common/impl/AntiEntropyAdvertisement.java
index b70da73..132f27a 100644
--- 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/common/impl/AntiEntropyAdvertisement.java
@@ -1,4 +1,4 @@
-package org.onlab.onos.store.cluster.messaging;
+package org.onlab.onos.store.common.impl;
 
 import java.util.Map;
 
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/common/impl/AntiEntropyReply.java
similarity index 97%
rename from core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/AntiEntropyReply.java
rename to core/store/dist/src/main/java/org/onlab/onos/store/common/impl/AntiEntropyReply.java
index 095752b..033a1de 100644
--- 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/common/impl/AntiEntropyReply.java
@@ -1,4 +1,4 @@
-package org.onlab.onos.store.cluster.messaging;
+package org.onlab.onos.store.common.impl;
 
 import java.util.Map;
 import java.util.Set;
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/impl/OnosTimestamp.java b/core/store/dist/src/main/java/org/onlab/onos/store/common/impl/MastershipBasedTimestamp.java
similarity index 73%
rename from core/store/dist/src/main/java/org/onlab/onos/store/impl/OnosTimestamp.java
rename to core/store/dist/src/main/java/org/onlab/onos/store/common/impl/MastershipBasedTimestamp.java
index 2005582..0f4f894 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/impl/OnosTimestamp.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/common/impl/MastershipBasedTimestamp.java
@@ -1,4 +1,4 @@
-package org.onlab.onos.store.impl;
+package org.onlab.onos.store.common.impl;
 
 import static com.google.common.base.Preconditions.checkArgument;
 
@@ -9,12 +9,11 @@
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.ComparisonChain;
 
-// If it is store specific, implement serializable interfaces?
 /**
  * Default implementation of Timestamp.
  * TODO: Better documentation.
  */
-public final class OnosTimestamp implements Timestamp {
+public final class MastershipBasedTimestamp implements Timestamp {
 
     private final int termNumber;
     private final int sequenceNumber;
@@ -25,15 +24,16 @@
      * @param termNumber the mastership termNumber
      * @param sequenceNumber  the sequenceNumber number within the termNumber
      */
-    public OnosTimestamp(int termNumber, int sequenceNumber) {
+    public MastershipBasedTimestamp(int termNumber, int sequenceNumber) {
         this.termNumber = termNumber;
         this.sequenceNumber = sequenceNumber;
     }
 
     @Override
     public int compareTo(Timestamp o) {
-        checkArgument(o instanceof OnosTimestamp, "Must be OnosTimestamp", o);
-        OnosTimestamp that = (OnosTimestamp) o;
+        checkArgument(o instanceof MastershipBasedTimestamp,
+                "Must be MastershipBasedTimestamp", o);
+        MastershipBasedTimestamp that = (MastershipBasedTimestamp) o;
 
         return ComparisonChain.start()
                 .compare(this.termNumber, that.termNumber)
@@ -51,10 +51,10 @@
         if (this == obj) {
             return true;
         }
-        if (!(obj instanceof OnosTimestamp)) {
+        if (!(obj instanceof MastershipBasedTimestamp)) {
             return false;
         }
-        OnosTimestamp that = (OnosTimestamp) obj;
+        MastershipBasedTimestamp that = (MastershipBasedTimestamp) obj;
         return Objects.equals(this.termNumber, that.termNumber) &&
                 Objects.equals(this.sequenceNumber, that.sequenceNumber);
     }
@@ -84,4 +84,11 @@
     public int sequenceNumber() {
         return sequenceNumber;
     }
+
+    // Default constructor for serialization
+    @Deprecated
+    protected MastershipBasedTimestamp() {
+        this.termNumber = -1;
+        this.sequenceNumber = -1;
+    }
 }
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/common/impl/Timestamped.java b/core/store/dist/src/main/java/org/onlab/onos/store/common/impl/Timestamped.java
new file mode 100644
index 0000000..77b0a87
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/common/impl/Timestamped.java
@@ -0,0 +1,89 @@
+package org.onlab.onos.store.common.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Objects;
+
+import org.onlab.onos.store.Timestamp;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Wrapper class to store Timestamped value.
+ * @param <T>
+ */
+public final class Timestamped<T> {
+
+    private final Timestamp timestamp;
+    private final T value;
+
+    /**
+     * Creates a time stamped value.
+     *
+     * @param value to be timestamp
+     * @param timestamp the timestamp
+     */
+    public Timestamped(T value, Timestamp timestamp) {
+        this.value = checkNotNull(value);
+        this.timestamp = checkNotNull(timestamp);
+    }
+
+    /**
+     * Returns the value.
+     * @return value
+     */
+    public T value() {
+        return value;
+    }
+
+    /**
+     * Returns the time stamp.
+     * @return time stamp
+     */
+    public Timestamp timestamp() {
+        return timestamp;
+    }
+
+    /**
+     * Tests if this timestamped value is newer than the other.
+     *
+     * @param other timestamped value
+     * @return true if this instance is newer.
+     */
+    public boolean isNewer(Timestamped<T> other) {
+        return this.timestamp.compareTo(checkNotNull(other).timestamp()) > 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return timestamp.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof Timestamped)) {
+            return false;
+        }
+        @SuppressWarnings("unchecked")
+        Timestamped<T> that = (Timestamped<T>) obj;
+        return Objects.equals(this.timestamp, that.timestamp);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                    .add("timestamp", timestamp)
+                    .add("value", value)
+                    .toString();
+    }
+
+    // Default constructor for serialization
+    @Deprecated
+    private Timestamped() {
+        this.value = null;
+        this.timestamp = null;
+    }
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/common/impl/package-info.java b/core/store/dist/src/main/java/org/onlab/onos/store/common/impl/package-info.java
new file mode 100644
index 0000000..992fd49
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/common/impl/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Common abstractions and facilities for implementing distributed store
+ * using gossip protocol.
+ */
+package org.onlab.onos.store.common.impl;
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
index 301884c..d05659b 100644
--- 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
@@ -8,7 +8,7 @@
 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;
+import org.onlab.onos.store.common.impl.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?
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
index 011713e..e7a4d0a 100644
--- 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
@@ -10,7 +10,7 @@
 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 org.onlab.onos.store.common.impl.AntiEntropyReply;
 
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/OnosClockService.java b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/DeviceClockManager.java
similarity index 81%
rename from core/store/dist/src/main/java/org/onlab/onos/store/device/impl/OnosClockService.java
rename to core/store/dist/src/main/java/org/onlab/onos/store/device/impl/DeviceClockManager.java
index a99482f..e1e3692 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/OnosClockService.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/DeviceClockManager.java
@@ -12,14 +12,18 @@
 import org.apache.felix.scr.annotations.Service;
 import org.onlab.onos.cluster.MastershipTerm;
 import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.store.ClockProviderService;
 import org.onlab.onos.store.ClockService;
 import org.onlab.onos.store.Timestamp;
-import org.onlab.onos.store.impl.OnosTimestamp;
+import org.onlab.onos.store.common.impl.MastershipBasedTimestamp;
 import org.slf4j.Logger;
 
+/**
+ * Clock service to issue Timestamp based on Device Mastership.
+ */
 @Component(immediate = true)
 @Service
-public class OnosClockService implements ClockService {
+public class DeviceClockManager implements ClockService, ClockProviderService {
 
     private final Logger log = getLogger(getClass());
 
@@ -43,7 +47,7 @@
         if (term == null) {
             throw new IllegalStateException("Requesting timestamp for a deviceId without mastership");
         }
-        return new OnosTimestamp(term.termNumber(), ticker.incrementAndGet());
+        return new MastershipBasedTimestamp(term.termNumber(), ticker.incrementAndGet());
     }
 
     @Override
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/GossipDeviceStore.java b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/GossipDeviceStore.java
new file mode 100644
index 0000000..f39413b
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/GossipDeviceStore.java
@@ -0,0 +1,945 @@
+package org.onlab.onos.store.device.impl;
+
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import org.apache.commons.lang3.concurrent.ConcurrentException;
+import org.apache.commons.lang3.concurrent.ConcurrentInitializer;
+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.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.onos.cluster.ClusterService;
+import org.onlab.onos.net.AnnotationsUtil;
+import org.onlab.onos.net.DefaultAnnotations;
+import org.onlab.onos.net.DefaultDevice;
+import org.onlab.onos.net.DefaultPort;
+import org.onlab.onos.net.Device;
+import org.onlab.onos.net.Device.Type;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.Port;
+import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.SparseAnnotations;
+import org.onlab.onos.net.device.DefaultDeviceDescription;
+import org.onlab.onos.net.device.DefaultPortDescription;
+import org.onlab.onos.net.device.DeviceDescription;
+import org.onlab.onos.net.device.DeviceEvent;
+import org.onlab.onos.net.device.DeviceStore;
+import org.onlab.onos.net.device.DeviceStoreDelegate;
+import org.onlab.onos.net.device.PortDescription;
+import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.onos.store.AbstractStore;
+import org.onlab.onos.store.ClockService;
+import org.onlab.onos.store.Timestamp;
+import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService;
+import org.onlab.onos.store.cluster.messaging.ClusterMessage;
+import org.onlab.onos.store.cluster.messaging.ClusterMessageHandler;
+import org.onlab.onos.store.common.impl.MastershipBasedTimestamp;
+import org.onlab.onos.store.common.impl.Timestamped;
+import org.onlab.onos.store.serializers.KryoPoolUtil;
+import org.onlab.onos.store.serializers.KryoSerializer;
+import org.onlab.onos.store.serializers.MastershipBasedTimestampSerializer;
+import org.onlab.util.KryoPool;
+import org.onlab.util.NewConcurrentHashMap;
+import org.slf4j.Logger;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Predicates.notNull;
+import static org.onlab.onos.net.device.DeviceEvent.Type.*;
+import static org.slf4j.LoggerFactory.getLogger;
+import static org.apache.commons.lang3.concurrent.ConcurrentUtils.createIfAbsentUnchecked;
+import static org.onlab.onos.net.DefaultAnnotations.merge;
+import static org.onlab.onos.net.DefaultAnnotations.union;
+import static com.google.common.base.Verify.verify;
+
+// TODO: give me a better name
+/**
+ * Manages inventory of infrastructure devices using gossip protocol to distribute
+ * information.
+ */
+@Component(immediate = true)
+@Service
+public class GossipDeviceStore
+        extends AbstractStore<DeviceEvent, DeviceStoreDelegate>
+        implements DeviceStore {
+
+    private final Logger log = getLogger(getClass());
+
+    public static final String DEVICE_NOT_FOUND = "Device with ID %s not found";
+
+    // TODO: Check if inner Map can be replaced with plain Map
+    // innerMap is used to lock a Device, thus instance should never be replaced.
+    // collection of Description given from various providers
+    private final ConcurrentMap<DeviceId,
+                            ConcurrentMap<ProviderId, DeviceDescriptions>>
+                                deviceDescs = Maps.newConcurrentMap();
+
+    // cache of Device and Ports generated by compositing descriptions from providers
+    private final ConcurrentMap<DeviceId, Device> devices = Maps.newConcurrentMap();
+    private final ConcurrentMap<DeviceId, ConcurrentMap<PortNumber, Port>> devicePorts = Maps.newConcurrentMap();
+
+    // to be updated under Device lock
+    private final Map<DeviceId, Timestamp> offline = Maps.newHashMap();
+    private final Map<DeviceId, Timestamp> removalRequest = Maps.newHashMap();
+
+    // available(=UP) devices
+    private final Set<DeviceId> availableDevices = Sets.newConcurrentHashSet();
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ClockService clockService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ClusterCommunicationService clusterCommunicator;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ClusterService clusterService;
+
+    private static final KryoSerializer SERIALIZER = new KryoSerializer() {
+        @Override
+        protected void setupKryoPool() {
+            serializerPool = KryoPool.newBuilder()
+                    .register(KryoPoolUtil.API)
+                    .register(InternalDeviceEvent.class, new InternalDeviceEventSerializer())
+                    .register(InternalDeviceOfflineEvent.class, new InternalDeviceOfflineEventSerializer())
+                    .register(InternalDeviceRemovedEvent.class)
+                    .register(InternalPortEvent.class, new InternalPortEventSerializer())
+                    .register(InternalPortStatusEvent.class, new InternalPortStatusEventSerializer())
+                    .register(Timestamp.class)
+                    .register(Timestamped.class)
+                    .register(MastershipBasedTimestamp.class, new MastershipBasedTimestampSerializer())
+                    .build()
+                    .populate(1);
+        }
+
+    };
+
+    @Activate
+    public void activate() {
+        clusterCommunicator.addSubscriber(
+                GossipDeviceStoreMessageSubjects.DEVICE_UPDATE, new InternalDeviceEventListener());
+        clusterCommunicator.addSubscriber(
+                GossipDeviceStoreMessageSubjects.DEVICE_OFFLINE, new InternalDeviceOfflineEventListener());
+        clusterCommunicator.addSubscriber(
+                GossipDeviceStoreMessageSubjects.DEVICE_REMOVED, new InternalDeviceRemovedEventListener());
+        clusterCommunicator.addSubscriber(
+                GossipDeviceStoreMessageSubjects.PORT_UPDATE, new InternalPortEventListener());
+        clusterCommunicator.addSubscriber(
+                GossipDeviceStoreMessageSubjects.PORT_STATUS_UPDATE, new InternalPortStatusEventListener());
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        deviceDescs.clear();
+        devices.clear();
+        devicePorts.clear();
+        availableDevices.clear();
+        log.info("Stopped");
+    }
+
+    @Override
+    public int getDeviceCount() {
+        return devices.size();
+    }
+
+    @Override
+    public Iterable<Device> getDevices() {
+        return Collections.unmodifiableCollection(devices.values());
+    }
+
+    @Override
+    public Device getDevice(DeviceId deviceId) {
+        return devices.get(deviceId);
+    }
+
+    @Override
+    public synchronized DeviceEvent createOrUpdateDevice(ProviderId providerId,
+                                     DeviceId deviceId,
+                                     DeviceDescription deviceDescription) {
+        Timestamp newTimestamp = clockService.getTimestamp(deviceId);
+        final Timestamped<DeviceDescription> deltaDesc = new Timestamped<>(deviceDescription, newTimestamp);
+        DeviceEvent event = createOrUpdateDeviceInternal(providerId, deviceId, deltaDesc);
+        if (event != null) {
+            log.info("Notifying peers of a device update topology event for providerId: {} and deviceId: {}",
+                providerId, deviceId);
+            try {
+                notifyPeers(new InternalDeviceEvent(providerId, deviceId, deltaDesc));
+            } catch (IOException e) {
+                log.error("Failed to notify peers of a device update topology event for providerId: "
+                        + providerId + " and deviceId: " + deviceId, e);
+            }
+        }
+        return event;
+    }
+
+    private DeviceEvent createOrUpdateDeviceInternal(ProviderId providerId,
+                                    DeviceId deviceId,
+                                    Timestamped<DeviceDescription> deltaDesc) {
+
+        // Collection of DeviceDescriptions for a Device
+        ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs
+            = getDeviceDescriptions(deviceId);
+
+        synchronized (providerDescs) {
+            // locking per device
+
+            if (isDeviceRemoved(deviceId, deltaDesc.timestamp())) {
+                log.debug("Ignoring outdated event: {}", deltaDesc);
+                return null;
+            }
+
+            DeviceDescriptions descs
+                = createIfAbsentUnchecked(providerDescs, providerId,
+                    new InitDeviceDescs(deltaDesc));
+
+            final Device oldDevice = devices.get(deviceId);
+            final Device newDevice;
+
+            if (deltaDesc == descs.getDeviceDesc() ||
+                deltaDesc.isNewer(descs.getDeviceDesc())) {
+                // on new device or valid update
+                descs.putDeviceDesc(deltaDesc);
+                newDevice = composeDevice(deviceId, providerDescs);
+            } else {
+                // outdated event, ignored.
+                return null;
+            }
+            if (oldDevice == null) {
+                // ADD
+                return createDevice(providerId, newDevice, deltaDesc.timestamp());
+            } else {
+                // UPDATE or ignore (no change or stale)
+                return updateDevice(providerId, oldDevice, newDevice, deltaDesc.timestamp());
+            }
+        }
+    }
+
+    // Creates the device and returns the appropriate event if necessary.
+    // Guarded by deviceDescs value (=Device lock)
+    private DeviceEvent createDevice(ProviderId providerId,
+                                     Device newDevice, Timestamp timestamp) {
+
+        // update composed device cache
+        Device oldDevice = devices.putIfAbsent(newDevice.id(), newDevice);
+        verify(oldDevice == null,
+                "Unexpected Device in cache. PID:%s [old=%s, new=%s]",
+                providerId, oldDevice, newDevice);
+
+        if (!providerId.isAncillary()) {
+            markOnline(newDevice.id(), timestamp);
+        }
+
+        return new DeviceEvent(DeviceEvent.Type.DEVICE_ADDED, newDevice, null);
+    }
+
+    // Updates the device and returns the appropriate event if necessary.
+    // Guarded by deviceDescs value (=Device lock)
+    private DeviceEvent updateDevice(ProviderId providerId,
+                                     Device oldDevice,
+                                     Device newDevice, Timestamp newTimestamp) {
+
+        // We allow only certain attributes to trigger update
+        if (!Objects.equals(oldDevice.hwVersion(), newDevice.hwVersion()) ||
+            !Objects.equals(oldDevice.swVersion(), newDevice.swVersion()) ||
+            !AnnotationsUtil.isEqual(oldDevice.annotations(), newDevice.annotations())) {
+
+            boolean replaced = devices.replace(newDevice.id(), oldDevice, newDevice);
+            if (!replaced) {
+                verify(replaced,
+                        "Replacing devices cache failed. PID:%s [expected:%s, found:%s, new=%s]",
+                        providerId, oldDevice, devices.get(newDevice.id())
+                        , newDevice);
+            }
+            if (!providerId.isAncillary()) {
+                markOnline(newDevice.id(), newTimestamp);
+            }
+            return new DeviceEvent(DeviceEvent.Type.DEVICE_UPDATED, newDevice, null);
+        }
+
+        // Otherwise merely attempt to change availability if primary provider
+        if (!providerId.isAncillary()) {
+            boolean added = markOnline(newDevice.id(), newTimestamp);
+            return !added ? null :
+                    new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, newDevice, null);
+        }
+        return null;
+    }
+
+    @Override
+    public DeviceEvent markOffline(DeviceId deviceId) {
+        Timestamp timestamp = clockService.getTimestamp(deviceId);
+        DeviceEvent event = markOfflineInternal(deviceId, timestamp);
+        if (event != null) {
+            log.info("Notifying peers of a device offline topology event for deviceId: {}",
+                    deviceId);
+            try {
+                notifyPeers(new InternalDeviceOfflineEvent(deviceId, timestamp));
+            } catch (IOException e) {
+                log.error("Failed to notify peers of a device offline topology event for deviceId: {}",
+                     deviceId);
+            }
+        }
+        return event;
+    }
+
+    private DeviceEvent markOfflineInternal(DeviceId deviceId, Timestamp timestamp) {
+
+        Map<ProviderId, DeviceDescriptions> providerDescs
+            = getDeviceDescriptions(deviceId);
+
+        // locking device
+        synchronized (providerDescs) {
+
+            // accept off-line if given timestamp is newer than
+            // the latest Timestamp from Primary provider
+            DeviceDescriptions primDescs = getPrimaryDescriptions(providerDescs);
+            Timestamp lastTimestamp = primDescs.getLatestTimestamp();
+            if (timestamp.compareTo(lastTimestamp) <= 0) {
+                // outdated event ignore
+                return null;
+            }
+
+            offline.put(deviceId, timestamp);
+
+            Device device = devices.get(deviceId);
+            if (device == null) {
+                return null;
+            }
+            boolean removed = availableDevices.remove(deviceId);
+            if (removed) {
+                // TODO: broadcast ... DOWN only?
+                return new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device, null);
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Marks the device as available if the given timestamp is not outdated,
+     * compared to the time the device has been marked offline.
+     *
+     * @param deviceId identifier of the device
+     * @param timestamp of the event triggering this change.
+     * @return true if availability change request was accepted and changed the state
+     */
+    // Guarded by deviceDescs value (=Device lock)
+    private boolean markOnline(DeviceId deviceId, Timestamp timestamp) {
+        // accept on-line if given timestamp is newer than
+        // the latest offline request Timestamp
+        Timestamp offlineTimestamp = offline.get(deviceId);
+        if (offlineTimestamp == null ||
+            offlineTimestamp.compareTo(timestamp) < 0) {
+
+            offline.remove(deviceId);
+            return availableDevices.add(deviceId);
+        }
+        return false;
+    }
+
+    @Override
+    public synchronized List<DeviceEvent> updatePorts(ProviderId providerId,
+                                       DeviceId deviceId,
+                                       List<PortDescription> portDescriptions) {
+        Timestamp newTimestamp = clockService.getTimestamp(deviceId);
+
+        Timestamped<List<PortDescription>> timestampedPortDescriptions =
+            new Timestamped<>(portDescriptions, newTimestamp);
+
+        List<DeviceEvent> events = updatePortsInternal(providerId, deviceId, timestampedPortDescriptions);
+        if (!events.isEmpty()) {
+            log.info("Notifying peers of a port update topology event for providerId: {} and deviceId: {}",
+                    providerId, deviceId);
+            try {
+                notifyPeers(new InternalPortEvent(providerId, deviceId, timestampedPortDescriptions));
+            } catch (IOException e) {
+                log.error("Failed to notify peers of a port update topology event or providerId: "
+                    + providerId + " and deviceId: " + deviceId, e);
+            }
+        }
+        return events;
+    }
+
+    private List<DeviceEvent> updatePortsInternal(ProviderId providerId,
+                                DeviceId deviceId,
+                                Timestamped<List<PortDescription>> portDescriptions) {
+
+        Device device = devices.get(deviceId);
+        checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
+
+        ConcurrentMap<ProviderId, DeviceDescriptions> descsMap = deviceDescs.get(deviceId);
+        checkArgument(descsMap != null, DEVICE_NOT_FOUND, deviceId);
+
+        List<DeviceEvent> events = new ArrayList<>();
+        synchronized (descsMap) {
+
+            if (isDeviceRemoved(deviceId, portDescriptions.timestamp())) {
+                log.debug("Ignoring outdated events: {}", portDescriptions);
+                return null;
+            }
+
+            DeviceDescriptions descs = descsMap.get(providerId);
+            // every provider must provide DeviceDescription.
+            checkArgument(descs != null,
+                    "Device description for Device ID %s from Provider %s was not found",
+                    deviceId, providerId);
+
+            Map<PortNumber, Port> ports = getPortMap(deviceId);
+
+            final Timestamp newTimestamp = portDescriptions.timestamp();
+
+            // Add new ports
+            Set<PortNumber> processed = new HashSet<>();
+            for (PortDescription portDescription : portDescriptions.value()) {
+                final PortNumber number = portDescription.portNumber();
+                processed.add(number);
+
+                final Port oldPort = ports.get(number);
+                final Port newPort;
+
+
+                final Timestamped<PortDescription> existingPortDesc = descs.getPortDesc(number);
+                if (existingPortDesc == null ||
+                    newTimestamp.compareTo(existingPortDesc.timestamp()) >= 0) {
+                    // on new port or valid update
+                    // update description
+                    descs.putPortDesc(new Timestamped<>(portDescription,
+                                            portDescriptions.timestamp()));
+                    newPort = composePort(device, number, descsMap);
+                } else {
+                    // outdated event, ignored.
+                    continue;
+                }
+
+                events.add(oldPort == null ?
+                                   createPort(device, newPort, ports) :
+                                   updatePort(device, oldPort, newPort, ports));
+            }
+
+            events.addAll(pruneOldPorts(device, ports, processed));
+        }
+        return FluentIterable.from(events).filter(notNull()).toList();
+    }
+
+    // Creates a new port based on the port description adds it to the map and
+    // Returns corresponding event.
+    // Guarded by deviceDescs value (=Device lock)
+    private DeviceEvent createPort(Device device, Port newPort,
+                                   Map<PortNumber, Port> ports) {
+        ports.put(newPort.number(), newPort);
+        return new DeviceEvent(PORT_ADDED, device, newPort);
+    }
+
+    // Checks if the specified port requires update and if so, it replaces the
+    // existing entry in the map and returns corresponding event.
+    // Guarded by deviceDescs value (=Device lock)
+    private DeviceEvent updatePort(Device device, Port oldPort,
+                                   Port newPort,
+                                   Map<PortNumber, Port> ports) {
+        if (oldPort.isEnabled() != newPort.isEnabled() ||
+            !AnnotationsUtil.isEqual(oldPort.annotations(), newPort.annotations())) {
+
+            ports.put(oldPort.number(), newPort);
+            return new DeviceEvent(PORT_UPDATED, device, newPort);
+        }
+        return null;
+    }
+
+    // Prunes the specified list of ports based on which ports are in the
+    // processed list and returns list of corresponding events.
+    // Guarded by deviceDescs value (=Device lock)
+    private List<DeviceEvent> pruneOldPorts(Device device,
+                                            Map<PortNumber, Port> ports,
+                                            Set<PortNumber> processed) {
+        List<DeviceEvent> events = new ArrayList<>();
+        Iterator<PortNumber> iterator = ports.keySet().iterator();
+        while (iterator.hasNext()) {
+            PortNumber portNumber = iterator.next();
+            if (!processed.contains(portNumber)) {
+                events.add(new DeviceEvent(PORT_REMOVED, device,
+                                           ports.get(portNumber)));
+                iterator.remove();
+            }
+        }
+        return events;
+    }
+
+    // Gets the map of ports for the specified device; if one does not already
+    // exist, it creates and registers a new one.
+    private ConcurrentMap<PortNumber, Port> getPortMap(DeviceId deviceId) {
+        return createIfAbsentUnchecked(devicePorts, deviceId,
+                NewConcurrentHashMap.<PortNumber, Port>ifNeeded());
+    }
+
+    private ConcurrentMap<ProviderId, DeviceDescriptions> getDeviceDescriptions(
+            DeviceId deviceId) {
+        return createIfAbsentUnchecked(deviceDescs, deviceId,
+                NewConcurrentHashMap.<ProviderId, DeviceDescriptions>ifNeeded());
+    }
+
+    @Override
+    public synchronized DeviceEvent updatePortStatus(ProviderId providerId, DeviceId deviceId,
+            PortDescription portDescription) {
+        Timestamp newTimestamp = clockService.getTimestamp(deviceId);
+        final Timestamped<PortDescription> deltaDesc = new Timestamped<>(portDescription, newTimestamp);
+        DeviceEvent event = updatePortStatusInternal(providerId, deviceId, deltaDesc);
+        if (event != null) {
+            log.info("Notifying peers of a port status update topology event for providerId: {} and deviceId: {}",
+                        providerId, deviceId);
+            try {
+                notifyPeers(new InternalPortStatusEvent(providerId, deviceId, deltaDesc));
+            } catch (IOException e) {
+                log.error("Failed to notify peers of a port status update topology event or providerId: "
+                        + providerId + " and deviceId: " + deviceId, e);
+            }
+        }
+        return event;
+    }
+
+    private DeviceEvent updatePortStatusInternal(ProviderId providerId, DeviceId deviceId,
+                Timestamped<PortDescription> deltaDesc) {
+
+        Device device = devices.get(deviceId);
+        checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
+
+        ConcurrentMap<ProviderId, DeviceDescriptions> descsMap = deviceDescs.get(deviceId);
+        checkArgument(descsMap != null, DEVICE_NOT_FOUND, deviceId);
+
+        synchronized (descsMap) {
+
+            if (isDeviceRemoved(deviceId, deltaDesc.timestamp())) {
+                log.debug("Ignoring outdated event: {}", deltaDesc);
+                return null;
+            }
+
+            DeviceDescriptions descs = descsMap.get(providerId);
+            // assuming all providers must to give DeviceDescription
+            checkArgument(descs != null,
+                    "Device description for Device ID %s from Provider %s was not found",
+                    deviceId, providerId);
+
+            ConcurrentMap<PortNumber, Port> ports = getPortMap(deviceId);
+            final PortNumber number = deltaDesc.value().portNumber();
+            final Port oldPort = ports.get(number);
+            final Port newPort;
+
+            final Timestamped<PortDescription> existingPortDesc = descs.getPortDesc(number);
+            if (existingPortDesc == null ||
+                deltaDesc == existingPortDesc ||
+                deltaDesc.isNewer(existingPortDesc)) {
+                // on new port or valid update
+                // update description
+                descs.putPortDesc(deltaDesc);
+                newPort = composePort(device, number, descsMap);
+            } else {
+                // outdated event, ignored.
+                return null;
+            }
+
+            if (oldPort == null) {
+                return createPort(device, newPort, ports);
+            } else {
+                return updatePort(device, oldPort, newPort, ports);
+            }
+        }
+    }
+
+    @Override
+    public List<Port> getPorts(DeviceId deviceId) {
+        Map<PortNumber, Port> ports = devicePorts.get(deviceId);
+        if (ports == null) {
+            return Collections.emptyList();
+        }
+        return ImmutableList.copyOf(ports.values());
+    }
+
+    @Override
+    public Port getPort(DeviceId deviceId, PortNumber portNumber) {
+        Map<PortNumber, Port> ports = devicePorts.get(deviceId);
+        return ports == null ? null : ports.get(portNumber);
+    }
+
+    @Override
+    public boolean isAvailable(DeviceId deviceId) {
+        return availableDevices.contains(deviceId);
+    }
+
+    @Override
+    public synchronized DeviceEvent removeDevice(DeviceId deviceId) {
+        Timestamp timestamp = clockService.getTimestamp(deviceId);
+        DeviceEvent event = removeDeviceInternal(deviceId, timestamp);
+        if (event != null) {
+            log.info("Notifying peers of a device removed topology event for deviceId: {}",
+                    deviceId);
+            try {
+                notifyPeers(new InternalDeviceRemovedEvent(deviceId, timestamp));
+            } catch (IOException e) {
+                log.error("Failed to notify peers of a device removed topology event for deviceId: {}",
+                     deviceId);
+            }
+        }
+        return event;
+    }
+
+    private DeviceEvent removeDeviceInternal(DeviceId deviceId,
+                                             Timestamp timestamp) {
+
+        Map<ProviderId, DeviceDescriptions> descs = getDeviceDescriptions(deviceId);
+        synchronized (descs) {
+            // accept removal request if given timestamp is newer than
+            // the latest Timestamp from Primary provider
+            DeviceDescriptions primDescs = getPrimaryDescriptions(descs);
+            Timestamp lastTimestamp = primDescs.getLatestTimestamp();
+            if (timestamp.compareTo(lastTimestamp) <= 0) {
+                // outdated event ignore
+                return null;
+            }
+            removalRequest.put(deviceId, timestamp);
+
+            Device device = devices.remove(deviceId);
+            // should DEVICE_REMOVED carry removed ports?
+            Map<PortNumber, Port> ports = devicePorts.get(deviceId);
+            if (ports != null) {
+                ports.clear();
+            }
+            markOfflineInternal(deviceId, timestamp);
+            descs.clear();
+            return device == null ? null :
+                new DeviceEvent(DEVICE_REMOVED, device, null);
+        }
+    }
+
+    private boolean isDeviceRemoved(DeviceId deviceId, Timestamp timestampToCheck) {
+        Timestamp removalTimestamp = removalRequest.get(deviceId);
+        if (removalTimestamp != null &&
+            removalTimestamp.compareTo(timestampToCheck) >= 0) {
+            // removalRequest is more recent
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns a Device, merging description given from multiple Providers.
+     *
+     * @param deviceId device identifier
+     * @param providerDescs Collection of Descriptions from multiple providers
+     * @return Device instance
+     */
+    private Device composeDevice(DeviceId deviceId,
+            ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs) {
+
+        checkArgument(!providerDescs.isEmpty(), "No Device descriptions supplied");
+
+        ProviderId primary = pickPrimaryPID(providerDescs);
+
+        DeviceDescriptions desc = providerDescs.get(primary);
+
+        final DeviceDescription base = desc.getDeviceDesc().value();
+        Type type = base.type();
+        String manufacturer = base.manufacturer();
+        String hwVersion = base.hwVersion();
+        String swVersion = base.swVersion();
+        String serialNumber = base.serialNumber();
+        DefaultAnnotations annotations = DefaultAnnotations.builder().build();
+        annotations = merge(annotations, base.annotations());
+
+        for (Entry<ProviderId, DeviceDescriptions> e : providerDescs.entrySet()) {
+            if (e.getKey().equals(primary)) {
+                continue;
+            }
+            // TODO: should keep track of Description timestamp
+            // and only merge conflicting keys when timestamp is newer
+            // Currently assuming there will never be a key conflict between
+            // providers
+
+            // annotation merging. not so efficient, should revisit later
+            annotations = merge(annotations, e.getValue().getDeviceDesc().value().annotations());
+        }
+
+        return new DefaultDevice(primary, deviceId , type, manufacturer,
+                            hwVersion, swVersion, serialNumber, annotations);
+    }
+
+    /**
+     * Returns a Port, merging description given from multiple Providers.
+     *
+     * @param device device the port is on
+     * @param number port number
+     * @param providerDescs Collection of Descriptions from multiple providers
+     * @return Port instance
+     */
+    private Port composePort(Device device, PortNumber number,
+                ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs) {
+
+        ProviderId primary = pickPrimaryPID(providerDescs);
+        DeviceDescriptions primDescs = providerDescs.get(primary);
+        // if no primary, assume not enabled
+        // TODO: revisit this default port enabled/disabled behavior
+        boolean isEnabled = false;
+        DefaultAnnotations annotations = DefaultAnnotations.builder().build();
+
+        final Timestamped<PortDescription> portDesc = primDescs.getPortDesc(number);
+        if (portDesc != null) {
+            isEnabled = portDesc.value().isEnabled();
+            annotations = merge(annotations, portDesc.value().annotations());
+        }
+
+        for (Entry<ProviderId, DeviceDescriptions> e : providerDescs.entrySet()) {
+            if (e.getKey().equals(primary)) {
+                continue;
+            }
+            // TODO: should keep track of Description timestamp
+            // and only merge conflicting keys when timestamp is newer
+            // Currently assuming there will never be a key conflict between
+            // providers
+
+            // annotation merging. not so efficient, should revisit later
+            final Timestamped<PortDescription> otherPortDesc = e.getValue().getPortDesc(number);
+            if (otherPortDesc != null) {
+                annotations = merge(annotations, otherPortDesc.value().annotations());
+            }
+        }
+
+        return new DefaultPort(device, number, isEnabled, annotations);
+    }
+
+    /**
+     * @return primary ProviderID, or randomly chosen one if none exists
+     */
+    private ProviderId pickPrimaryPID(
+            Map<ProviderId, DeviceDescriptions> providerDescs) {
+        ProviderId fallBackPrimary = null;
+        for (Entry<ProviderId, DeviceDescriptions> e : providerDescs.entrySet()) {
+            if (!e.getKey().isAncillary()) {
+                return e.getKey();
+            } else if (fallBackPrimary == null) {
+                // pick randomly as a fallback in case there is no primary
+                fallBackPrimary = e.getKey();
+            }
+        }
+        return fallBackPrimary;
+    }
+
+    private DeviceDescriptions getPrimaryDescriptions(
+                            Map<ProviderId, DeviceDescriptions> providerDescs) {
+        ProviderId pid = pickPrimaryPID(providerDescs);
+        return providerDescs.get(pid);
+    }
+
+    public static final class InitDeviceDescs
+        implements ConcurrentInitializer<DeviceDescriptions> {
+
+        private final Timestamped<DeviceDescription> deviceDesc;
+
+        public InitDeviceDescs(Timestamped<DeviceDescription> deviceDesc) {
+            this.deviceDesc = checkNotNull(deviceDesc);
+        }
+        @Override
+        public DeviceDescriptions get() throws ConcurrentException {
+            return new DeviceDescriptions(deviceDesc);
+        }
+    }
+
+
+    /**
+     * Collection of Description of a Device and it's Ports given from a Provider.
+     */
+    public static class DeviceDescriptions {
+
+        private final AtomicReference<Timestamped<DeviceDescription>> deviceDesc;
+        private final ConcurrentMap<PortNumber, Timestamped<PortDescription>> portDescs;
+
+        public DeviceDescriptions(Timestamped<DeviceDescription> desc) {
+            this.deviceDesc = new AtomicReference<>(checkNotNull(desc));
+            this.portDescs = new ConcurrentHashMap<>();
+        }
+
+        Timestamp getLatestTimestamp() {
+            Timestamp latest = deviceDesc.get().timestamp();
+            for (Timestamped<PortDescription> desc : portDescs.values()) {
+                if (desc.timestamp().compareTo(latest) > 0) {
+                    latest = desc.timestamp();
+                }
+            }
+            return latest;
+        }
+
+        public Timestamped<DeviceDescription> getDeviceDesc() {
+            return deviceDesc.get();
+        }
+
+        public Timestamped<PortDescription> getPortDesc(PortNumber number) {
+            return portDescs.get(number);
+        }
+
+        /**
+         * Puts DeviceDescription, merging annotations as necessary.
+         *
+         * @param newDesc new DeviceDescription
+         * @return previous DeviceDescription
+         */
+        public synchronized Timestamped<DeviceDescription> putDeviceDesc(Timestamped<DeviceDescription> newDesc) {
+            Timestamped<DeviceDescription> oldOne = deviceDesc.get();
+            Timestamped<DeviceDescription> newOne = newDesc;
+            if (oldOne != null) {
+                SparseAnnotations merged = union(oldOne.value().annotations(),
+                                                 newDesc.value().annotations());
+                newOne = new Timestamped<DeviceDescription>(
+                        new DefaultDeviceDescription(newDesc.value(), merged),
+                        newDesc.timestamp());
+            }
+            return deviceDesc.getAndSet(newOne);
+        }
+
+        /**
+         * Puts PortDescription, merging annotations as necessary.
+         *
+         * @param newDesc new PortDescription
+         * @return previous PortDescription
+         */
+        public synchronized Timestamped<PortDescription> putPortDesc(Timestamped<PortDescription> newDesc) {
+            Timestamped<PortDescription> oldOne = portDescs.get(newDesc.value().portNumber());
+            Timestamped<PortDescription> newOne = newDesc;
+            if (oldOne != null) {
+                SparseAnnotations merged = union(oldOne.value().annotations(),
+                                                 newDesc.value().annotations());
+                newOne = new Timestamped<PortDescription>(
+                        new DefaultPortDescription(newDesc.value(), merged),
+                        newDesc.timestamp());
+            }
+            return portDescs.put(newOne.value().portNumber(), newOne);
+        }
+    }
+
+    private void notifyPeers(InternalDeviceEvent event) throws IOException {
+        ClusterMessage message = new ClusterMessage(
+                clusterService.getLocalNode().id(),
+                GossipDeviceStoreMessageSubjects.DEVICE_UPDATE,
+                SERIALIZER.encode(event));
+        clusterCommunicator.broadcast(message);
+    }
+
+    private void notifyPeers(InternalDeviceOfflineEvent event) throws IOException {
+        ClusterMessage message = new ClusterMessage(
+                clusterService.getLocalNode().id(),
+                GossipDeviceStoreMessageSubjects.DEVICE_OFFLINE,
+                SERIALIZER.encode(event));
+        clusterCommunicator.broadcast(message);
+    }
+
+    private void notifyPeers(InternalDeviceRemovedEvent event) throws IOException {
+        ClusterMessage message = new ClusterMessage(
+                clusterService.getLocalNode().id(),
+                GossipDeviceStoreMessageSubjects.DEVICE_REMOVED,
+                SERIALIZER.encode(event));
+        clusterCommunicator.broadcast(message);
+    }
+
+    private void notifyPeers(InternalPortEvent event) throws IOException {
+        ClusterMessage message = new ClusterMessage(
+                clusterService.getLocalNode().id(),
+                GossipDeviceStoreMessageSubjects.PORT_UPDATE,
+                SERIALIZER.encode(event));
+        clusterCommunicator.broadcast(message);
+    }
+
+    private void notifyPeers(InternalPortStatusEvent event) throws IOException {
+        ClusterMessage message = new ClusterMessage(
+                clusterService.getLocalNode().id(),
+                GossipDeviceStoreMessageSubjects.PORT_STATUS_UPDATE,
+                SERIALIZER.encode(event));
+        clusterCommunicator.broadcast(message);
+    }
+
+    private class InternalDeviceEventListener implements ClusterMessageHandler {
+        @Override
+        public void handle(ClusterMessage message) {
+
+            log.info("Received device update event from peer: {}", message.sender());
+            InternalDeviceEvent event = (InternalDeviceEvent) SERIALIZER.decode(message.payload());
+
+            ProviderId providerId = event.providerId();
+            DeviceId deviceId = event.deviceId();
+            Timestamped<DeviceDescription> deviceDescription = event.deviceDescription();
+
+            createOrUpdateDeviceInternal(providerId, deviceId, deviceDescription);
+        }
+    }
+
+    private class InternalDeviceOfflineEventListener implements ClusterMessageHandler {
+        @Override
+        public void handle(ClusterMessage message) {
+
+            log.info("Received device offline event from peer: {}", message.sender());
+            InternalDeviceOfflineEvent event = (InternalDeviceOfflineEvent) SERIALIZER.decode(message.payload());
+
+            DeviceId deviceId = event.deviceId();
+            Timestamp timestamp = event.timestamp();
+
+            markOfflineInternal(deviceId, timestamp);
+        }
+    }
+
+    private class InternalDeviceRemovedEventListener implements ClusterMessageHandler {
+        @Override
+        public void handle(ClusterMessage message) {
+
+            log.info("Received device removed event from peer: {}", message.sender());
+            InternalDeviceRemovedEvent event = (InternalDeviceRemovedEvent) SERIALIZER.decode(message.payload());
+
+            DeviceId deviceId = event.deviceId();
+            Timestamp timestamp = event.timestamp();
+
+            removeDeviceInternal(deviceId, timestamp);
+        }
+    }
+
+    private class InternalPortEventListener implements ClusterMessageHandler {
+        @Override
+        public void handle(ClusterMessage message) {
+
+            log.info("Received port update event from peer: {}", message.sender());
+            InternalPortEvent event = (InternalPortEvent) SERIALIZER.decode(message.payload());
+
+            ProviderId providerId = event.providerId();
+            DeviceId deviceId = event.deviceId();
+            Timestamped<List<PortDescription>> portDescriptions = event.portDescriptions();
+
+            updatePortsInternal(providerId, deviceId, portDescriptions);
+        }
+    }
+
+    private class InternalPortStatusEventListener implements ClusterMessageHandler {
+        @Override
+        public void handle(ClusterMessage message) {
+
+            log.info("Received port status update event from peer: {}", message.sender());
+            InternalPortStatusEvent event = (InternalPortStatusEvent) SERIALIZER.decode(message.payload());
+
+            ProviderId providerId = event.providerId();
+            DeviceId deviceId = event.deviceId();
+            Timestamped<PortDescription> portDescription = event.portDescription();
+
+            updatePortStatusInternal(providerId, deviceId, portDescription);
+        }
+    }
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/GossipDeviceStoreMessageSubjects.java b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/GossipDeviceStoreMessageSubjects.java
new file mode 100644
index 0000000..5272182
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/GossipDeviceStoreMessageSubjects.java
@@ -0,0 +1,17 @@
+package org.onlab.onos.store.device.impl;
+
+import org.onlab.onos.store.cluster.messaging.MessageSubject;
+
+/**
+ * MessageSubjects used by GossipDeviceStore peer-peer communication.
+ */
+public final class GossipDeviceStoreMessageSubjects {
+
+    private GossipDeviceStoreMessageSubjects() {}
+
+    public static final MessageSubject DEVICE_UPDATE = new MessageSubject("peer-device-update");
+    public static final MessageSubject DEVICE_OFFLINE = new MessageSubject("peer-device-offline");
+    public static final MessageSubject DEVICE_REMOVED = new MessageSubject("peer-device-removed");
+    public static final MessageSubject PORT_UPDATE = new MessageSubject("peer-port-update");
+    public static final MessageSubject PORT_STATUS_UPDATE = new MessageSubject("peer-port-status-update");
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalDeviceEvent.java b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalDeviceEvent.java
new file mode 100644
index 0000000..4214384
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalDeviceEvent.java
@@ -0,0 +1,45 @@
+package org.onlab.onos.store.device.impl;
+
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.device.DeviceDescription;
+import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.onos.store.common.impl.Timestamped;
+
+/**
+ * Information published by GossipDeviceStore to notify peers of a device
+ * change event.
+ */
+public class InternalDeviceEvent {
+
+    private final ProviderId providerId;
+    private final DeviceId deviceId;
+    private final Timestamped<DeviceDescription> deviceDescription;
+
+    protected InternalDeviceEvent(
+            ProviderId providerId,
+            DeviceId deviceId,
+            Timestamped<DeviceDescription> deviceDescription) {
+        this.providerId = providerId;
+        this.deviceId = deviceId;
+        this.deviceDescription = deviceDescription;
+    }
+
+    public DeviceId deviceId() {
+        return deviceId;
+    }
+
+    public ProviderId providerId() {
+        return providerId;
+    }
+
+    public Timestamped<DeviceDescription> deviceDescription() {
+        return deviceDescription;
+    }
+
+    // for serializer
+    protected InternalDeviceEvent() {
+        this.providerId = null;
+        this.deviceId = null;
+        this.deviceDescription = null;
+    }
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalDeviceEventSerializer.java b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalDeviceEventSerializer.java
new file mode 100644
index 0000000..0d3d013
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalDeviceEventSerializer.java
@@ -0,0 +1,43 @@
+package org.onlab.onos.store.device.impl;
+
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.device.DeviceDescription;
+import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.onos.store.common.impl.Timestamped;
+
+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 InternalDeviceEvent}.
+ */
+public class InternalDeviceEventSerializer extends Serializer<InternalDeviceEvent> {
+
+    /**
+     * Creates a serializer for {@link InternalDeviceEvent}.
+     */
+    public InternalDeviceEventSerializer() {
+        // does not accept null
+        super(false);
+    }
+
+    @Override
+    public void write(Kryo kryo, Output output, InternalDeviceEvent event) {
+        kryo.writeClassAndObject(output, event.providerId());
+        kryo.writeClassAndObject(output, event.deviceId());
+        kryo.writeClassAndObject(output, event.deviceDescription());
+    }
+
+    @Override
+    public InternalDeviceEvent read(Kryo kryo, Input input,
+                               Class<InternalDeviceEvent> type) {
+        ProviderId providerId = (ProviderId) kryo.readClassAndObject(input);
+        DeviceId deviceId = (DeviceId) kryo.readClassAndObject(input);
+        Timestamped<DeviceDescription> deviceDescription
+            = (Timestamped<DeviceDescription>) kryo.readClassAndObject(input);
+
+        return new InternalDeviceEvent(providerId, deviceId, deviceDescription);
+    }
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalDeviceOfflineEvent.java b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalDeviceOfflineEvent.java
new file mode 100644
index 0000000..d8942d6
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalDeviceOfflineEvent.java
@@ -0,0 +1,39 @@
+package org.onlab.onos.store.device.impl;
+
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.store.Timestamp;
+
+/**
+ * Information published by GossipDeviceStore to notify peers of a device
+ * going offline.
+ */
+public class InternalDeviceOfflineEvent {
+
+    private final DeviceId deviceId;
+    private final Timestamp timestamp;
+
+    /**
+     * Creates a InternalDeviceOfflineEvent.
+     * @param deviceId identifier of device going offline.
+     * @param timestamp timestamp of when the device went offline.
+     */
+    public InternalDeviceOfflineEvent(DeviceId deviceId, Timestamp timestamp) {
+        this.deviceId = deviceId;
+        this.timestamp = timestamp;
+    }
+
+    public DeviceId deviceId() {
+        return deviceId;
+    }
+
+    public Timestamp timestamp() {
+        return timestamp;
+    }
+
+    // for serializer
+    @SuppressWarnings("unused")
+    private InternalDeviceOfflineEvent() {
+        deviceId = null;
+        timestamp = null;
+    }
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalDeviceOfflineEventSerializer.java b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalDeviceOfflineEventSerializer.java
new file mode 100644
index 0000000..7059636
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalDeviceOfflineEventSerializer.java
@@ -0,0 +1,38 @@
+package org.onlab.onos.store.device.impl;
+
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.store.Timestamp;
+
+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 InternalDeviceOfflineEvent}.
+ */
+public class InternalDeviceOfflineEventSerializer extends Serializer<InternalDeviceOfflineEvent> {
+
+    /**
+     * Creates a serializer for {@link InternalDeviceOfflineEvent}.
+     */
+    public InternalDeviceOfflineEventSerializer() {
+        // does not accept null
+        super(false);
+    }
+
+    @Override
+    public void write(Kryo kryo, Output output, InternalDeviceOfflineEvent event) {
+        kryo.writeClassAndObject(output, event.deviceId());
+        kryo.writeClassAndObject(output, event.timestamp());
+    }
+
+    @Override
+    public InternalDeviceOfflineEvent read(Kryo kryo, Input input,
+                               Class<InternalDeviceOfflineEvent> type) {
+        DeviceId deviceId = (DeviceId) kryo.readClassAndObject(input);
+        Timestamp timestamp = (Timestamp) kryo.readClassAndObject(input);
+
+        return new InternalDeviceOfflineEvent(deviceId, timestamp);
+    }
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalDeviceRemovedEvent.java b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalDeviceRemovedEvent.java
new file mode 100644
index 0000000..6c8b905
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalDeviceRemovedEvent.java
@@ -0,0 +1,39 @@
+package org.onlab.onos.store.device.impl;
+
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.store.Timestamp;
+
+/**
+ * Information published by GossipDeviceStore to notify peers of a device
+ * being administratively removed.
+ */
+public class InternalDeviceRemovedEvent {
+
+    private final DeviceId deviceId;
+    private final Timestamp timestamp;
+
+    /**
+     * Creates a InternalDeviceRemovedEvent.
+     * @param deviceId identifier of the removed device.
+     * @param timestamp timestamp of when the device was administratively removed.
+     */
+    public InternalDeviceRemovedEvent(DeviceId deviceId, Timestamp timestamp) {
+        this.deviceId = deviceId;
+        this.timestamp = timestamp;
+    }
+
+    public DeviceId deviceId() {
+        return deviceId;
+    }
+
+    public Timestamp timestamp() {
+        return timestamp;
+    }
+
+    // for serializer
+    @SuppressWarnings("unused")
+    private InternalDeviceRemovedEvent() {
+        deviceId = null;
+        timestamp = null;
+    }
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalPortEvent.java b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalPortEvent.java
new file mode 100644
index 0000000..64e77ca
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalPortEvent.java
@@ -0,0 +1,47 @@
+package org.onlab.onos.store.device.impl;
+
+import java.util.List;
+
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.device.PortDescription;
+import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.onos.store.common.impl.Timestamped;
+
+/**
+ * Information published by GossipDeviceStore to notify peers of a port
+ * change event.
+ */
+public class InternalPortEvent {
+
+    private final ProviderId providerId;
+    private final DeviceId deviceId;
+    private final Timestamped<List<PortDescription>> portDescriptions;
+
+    protected InternalPortEvent(
+            ProviderId providerId,
+            DeviceId deviceId,
+            Timestamped<List<PortDescription>> portDescriptions) {
+        this.providerId = providerId;
+        this.deviceId = deviceId;
+        this.portDescriptions = portDescriptions;
+    }
+
+    public DeviceId deviceId() {
+        return deviceId;
+    }
+
+    public ProviderId providerId() {
+        return providerId;
+    }
+
+    public Timestamped<List<PortDescription>> portDescriptions() {
+        return portDescriptions;
+    }
+
+    // for serializer
+    protected InternalPortEvent() {
+        this.providerId = null;
+        this.deviceId = null;
+        this.portDescriptions = null;
+    }
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalPortEventSerializer.java b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalPortEventSerializer.java
new file mode 100644
index 0000000..6fff395
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalPortEventSerializer.java
@@ -0,0 +1,45 @@
+package org.onlab.onos.store.device.impl;
+
+import java.util.List;
+
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.device.PortDescription;
+import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.onos.store.common.impl.Timestamped;
+
+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 InternalPortEvent}.
+ */
+public class InternalPortEventSerializer extends Serializer<InternalPortEvent> {
+
+    /**
+     * Creates a serializer for {@link InternalPortEvent}.
+     */
+    public InternalPortEventSerializer() {
+        // does not accept null
+        super(false);
+    }
+
+    @Override
+    public void write(Kryo kryo, Output output, InternalPortEvent event) {
+        kryo.writeClassAndObject(output, event.providerId());
+        kryo.writeClassAndObject(output, event.deviceId());
+        kryo.writeClassAndObject(output, event.portDescriptions());
+    }
+
+    @Override
+    public InternalPortEvent read(Kryo kryo, Input input,
+                               Class<InternalPortEvent> type) {
+        ProviderId providerId = (ProviderId) kryo.readClassAndObject(input);
+        DeviceId deviceId = (DeviceId) kryo.readClassAndObject(input);
+        Timestamped<List<PortDescription>> portDescriptions
+            = (Timestamped<List<PortDescription>>) kryo.readClassAndObject(input);
+
+        return new InternalPortEvent(providerId, deviceId, portDescriptions);
+    }
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalPortStatusEvent.java b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalPortStatusEvent.java
new file mode 100644
index 0000000..7d3854b
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalPortStatusEvent.java
@@ -0,0 +1,45 @@
+package org.onlab.onos.store.device.impl;
+
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.device.PortDescription;
+import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.onos.store.common.impl.Timestamped;
+
+/**
+ * Information published by GossipDeviceStore to notify peers of a port
+ * status change event.
+ */
+public class InternalPortStatusEvent {
+
+    private final ProviderId providerId;
+    private final DeviceId deviceId;
+    private final Timestamped<PortDescription> portDescription;
+
+    protected InternalPortStatusEvent(
+            ProviderId providerId,
+            DeviceId deviceId,
+            Timestamped<PortDescription> portDescription) {
+        this.providerId = providerId;
+        this.deviceId = deviceId;
+        this.portDescription = portDescription;
+    }
+
+    public DeviceId deviceId() {
+        return deviceId;
+    }
+
+    public ProviderId providerId() {
+        return providerId;
+    }
+
+    public Timestamped<PortDescription> portDescription() {
+        return portDescription;
+    }
+
+    // for serializer
+    protected InternalPortStatusEvent() {
+        this.providerId = null;
+        this.deviceId = null;
+        this.portDescription = null;
+    }
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalPortStatusEventSerializer.java b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalPortStatusEventSerializer.java
new file mode 100644
index 0000000..6ec4122
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalPortStatusEventSerializer.java
@@ -0,0 +1,42 @@
+package org.onlab.onos.store.device.impl;
+
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.device.PortDescription;
+import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.onos.store.common.impl.Timestamped;
+
+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 InternalPortStatusEvent}.
+ */
+public class InternalPortStatusEventSerializer extends Serializer<InternalPortStatusEvent> {
+
+    /**
+     * Creates a serializer for {@link InternalPortStatusEvent}.
+     */
+    public InternalPortStatusEventSerializer() {
+        // does not accept null
+        super(false);
+    }
+
+    @Override
+    public void write(Kryo kryo, Output output, InternalPortStatusEvent event) {
+        kryo.writeClassAndObject(output, event.providerId());
+        kryo.writeClassAndObject(output, event.deviceId());
+        kryo.writeClassAndObject(output, event.portDescription());
+    }
+
+    @Override
+    public InternalPortStatusEvent read(Kryo kryo, Input input,
+                               Class<InternalPortStatusEvent> type) {
+        ProviderId providerId = (ProviderId) kryo.readClassAndObject(input);
+        DeviceId deviceId = (DeviceId) kryo.readClassAndObject(input);
+        Timestamped<PortDescription> portDescription = (Timestamped<PortDescription>) kryo.readClassAndObject(input);
+
+        return new InternalPortStatusEvent(providerId, deviceId, portDescription);
+    }
+}
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
deleted file mode 100644
index d912983..0000000
--- a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/OnosDistributedDeviceStore.java
+++ /dev/null
@@ -1,339 +0,0 @@
-package org.onlab.onos.store.device.impl;
-
-import static com.google.common.base.Predicates.notNull;
-import static com.google.common.base.Preconditions.checkState;
-
-import com.google.common.collect.FluentIterable;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ImmutableSet.Builder;
-
-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.Reference;
-import org.apache.felix.scr.annotations.ReferenceCardinality;
-import org.apache.felix.scr.annotations.Service;
-import org.onlab.onos.net.DefaultDevice;
-import org.onlab.onos.net.DefaultPort;
-import org.onlab.onos.net.Device;
-import org.onlab.onos.net.DeviceId;
-import org.onlab.onos.net.Port;
-import org.onlab.onos.net.PortNumber;
-import org.onlab.onos.net.device.DeviceDescription;
-import org.onlab.onos.net.device.DeviceEvent;
-import org.onlab.onos.net.device.DeviceStore;
-import org.onlab.onos.net.device.DeviceStoreDelegate;
-import org.onlab.onos.net.device.PortDescription;
-import org.onlab.onos.net.provider.ProviderId;
-import org.onlab.onos.store.AbstractStore;
-import org.onlab.onos.store.ClockService;
-import org.onlab.onos.store.Timestamp;
-import org.slf4j.Logger;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-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.*;
-import static org.slf4j.LoggerFactory.getLogger;
-
-/**
- * Manages inventory of infrastructure devices using a protocol that takes into consideration
- * the order in which device events occur.
- */
-@Component(immediate = true)
-@Service
-public class OnosDistributedDeviceStore
-        extends AbstractStore<DeviceEvent, DeviceStoreDelegate>
-        implements DeviceStore {
-
-    private final Logger log = getLogger(getClass());
-
-    public static final String DEVICE_NOT_FOUND = "Device with ID %s not found";
-
-    private ConcurrentMap<DeviceId, VersionedValue<Device>> devices;
-    private ConcurrentMap<DeviceId, Map<PortNumber, VersionedValue<Port>>> devicePorts;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected ClockService clockService;
-
-    @Activate
-    public void activate() {
-
-        devices = new ConcurrentHashMap<>();
-        devicePorts = new ConcurrentHashMap<>();
-
-        log.info("Started");
-    }
-
-    @Deactivate
-    public void deactivate() {
-        log.info("Stopped");
-    }
-
-    @Override
-    public int getDeviceCount() {
-        return devices.size();
-    }
-
-    @Override
-    public Iterable<Device> getDevices() {
-        Builder<Device> builder = ImmutableSet.builder();
-        synchronized (this) {
-            for (VersionedValue<Device> device : devices.values()) {
-                builder.add(device.entity());
-            }
-            return builder.build();
-        }
-    }
-
-    @Override
-    public Device getDevice(DeviceId deviceId) {
-        VersionedValue<Device> device = devices.get(deviceId);
-        checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
-        return device.entity();
-    }
-
-    @Override
-    public DeviceEvent createOrUpdateDevice(ProviderId providerId, DeviceId deviceId,
-                                            DeviceDescription deviceDescription) {
-        Timestamp newTimestamp = clockService.getTimestamp(deviceId);
-        VersionedValue<Device> device = devices.get(deviceId);
-
-        if (device == null) {
-            return createDevice(providerId, deviceId, deviceDescription, newTimestamp);
-        }
-
-        checkState(newTimestamp.compareTo(device.timestamp()) > 0,
-                "Existing device has a timestamp in the future!");
-
-        return updateDevice(providerId, device.entity(), deviceDescription, newTimestamp);
-    }
-
-    // Creates the device and returns the appropriate event if necessary.
-    private DeviceEvent createDevice(ProviderId providerId, DeviceId deviceId,
-                                     DeviceDescription desc, Timestamp timestamp) {
-        Device device = new DefaultDevice(providerId, deviceId, desc.type(),
-                                                 desc.manufacturer(),
-                                                 desc.hwVersion(), desc.swVersion(),
-                                                 desc.serialNumber());
-
-        devices.put(deviceId, new VersionedValue<>(device, true, timestamp));
-        // TODO,FIXME: broadcast a message telling peers of a device event.
-        return new DeviceEvent(DEVICE_ADDED, device, null);
-    }
-
-    // Updates the device and returns the appropriate event if necessary.
-    private DeviceEvent updateDevice(ProviderId providerId, Device device,
-                                     DeviceDescription desc, Timestamp timestamp) {
-        // We allow only certain attributes to trigger update
-        if (!Objects.equals(device.hwVersion(), desc.hwVersion()) ||
-                !Objects.equals(device.swVersion(), desc.swVersion())) {
-
-            Device updated = new DefaultDevice(providerId, device.id(),
-                                                      desc.type(),
-                                                      desc.manufacturer(),
-                                                      desc.hwVersion(),
-                                                      desc.swVersion(),
-                                                      desc.serialNumber());
-            devices.put(device.id(), new VersionedValue<Device>(updated, true, timestamp));
-            // FIXME: broadcast a message telling peers of a device event.
-            return new DeviceEvent(DeviceEvent.Type.DEVICE_UPDATED, updated, null);
-        }
-
-        // Otherwise merely attempt to change availability
-        Device updated = new DefaultDevice(providerId, device.id(),
-                desc.type(),
-                desc.manufacturer(),
-                desc.hwVersion(),
-                desc.swVersion(),
-                desc.serialNumber());
-
-        VersionedValue<Device> oldDevice = devices.put(device.id(),
-                new VersionedValue<Device>(updated, true, timestamp));
-        if (!oldDevice.isUp()) {
-            return new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device, null);
-        } else {
-            return null;
-        }
-    }
-
-    @Override
-    public DeviceEvent markOffline(DeviceId deviceId) {
-        VersionedValue<Device> device = devices.get(deviceId);
-        boolean willRemove = device != null && device.isUp();
-        if (!willRemove) {
-            return null;
-        }
-        Timestamp timestamp = clockService.getTimestamp(deviceId);
-        if (replaceIfLatest(device.entity(), false, timestamp)) {
-            return new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device.entity(), null);
-        }
-        return null;
-    }
-
-    // Replace existing value if its timestamp is older.
-    private synchronized boolean replaceIfLatest(Device device, boolean isUp, Timestamp timestamp) {
-        VersionedValue<Device> existingValue = devices.get(device.id());
-        if (timestamp.compareTo(existingValue.timestamp()) > 0) {
-            devices.put(device.id(), new VersionedValue<Device>(device, isUp, timestamp));
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public List<DeviceEvent> updatePorts(ProviderId providerId, DeviceId deviceId,
-                                         List<PortDescription> portDescriptions) {
-        List<DeviceEvent> events = new ArrayList<>();
-        synchronized (this) {
-            VersionedValue<Device> device = devices.get(deviceId);
-            checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
-            Map<PortNumber, VersionedValue<Port>> ports = getPortMap(deviceId);
-            Timestamp newTimestamp = clockService.getTimestamp(deviceId);
-
-            // Add new ports
-            Set<PortNumber> processed = new HashSet<>();
-            for (PortDescription portDescription : portDescriptions) {
-                VersionedValue<Port> port = ports.get(portDescription.portNumber());
-                if (port == null) {
-                    events.add(createPort(device, portDescription, ports, newTimestamp));
-                }
-                checkState(newTimestamp.compareTo(port.timestamp()) > 0,
-                        "Existing port state has a timestamp in the future!");
-                events.add(updatePort(device.entity(), port.entity(), portDescription, ports, newTimestamp));
-                processed.add(portDescription.portNumber());
-            }
-
-            updatePortMap(deviceId, ports);
-
-            events.addAll(pruneOldPorts(device.entity(), ports, processed));
-        }
-        return FluentIterable.from(events).filter(notNull()).toList();
-    }
-
-    // Creates a new port based on the port description adds it to the map and
-    // Returns corresponding event.
-    //@GuardedBy("this")
-    private DeviceEvent createPort(VersionedValue<Device> device, PortDescription portDescription,
-                                   Map<PortNumber, VersionedValue<Port>> ports, Timestamp timestamp) {
-        Port port = new DefaultPort(device.entity(), portDescription.portNumber(),
-                                           portDescription.isEnabled());
-        ports.put(port.number(), new VersionedValue<Port>(port, true, timestamp));
-        updatePortMap(device.entity().id(), ports);
-        return new DeviceEvent(PORT_ADDED, device.entity(), port);
-    }
-
-    // Checks if the specified port requires update and if so, it replaces the
-    // existing entry in the map and returns corresponding event.
-    //@GuardedBy("this")
-    private DeviceEvent updatePort(Device device, Port port,
-                                   PortDescription portDescription,
-                                   Map<PortNumber, VersionedValue<Port>> ports,
-                                   Timestamp timestamp) {
-        if (port.isEnabled() != portDescription.isEnabled()) {
-            VersionedValue<Port> updatedPort = new VersionedValue<Port>(
-                    new DefaultPort(device, portDescription.portNumber(),
-                                    portDescription.isEnabled()),
-                    portDescription.isEnabled(),
-                    timestamp);
-            ports.put(port.number(), updatedPort);
-            updatePortMap(device.id(), ports);
-            return new DeviceEvent(PORT_UPDATED, device, updatedPort.entity());
-        }
-        return null;
-    }
-
-    // Prunes the specified list of ports based on which ports are in the
-    // processed list and returns list of corresponding events.
-    //@GuardedBy("this")
-    private List<DeviceEvent> pruneOldPorts(Device device,
-                                            Map<PortNumber, VersionedValue<Port>> ports,
-                                            Set<PortNumber> processed) {
-        List<DeviceEvent> events = new ArrayList<>();
-        Iterator<PortNumber> iterator = ports.keySet().iterator();
-        while (iterator.hasNext()) {
-            PortNumber portNumber = iterator.next();
-            if (!processed.contains(portNumber)) {
-                events.add(new DeviceEvent(PORT_REMOVED, device,
-                                           ports.get(portNumber).entity()));
-                iterator.remove();
-            }
-        }
-        if (!events.isEmpty()) {
-            updatePortMap(device.id(), ports);
-        }
-        return events;
-    }
-
-    // Gets the map of ports for the specified device; if one does not already
-    // exist, it creates and registers a new one.
-    // WARN: returned value is a copy, changes made to the Map
-    //       needs to be written back using updatePortMap
-    //@GuardedBy("this")
-    private Map<PortNumber, VersionedValue<Port>> getPortMap(DeviceId deviceId) {
-        Map<PortNumber, VersionedValue<Port>> ports = devicePorts.get(deviceId);
-        if (ports == null) {
-            ports = new HashMap<>();
-            // this probably is waste of time in most cases.
-            updatePortMap(deviceId, ports);
-        }
-        return ports;
-    }
-
-    //@GuardedBy("this")
-    private void updatePortMap(DeviceId deviceId, Map<PortNumber, VersionedValue<Port>> ports) {
-        devicePorts.put(deviceId, ports);
-    }
-
-    @Override
-    public DeviceEvent updatePortStatus(ProviderId providerId, DeviceId deviceId,
-                                        PortDescription portDescription) {
-        VersionedValue<Device> device = devices.get(deviceId);
-        checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
-        Map<PortNumber, VersionedValue<Port>> ports = getPortMap(deviceId);
-        VersionedValue<Port> port = ports.get(portDescription.portNumber());
-        Timestamp timestamp = clockService.getTimestamp(deviceId);
-        return updatePort(device.entity(), port.entity(), portDescription, ports, timestamp);
-    }
-
-    @Override
-    public List<Port> getPorts(DeviceId deviceId) {
-        Map<PortNumber, VersionedValue<Port>> versionedPorts = devicePorts.get(deviceId);
-        if (versionedPorts == null) {
-            return Collections.emptyList();
-        }
-        List<Port> ports = new ArrayList<>();
-        for (VersionedValue<Port> port : versionedPorts.values()) {
-            ports.add(port.entity());
-        }
-        return ports;
-    }
-
-    @Override
-    public Port getPort(DeviceId deviceId, PortNumber portNumber) {
-        Map<PortNumber, VersionedValue<Port>> ports = devicePorts.get(deviceId);
-        return ports == null ? null : ports.get(portNumber).entity();
-    }
-
-    @Override
-    public boolean isAvailable(DeviceId deviceId) {
-        return devices.get(deviceId).isUp();
-    }
-
-    @Override
-    public DeviceEvent removeDevice(DeviceId deviceId) {
-        VersionedValue<Device> previousDevice = devices.remove(deviceId);
-        return previousDevice == null ? null :
-            new DeviceEvent(DEVICE_REMOVED, previousDevice.entity(), null);
-    }
-}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/package-info.java b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/package-info.java
index aa644db..c1f5aad 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/package-info.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/package-info.java
@@ -1,4 +1,4 @@
 /**
  * Implementation of device store using distributed distributed p2p synchronization protocol.
  */
-package org.onlab.onos.store.device.impl;
\ No newline at end of file
+package org.onlab.onos.store.device.impl;
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/flow/impl/DistributedFlowRuleStore.java b/core/store/dist/src/main/java/org/onlab/onos/store/flow/impl/DistributedFlowRuleStore.java
index 5a5592a..d49e00b 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/flow/impl/DistributedFlowRuleStore.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/flow/impl/DistributedFlowRuleStore.java
@@ -1,6 +1,5 @@
 package org.onlab.onos.store.flow.impl;
 
-import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_ADDED;
 import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
 import static org.slf4j.LoggerFactory.getLogger;
 
@@ -13,9 +12,10 @@
 import org.apache.felix.scr.annotations.Service;
 import org.onlab.onos.ApplicationId;
 import org.onlab.onos.net.DeviceId;
-import org.onlab.onos.net.flow.DefaultFlowRule;
+import org.onlab.onos.net.flow.DefaultFlowEntry;
+import org.onlab.onos.net.flow.FlowEntry;
+import org.onlab.onos.net.flow.FlowEntry.FlowEntryState;
 import org.onlab.onos.net.flow.FlowRule;
-import org.onlab.onos.net.flow.FlowRule.FlowRuleState;
 import org.onlab.onos.net.flow.FlowRuleEvent;
 import org.onlab.onos.net.flow.FlowRuleEvent.Type;
 import org.onlab.onos.net.flow.FlowRuleStore;
@@ -30,18 +30,18 @@
 /**
  * Manages inventory of flow rules using trivial in-memory implementation.
  */
-//FIXME: I LIE I AM NOT DISTRIBUTED
+//FIXME I LIE. I AIN'T DISTRIBUTED
 @Component(immediate = true)
 @Service
 public class DistributedFlowRuleStore
-extends AbstractStore<FlowRuleEvent, FlowRuleStoreDelegate>
-implements FlowRuleStore {
+        extends AbstractStore<FlowRuleEvent, FlowRuleStoreDelegate>
+        implements FlowRuleStore {
 
     private final Logger log = getLogger(getClass());
 
     // store entries as a pile of rules, no info about device tables
-    private final Multimap<DeviceId, FlowRule> flowEntries =
-            ArrayListMultimap.<DeviceId, FlowRule>create();
+    private final Multimap<DeviceId, FlowEntry> flowEntries =
+            ArrayListMultimap.<DeviceId, FlowEntry>create();
 
     private final Multimap<ApplicationId, FlowRule> flowEntriesById =
             ArrayListMultimap.<ApplicationId, FlowRule>create();
@@ -58,8 +58,13 @@
 
 
     @Override
-    public synchronized FlowRule getFlowRule(FlowRule rule) {
-        for (FlowRule f : flowEntries.get(rule.deviceId())) {
+    public int getFlowRuleCount() {
+        return flowEntries.size();
+    }
+
+    @Override
+    public synchronized FlowEntry getFlowEntry(FlowRule rule) {
+        for (FlowEntry f : flowEntries.get(rule.deviceId())) {
             if (f.equals(rule)) {
                 return f;
             }
@@ -68,8 +73,8 @@
     }
 
     @Override
-    public synchronized Iterable<FlowRule> getFlowEntries(DeviceId deviceId) {
-        Collection<FlowRule> rules = flowEntries.get(deviceId);
+    public synchronized Iterable<FlowEntry> getFlowEntries(DeviceId deviceId) {
+        Collection<FlowEntry> rules = flowEntries.get(deviceId);
         if (rules == null) {
             return Collections.emptyList();
         }
@@ -77,7 +82,7 @@
     }
 
     @Override
-    public synchronized Iterable<FlowRule> getFlowEntriesByAppId(ApplicationId appId) {
+    public synchronized Iterable<FlowRule> getFlowRulesByAppId(ApplicationId appId) {
         Collection<FlowRule> rules = flowEntriesById.get(appId);
         if (rules == null) {
             return Collections.emptyList();
@@ -87,7 +92,7 @@
 
     @Override
     public synchronized void storeFlowRule(FlowRule rule) {
-        FlowRule f = new DefaultFlowRule(rule, FlowRuleState.PENDING_ADD);
+        FlowEntry f = new DefaultFlowEntry(rule);
         DeviceId did = f.deviceId();
         if (!flowEntries.containsEntry(did, f)) {
             flowEntries.put(did, f);
@@ -97,57 +102,41 @@
 
     @Override
     public synchronized void deleteFlowRule(FlowRule rule) {
-        FlowRule f = new DefaultFlowRule(rule, FlowRuleState.PENDING_REMOVE);
-        DeviceId did = f.deviceId();
-
-        /*
-         *  find the rule and mark it for deletion.
-         *  Ultimately a flow removed will come remove it.
-         */
-
-        if (flowEntries.containsEntry(did, f)) {
-            //synchronized (flowEntries) {
-            flowEntries.remove(did, f);
-            flowEntries.put(did, f);
-            flowEntriesById.remove(rule.appId(), rule);
-            //}
+        FlowEntry entry = getFlowEntry(rule);
+        if (entry == null) {
+            return;
         }
+        entry.setState(FlowEntryState.PENDING_REMOVE);
     }
 
     @Override
-    public synchronized FlowRuleEvent addOrUpdateFlowRule(FlowRule rule) {
+    public synchronized FlowRuleEvent addOrUpdateFlowRule(FlowEntry rule) {
         DeviceId did = rule.deviceId();
 
         // check if this new rule is an update to an existing entry
-        if (flowEntries.containsEntry(did, rule)) {
-            //synchronized (flowEntries) {
-            // Multimaps support duplicates so we have to remove our rule
-            // and replace it with the current version.
-            flowEntries.remove(did, rule);
-            flowEntries.put(did, rule);
-            //}
+        FlowEntry stored = getFlowEntry(rule);
+        if (stored != null) {
+            stored.setBytes(rule.bytes());
+            stored.setLife(rule.life());
+            stored.setPackets(rule.packets());
+            if (stored.state() == FlowEntryState.PENDING_ADD) {
+                stored.setState(FlowEntryState.ADDED);
+                return new FlowRuleEvent(Type.RULE_ADDED, rule);
+            }
             return new FlowRuleEvent(Type.RULE_UPDATED, rule);
         }
 
         flowEntries.put(did, rule);
-        return new FlowRuleEvent(RULE_ADDED, rule);
+        return null;
     }
 
     @Override
-    public synchronized FlowRuleEvent removeFlowRule(FlowRule rule) {
-        //synchronized (this) {
+    public synchronized FlowRuleEvent removeFlowRule(FlowEntry rule) {
+        // This is where one could mark a rule as removed and still keep it in the store.
         if (flowEntries.remove(rule.deviceId(), rule)) {
             return new FlowRuleEvent(RULE_REMOVED, rule);
         } else {
             return null;
         }
-        //}
     }
-
-
-
-
-
-
-
 }
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/link/impl/OnosDistributedLinkStore.java b/core/store/dist/src/main/java/org/onlab/onos/store/link/impl/OnosDistributedLinkStore.java
index 5df25b4..a59b151 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/link/impl/OnosDistributedLinkStore.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/link/impl/OnosDistributedLinkStore.java
@@ -42,6 +42,7 @@
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkState;
 
+//TODO: Add support for multiple provider and annotations
 /**
  * Manages inventory of infrastructure links using a protocol that takes into consideration
  * the order in which events occur.
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/link/impl/package-info.java b/core/store/dist/src/main/java/org/onlab/onos/store/link/impl/package-info.java
index 127dc84..c675f84 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/link/impl/package-info.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/link/impl/package-info.java
@@ -1,4 +1,4 @@
 /**
  * Implementation of link store using distributed p2p synchronization protocol.
  */
-package org.onlab.onos.store.link.impl;
\ No newline at end of file
+package org.onlab.onos.store.link.impl;
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/serializers/ClusterMessageSerializer.java b/core/store/dist/src/main/java/org/onlab/onos/store/serializers/ClusterMessageSerializer.java
new file mode 100644
index 0000000..c0cefd6
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/serializers/ClusterMessageSerializer.java
@@ -0,0 +1,38 @@
+package org.onlab.onos.store.serializers;
+
+import org.onlab.onos.cluster.NodeId;
+import org.onlab.onos.store.cluster.messaging.ClusterMessage;
+import org.onlab.onos.store.cluster.messaging.MessageSubject;
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+public final class ClusterMessageSerializer extends Serializer<ClusterMessage> {
+
+    /**
+     * Creates a serializer for {@link ClusterMessage}.
+     */
+    public ClusterMessageSerializer() {
+        // does not accept null
+        super(false);
+    }
+
+    @Override
+    public void write(Kryo kryo, Output output, ClusterMessage message) {
+        kryo.writeClassAndObject(output, message.sender());
+        kryo.writeClassAndObject(output, message.subject());
+        output.writeInt(message.payload().length);
+        output.writeBytes(message.payload());
+    }
+
+    @Override
+    public ClusterMessage read(Kryo kryo, Input input,
+                               Class<ClusterMessage> type) {
+        NodeId sender = (NodeId) kryo.readClassAndObject(input);
+        MessageSubject subject = (MessageSubject) kryo.readClassAndObject(input);
+        int payloadSize = input.readInt();
+        byte[] payload = input.readBytes(payloadSize);
+        return new ClusterMessage(sender, subject, payload);
+    }
+}
\ No newline at end of file
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/serializers/MastershipBasedTimestampSerializer.java b/core/store/dist/src/main/java/org/onlab/onos/store/serializers/MastershipBasedTimestampSerializer.java
new file mode 100644
index 0000000..516915e
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/serializers/MastershipBasedTimestampSerializer.java
@@ -0,0 +1,36 @@
+package org.onlab.onos.store.serializers;
+
+import org.onlab.onos.store.common.impl.MastershipBasedTimestamp;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+// To be used if Timestamp ever needs to cross bundle boundary.
+/**
+ * Kryo Serializer for {@link MastershipBasedTimestamp}.
+ */
+public class MastershipBasedTimestampSerializer extends Serializer<MastershipBasedTimestamp> {
+
+    /**
+     * Creates a serializer for {@link MastershipBasedTimestamp}.
+     */
+    public MastershipBasedTimestampSerializer() {
+        // non-null, immutable
+        super(false, true);
+    }
+
+    @Override
+    public void write(Kryo kryo, Output output, MastershipBasedTimestamp object) {
+        output.writeInt(object.termNumber());
+        output.writeInt(object.sequenceNumber());
+    }
+
+    @Override
+    public MastershipBasedTimestamp read(Kryo kryo, Input input, Class<MastershipBasedTimestamp> type) {
+        final int term = input.readInt();
+        final int sequence = input.readInt();
+        return new MastershipBasedTimestamp(term, sequence);
+    }
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/serializers/MessageSubjectSerializer.java b/core/store/dist/src/main/java/org/onlab/onos/store/serializers/MessageSubjectSerializer.java
new file mode 100644
index 0000000..bb6b292
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/serializers/MessageSubjectSerializer.java
@@ -0,0 +1,31 @@
+package org.onlab.onos.store.serializers;
+
+import org.onlab.onos.store.cluster.messaging.MessageSubject;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+public final class MessageSubjectSerializer extends Serializer<MessageSubject> {
+
+    /**
+     * Creates a serializer for {@link MessageSubject}.
+     */
+    public MessageSubjectSerializer() {
+        // non-null, immutable
+        super(false, true);
+    }
+
+
+    @Override
+    public void write(Kryo kryo, Output output, MessageSubject object) {
+        output.writeString(object.value());
+    }
+
+    @Override
+    public MessageSubject read(Kryo kryo, Input input,
+            Class<MessageSubject> type) {
+        return new MessageSubject(input.readString());
+    }
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/serializers/OnosTimestampSerializer.java b/core/store/dist/src/main/java/org/onlab/onos/store/serializers/OnosTimestampSerializer.java
deleted file mode 100644
index 192e035..0000000
--- a/core/store/dist/src/main/java/org/onlab/onos/store/serializers/OnosTimestampSerializer.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.onlab.onos.store.serializers;
-
-import org.onlab.onos.store.impl.OnosTimestamp;
-
-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 OnosTimestamp}.
- */
-public class OnosTimestampSerializer extends Serializer<OnosTimestamp> {
-
-    /**
-     * Default constructor.
-     */
-    public OnosTimestampSerializer() {
-        // non-null, immutable
-        super(false, true);
-    }
-
-    @Override
-    public void write(Kryo kryo, Output output, OnosTimestamp object) {
-        output.writeInt(object.termNumber());
-        output.writeInt(object.sequenceNumber());
-    }
-
-    @Override
-    public OnosTimestamp read(Kryo kryo, Input input, Class<OnosTimestamp> type) {
-        final int term = input.readInt();
-        final int sequence = input.readInt();
-        return new OnosTimestamp(term, sequence);
-    }
-}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/topology/impl/DistributedTopologyStore.java b/core/store/dist/src/main/java/org/onlab/onos/store/topology/impl/DistributedTopologyStore.java
index 567861e..9ae9d7b 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/topology/impl/DistributedTopologyStore.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/topology/impl/DistributedTopologyStore.java
@@ -125,7 +125,8 @@
         // Promote the new topology to current and return a ready-to-send event.
         synchronized (this) {
             current = newTopology;
-            return new TopologyEvent(TopologyEvent.Type.TOPOLOGY_CHANGED, current);
+            return new TopologyEvent(TopologyEvent.Type.TOPOLOGY_CHANGED,
+                                     current, reasons);
         }
     }
 
diff --git a/core/store/dist/src/test/java/org/onlab/onos/store/cluster/impl/ClusterCommunicationManagerTest.java b/core/store/dist/src/test/java/org/onlab/onos/store/cluster/impl/ClusterCommunicationManagerTest.java
index bba12f2..e63fcaa 100644
--- a/core/store/dist/src/test/java/org/onlab/onos/store/cluster/impl/ClusterCommunicationManagerTest.java
+++ b/core/store/dist/src/test/java/org/onlab/onos/store/cluster/impl/ClusterCommunicationManagerTest.java
@@ -7,6 +7,7 @@
 import org.onlab.onos.cluster.DefaultControllerNode;
 import org.onlab.onos.cluster.NodeId;
 import org.onlab.onos.store.cluster.messaging.impl.ClusterCommunicationManager;
+import org.onlab.onos.store.cluster.messaging.impl.MessageSerializer;
 import org.onlab.netty.NettyMessagingService;
 import org.onlab.packet.IpPrefix;
 
diff --git a/core/store/dist/src/test/java/org/onlab/onos/store/common/impl/MastershipBasedTimestampTest.java b/core/store/dist/src/test/java/org/onlab/onos/store/common/impl/MastershipBasedTimestampTest.java
new file mode 100644
index 0000000..ea63ef8
--- /dev/null
+++ b/core/store/dist/src/test/java/org/onlab/onos/store/common/impl/MastershipBasedTimestampTest.java
@@ -0,0 +1,95 @@
+package org.onlab.onos.store.common.impl;
+
+import static org.junit.Assert.*;
+
+import java.nio.ByteBuffer;
+
+import org.junit.Test;
+import org.onlab.onos.store.Timestamp;
+import org.onlab.onos.store.serializers.MastershipBasedTimestampSerializer;
+import org.onlab.util.KryoPool;
+
+import com.google.common.testing.EqualsTester;
+
+/**
+ * Test of {@link MastershipBasedTimestamp}.
+ */
+public class MastershipBasedTimestampTest {
+
+    private static final Timestamp TS_1_1 = new MastershipBasedTimestamp(1, 1);
+    private static final Timestamp TS_1_2 = new MastershipBasedTimestamp(1, 2);
+    private static final Timestamp TS_2_1 = new MastershipBasedTimestamp(2, 1);
+    private static final Timestamp TS_2_2 = new MastershipBasedTimestamp(2, 2);
+
+    @Test
+    public final void testBasic() {
+        final int termNumber = 5;
+        final int sequenceNumber = 6;
+        MastershipBasedTimestamp ts = new MastershipBasedTimestamp(termNumber,
+                                                sequenceNumber);
+
+        assertEquals(termNumber, ts.termNumber());
+        assertEquals(sequenceNumber, ts.sequenceNumber());
+    }
+
+    @Test
+    public final void testCompareTo() {
+        assertTrue(TS_1_1.compareTo(TS_1_1) == 0);
+        assertTrue(TS_1_1.compareTo(new MastershipBasedTimestamp(1, 1)) == 0);
+
+        assertTrue(TS_1_1.compareTo(TS_1_2) < 0);
+        assertTrue(TS_1_2.compareTo(TS_1_1) > 0);
+
+        assertTrue(TS_1_2.compareTo(TS_2_1) < 0);
+        assertTrue(TS_1_2.compareTo(TS_2_2) < 0);
+        assertTrue(TS_2_1.compareTo(TS_1_1) > 0);
+        assertTrue(TS_2_2.compareTo(TS_1_1) > 0);
+    }
+
+    @Test
+    public final void testEqualsObject() {
+        new EqualsTester()
+        .addEqualityGroup(new MastershipBasedTimestamp(1, 1),
+                          new MastershipBasedTimestamp(1, 1), TS_1_1)
+        .addEqualityGroup(new MastershipBasedTimestamp(1, 2),
+                          new MastershipBasedTimestamp(1, 2), TS_1_2)
+        .addEqualityGroup(new MastershipBasedTimestamp(2, 1),
+                          new MastershipBasedTimestamp(2, 1), TS_2_1)
+        .addEqualityGroup(new MastershipBasedTimestamp(2, 2),
+                          new MastershipBasedTimestamp(2, 2), TS_2_2)
+        .testEquals();
+    }
+
+    @Test
+    public final void testKryoSerializable() {
+        final ByteBuffer buffer = ByteBuffer.allocate(1 * 1024 * 1024);
+        final KryoPool kryos = KryoPool.newBuilder()
+                .register(MastershipBasedTimestamp.class)
+                .build();
+
+        kryos.serialize(TS_2_1, buffer);
+        buffer.flip();
+        Timestamp copy = kryos.deserialize(buffer);
+
+        new EqualsTester()
+            .addEqualityGroup(TS_2_1, copy)
+            .testEquals();
+    }
+
+    @Test
+    public final void testKryoSerializableWithHandcraftedSerializer() {
+        final ByteBuffer buffer = ByteBuffer.allocate(1 * 1024 * 1024);
+        final KryoPool kryos = KryoPool.newBuilder()
+                .register(MastershipBasedTimestamp.class, new MastershipBasedTimestampSerializer())
+                .build();
+
+        kryos.serialize(TS_1_2, buffer);
+        buffer.flip();
+        Timestamp copy = kryos.deserialize(buffer);
+
+        new EqualsTester()
+            .addEqualityGroup(TS_1_2, copy)
+            .testEquals();
+    }
+
+}
diff --git a/core/store/dist/src/test/java/org/onlab/onos/store/common/impl/TimestampedTest.java b/core/store/dist/src/test/java/org/onlab/onos/store/common/impl/TimestampedTest.java
new file mode 100644
index 0000000..4b44d40
--- /dev/null
+++ b/core/store/dist/src/test/java/org/onlab/onos/store/common/impl/TimestampedTest.java
@@ -0,0 +1,94 @@
+package org.onlab.onos.store.common.impl;
+
+import static org.junit.Assert.*;
+
+import java.nio.ByteBuffer;
+
+import org.junit.Test;
+import org.onlab.onos.store.Timestamp;
+import org.onlab.util.KryoPool;
+
+import com.google.common.testing.EqualsTester;
+
+/**
+ * Test of {@link Timestamped}.
+ */
+public class TimestampedTest {
+
+    private static final Timestamp TS_1_1 = new MastershipBasedTimestamp(1, 1);
+    private static final Timestamp TS_1_2 = new MastershipBasedTimestamp(1, 2);
+    private static final Timestamp TS_2_1 = new MastershipBasedTimestamp(2, 1);
+
+    @Test
+    public final void testHashCode() {
+        Timestamped<String> a = new Timestamped<>("a", TS_1_1);
+        Timestamped<String> b = new Timestamped<>("b", TS_1_1);
+        assertTrue("value does not impact hashCode",
+                a.hashCode() == b.hashCode());
+    }
+
+    @Test
+    public final void testEquals() {
+        Timestamped<String> a = new Timestamped<>("a", TS_1_1);
+        Timestamped<String> b = new Timestamped<>("b", TS_1_1);
+        assertTrue("value does not impact equality",
+                a.equals(b));
+
+        new EqualsTester()
+        .addEqualityGroup(new Timestamped<>("a", TS_1_1),
+                          new Timestamped<>("b", TS_1_1),
+                          new Timestamped<>("c", TS_1_1))
+        .addEqualityGroup(new Timestamped<>("a", TS_1_2),
+                          new Timestamped<>("b", TS_1_2),
+                          new Timestamped<>("c", TS_1_2))
+        .addEqualityGroup(new Timestamped<>("a", TS_2_1),
+                          new Timestamped<>("b", TS_2_1),
+                          new Timestamped<>("c", TS_2_1))
+        .testEquals();
+
+    }
+
+    @Test
+    public final void testValue() {
+       final Integer n = Integer.valueOf(42);
+       Timestamped<Integer> tsv = new Timestamped<>(n, TS_1_1);
+       assertSame(n, tsv.value());
+
+    }
+
+    @Test(expected = NullPointerException.class)
+    public final void testValueNonNull() {
+        new Timestamped<>(null, TS_1_1);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public final void testTimestampNonNull() {
+        new Timestamped<>("Foo", null);
+    }
+
+    @Test
+    public final void testIsNewer() {
+        Timestamped<String> a = new Timestamped<>("a", TS_1_2);
+        Timestamped<String> b = new Timestamped<>("b", TS_1_1);
+        assertTrue(a.isNewer(b));
+        assertFalse(b.isNewer(a));
+    }
+
+    @Test
+    public final void testKryoSerializable() {
+        final ByteBuffer buffer = ByteBuffer.allocate(1 * 1024 * 1024);
+        final KryoPool kryos = KryoPool.newBuilder()
+                .register(Timestamped.class,
+                        MastershipBasedTimestamp.class)
+                .build();
+
+        Timestamped<String> original = new Timestamped<>("foobar", TS_1_1);
+        kryos.serialize(original, buffer);
+        buffer.flip();
+        Timestamped<String> copy = kryos.deserialize(buffer);
+
+        new EqualsTester()
+            .addEqualityGroup(original, copy)
+            .testEquals();
+    }
+}
diff --git a/core/store/dist/src/test/java/org/onlab/onos/store/device/impl/GossipDeviceStoreTest.java b/core/store/dist/src/test/java/org/onlab/onos/store/device/impl/GossipDeviceStoreTest.java
new file mode 100644
index 0000000..fa42a6b
--- /dev/null
+++ b/core/store/dist/src/test/java/org/onlab/onos/store/device/impl/GossipDeviceStoreTest.java
@@ -0,0 +1,619 @@
+package org.onlab.onos.store.device.impl;
+
+import static org.junit.Assert.*;
+import static org.onlab.onos.net.Device.Type.SWITCH;
+import static org.onlab.onos.net.DeviceId.deviceId;
+import static org.onlab.onos.net.device.DeviceEvent.Type.*;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.onlab.onos.cluster.ClusterEventListener;
+import org.onlab.onos.cluster.ClusterService;
+import org.onlab.onos.cluster.ControllerNode;
+import org.onlab.onos.cluster.ControllerNode.State;
+import org.onlab.onos.cluster.DefaultControllerNode;
+import org.onlab.onos.cluster.MastershipTerm;
+import org.onlab.onos.cluster.NodeId;
+import org.onlab.onos.net.Annotations;
+import org.onlab.onos.net.DefaultAnnotations;
+import org.onlab.onos.net.Device;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.Port;
+import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.SparseAnnotations;
+import org.onlab.onos.net.device.DefaultDeviceDescription;
+import org.onlab.onos.net.device.DefaultPortDescription;
+import org.onlab.onos.net.device.DeviceDescription;
+import org.onlab.onos.net.device.DeviceEvent;
+import org.onlab.onos.net.device.DeviceStore;
+import org.onlab.onos.net.device.DeviceStoreDelegate;
+import org.onlab.onos.net.device.PortDescription;
+import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.onos.store.ClockService;
+import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService;
+import org.onlab.onos.store.cluster.messaging.ClusterMessage;
+import org.onlab.onos.store.cluster.messaging.ClusterMessageHandler;
+import org.onlab.onos.store.cluster.messaging.MessageSubject;
+import org.onlab.packet.IpPrefix;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+
+
+// TODO add tests for remote replication
+/**
+ * Test of the gossip based distributed DeviceStore implementation.
+ */
+public class GossipDeviceStoreTest {
+
+    private static final ProviderId PID = new ProviderId("of", "foo");
+    private static final ProviderId PIDA = new ProviderId("of", "bar", true);
+    private static final DeviceId DID1 = deviceId("of:foo");
+    private static final DeviceId DID2 = deviceId("of:bar");
+    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 PortNumber P1 = PortNumber.portNumber(1);
+    private static final PortNumber P2 = PortNumber.portNumber(2);
+    private static final PortNumber P3 = PortNumber.portNumber(3);
+
+    private static final SparseAnnotations A1 = DefaultAnnotations.builder()
+            .set("A1", "a1")
+            .set("B1", "b1")
+            .build();
+    private static final SparseAnnotations A1_2 = DefaultAnnotations.builder()
+            .remove("A1")
+            .set("B3", "b3")
+            .build();
+    private static final SparseAnnotations A2 = DefaultAnnotations.builder()
+            .set("A2", "a2")
+            .set("B2", "b2")
+            .build();
+    private static final SparseAnnotations A2_2 = DefaultAnnotations.builder()
+            .remove("A2")
+            .set("B4", "b4")
+            .build();
+
+    private static final NodeId MYSELF = new NodeId("myself");
+
+    private GossipDeviceStore gossipDeviceStore;
+    private DeviceStore deviceStore;
+
+    private DeviceClockManager deviceClockManager;
+    private ClockService clockService;
+
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception {
+    }
+
+    @AfterClass
+    public static void tearDownAfterClass() throws Exception {
+    }
+
+
+    @Before
+    public void setUp() throws Exception {
+        deviceClockManager = new DeviceClockManager();
+        deviceClockManager.activate();
+        clockService = deviceClockManager;
+
+        deviceClockManager.setMastershipTerm(DID1, MastershipTerm.of(MYSELF, 1));
+        deviceClockManager.setMastershipTerm(DID2, MastershipTerm.of(MYSELF, 2));
+
+        ClusterCommunicationService clusterCommunicator = new TestClusterCommunicationService();
+        ClusterService clusterService = new TestClusterService();
+
+        gossipDeviceStore = new TestGossipDeviceStore(clockService, clusterService, clusterCommunicator);
+        gossipDeviceStore.activate();
+        deviceStore = gossipDeviceStore;
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        gossipDeviceStore.deactivate();
+        deviceClockManager.deactivate();
+    }
+
+    private void putDevice(DeviceId deviceId, String swVersion,
+                           SparseAnnotations... annotations) {
+        DeviceDescription description =
+                new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR,
+                        HW, swVersion, SN, annotations);
+        deviceStore.createOrUpdateDevice(PID, deviceId, description);
+    }
+
+    private void putDeviceAncillary(DeviceId deviceId, String swVersion,
+                                    SparseAnnotations... annotations) {
+        DeviceDescription description =
+                new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR,
+                        HW, swVersion, SN, annotations);
+        deviceStore.createOrUpdateDevice(PIDA, deviceId, description);
+    }
+
+    private static void assertDevice(DeviceId id, String swVersion, Device device) {
+        assertNotNull(device);
+        assertEquals(id, device.id());
+        assertEquals(MFR, device.manufacturer());
+        assertEquals(HW, device.hwVersion());
+        assertEquals(swVersion, device.swVersion());
+        assertEquals(SN, device.serialNumber());
+    }
+
+    /**
+     * Verifies that Annotations created by merging {@code annotations} is
+     * equal to actual Annotations.
+     *
+     * @param actual Annotations to check
+     * @param annotations
+     */
+    private static void assertAnnotationsEquals(Annotations actual, SparseAnnotations... annotations) {
+        DefaultAnnotations expected = DefaultAnnotations.builder().build();
+        for (SparseAnnotations a : annotations) {
+            expected = DefaultAnnotations.merge(expected, a);
+        }
+        assertEquals(expected.keys(), actual.keys());
+        for (String key : expected.keys()) {
+            assertEquals(expected.value(key), actual.value(key));
+        }
+    }
+
+    @Test
+    public final void testGetDeviceCount() {
+        assertEquals("initialy empty", 0, deviceStore.getDeviceCount());
+
+        putDevice(DID1, SW1);
+        putDevice(DID2, SW2);
+        putDevice(DID1, SW1);
+
+        assertEquals("expect 2 uniq devices", 2, deviceStore.getDeviceCount());
+    }
+
+    @Test
+    public final void testGetDevices() {
+        assertEquals("initialy empty", 0, Iterables.size(deviceStore.getDevices()));
+
+        putDevice(DID1, SW1);
+        putDevice(DID2, SW2);
+        putDevice(DID1, SW1);
+
+        assertEquals("expect 2 uniq devices",
+                2, Iterables.size(deviceStore.getDevices()));
+
+        Map<DeviceId, Device> devices = new HashMap<>();
+        for (Device device : deviceStore.getDevices()) {
+            devices.put(device.id(), device);
+        }
+
+        assertDevice(DID1, SW1, devices.get(DID1));
+        assertDevice(DID2, SW2, devices.get(DID2));
+
+        // add case for new node?
+    }
+
+    @Test
+    public final void testGetDevice() {
+
+        putDevice(DID1, SW1);
+
+        assertDevice(DID1, SW1, deviceStore.getDevice(DID1));
+        assertNull("DID2 shouldn't be there", deviceStore.getDevice(DID2));
+    }
+
+    @Test
+    public final void testCreateOrUpdateDevice() {
+        DeviceDescription description =
+                new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
+                        HW, SW1, SN);
+        DeviceEvent event = deviceStore.createOrUpdateDevice(PID, DID1, description);
+        assertEquals(DEVICE_ADDED, event.type());
+        assertDevice(DID1, SW1, event.subject());
+
+        DeviceDescription description2 =
+                new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
+                        HW, SW2, SN);
+        DeviceEvent event2 = deviceStore.createOrUpdateDevice(PID, DID1, description2);
+        assertEquals(DEVICE_UPDATED, event2.type());
+        assertDevice(DID1, SW2, event2.subject());
+
+        assertNull("No change expected", deviceStore.createOrUpdateDevice(PID, DID1, description2));
+    }
+
+    @Test
+    public final void testCreateOrUpdateDeviceAncillary() {
+        DeviceDescription description =
+                new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
+                        HW, SW1, SN, A2);
+        DeviceEvent event = deviceStore.createOrUpdateDevice(PIDA, DID1, description);
+        assertEquals(DEVICE_ADDED, event.type());
+        assertDevice(DID1, SW1, event.subject());
+        assertEquals(PIDA, event.subject().providerId());
+        assertAnnotationsEquals(event.subject().annotations(), A2);
+        assertFalse("Ancillary will not bring device up", deviceStore.isAvailable(DID1));
+
+        DeviceDescription description2 =
+                new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
+                        HW, SW2, SN, A1);
+        DeviceEvent event2 = deviceStore.createOrUpdateDevice(PID, DID1, description2);
+        assertEquals(DEVICE_UPDATED, event2.type());
+        assertDevice(DID1, SW2, event2.subject());
+        assertEquals(PID, event2.subject().providerId());
+        assertAnnotationsEquals(event2.subject().annotations(), A1, A2);
+        assertTrue(deviceStore.isAvailable(DID1));
+
+        assertNull("No change expected", deviceStore.createOrUpdateDevice(PID, DID1, description2));
+
+        // For now, Ancillary is ignored once primary appears
+        assertNull("No change expected", deviceStore.createOrUpdateDevice(PIDA, DID1, description));
+
+        // But, Ancillary annotations will be in effect
+        DeviceDescription description3 =
+                new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
+                        HW, SW1, SN, A2_2);
+        DeviceEvent event3 = deviceStore.createOrUpdateDevice(PIDA, DID1, description3);
+        assertEquals(DEVICE_UPDATED, event3.type());
+        // basic information will be the one from Primary
+        assertDevice(DID1, SW2, event3.subject());
+        assertEquals(PID, event3.subject().providerId());
+        // but annotation from Ancillary will be merged
+        assertAnnotationsEquals(event3.subject().annotations(), A1, A2, A2_2);
+        assertTrue(deviceStore.isAvailable(DID1));
+    }
+
+
+    @Test
+    public final void testMarkOffline() {
+
+        putDevice(DID1, SW1);
+        assertTrue(deviceStore.isAvailable(DID1));
+
+        DeviceEvent event = deviceStore.markOffline(DID1);
+        assertEquals(DEVICE_AVAILABILITY_CHANGED, event.type());
+        assertDevice(DID1, SW1, event.subject());
+        assertFalse(deviceStore.isAvailable(DID1));
+
+        DeviceEvent event2 = deviceStore.markOffline(DID1);
+        assertNull("No change, no event", event2);
+}
+
+    @Test
+    public final void testUpdatePorts() {
+        putDevice(DID1, SW1);
+        List<PortDescription> pds = Arrays.<PortDescription>asList(
+                new DefaultPortDescription(P1, true),
+                new DefaultPortDescription(P2, true)
+                );
+
+        List<DeviceEvent> events = deviceStore.updatePorts(PID, DID1, pds);
+
+        Set<PortNumber> expectedPorts = Sets.newHashSet(P1, P2);
+        for (DeviceEvent event : events) {
+            assertEquals(PORT_ADDED, event.type());
+            assertDevice(DID1, SW1, event.subject());
+            assertTrue("PortNumber is one of expected",
+                    expectedPorts.remove(event.port().number()));
+            assertTrue("Port is enabled", event.port().isEnabled());
+        }
+        assertTrue("Event for all expectedport appeared", expectedPorts.isEmpty());
+
+
+        List<PortDescription> pds2 = Arrays.<PortDescription>asList(
+                new DefaultPortDescription(P1, false),
+                new DefaultPortDescription(P2, true),
+                new DefaultPortDescription(P3, true)
+                );
+
+        events = deviceStore.updatePorts(PID, DID1, pds2);
+        assertFalse("event should be triggered", events.isEmpty());
+        for (DeviceEvent event : events) {
+            PortNumber num = event.port().number();
+            if (P1.equals(num)) {
+                assertEquals(PORT_UPDATED, event.type());
+                assertDevice(DID1, SW1, event.subject());
+                assertFalse("Port is disabled", event.port().isEnabled());
+            } else if (P2.equals(num)) {
+                fail("P2 event not expected.");
+            } else if (P3.equals(num)) {
+                assertEquals(PORT_ADDED, event.type());
+                assertDevice(DID1, SW1, event.subject());
+                assertTrue("Port is enabled", event.port().isEnabled());
+            } else {
+                fail("Unknown port number encountered: " + num);
+            }
+        }
+
+        List<PortDescription> pds3 = Arrays.<PortDescription>asList(
+                new DefaultPortDescription(P1, false),
+                new DefaultPortDescription(P2, true)
+                );
+        events = deviceStore.updatePorts(PID, DID1, pds3);
+        assertFalse("event should be triggered", events.isEmpty());
+        for (DeviceEvent event : events) {
+            PortNumber num = event.port().number();
+            if (P1.equals(num)) {
+                fail("P1 event not expected.");
+            } else if (P2.equals(num)) {
+                fail("P2 event not expected.");
+            } else if (P3.equals(num)) {
+                assertEquals(PORT_REMOVED, event.type());
+                assertDevice(DID1, SW1, event.subject());
+                assertTrue("Port was enabled", event.port().isEnabled());
+            } else {
+                fail("Unknown port number encountered: " + num);
+            }
+        }
+
+    }
+
+    @Test
+    public final void testUpdatePortStatus() {
+        putDevice(DID1, SW1);
+        List<PortDescription> pds = Arrays.<PortDescription>asList(
+                new DefaultPortDescription(P1, true)
+                );
+        deviceStore.updatePorts(PID, DID1, pds);
+
+        DeviceEvent event = deviceStore.updatePortStatus(PID, DID1,
+                new DefaultPortDescription(P1, false));
+        assertEquals(PORT_UPDATED, event.type());
+        assertDevice(DID1, SW1, event.subject());
+        assertEquals(P1, event.port().number());
+        assertFalse("Port is disabled", event.port().isEnabled());
+
+    }
+    @Test
+    public final void testUpdatePortStatusAncillary() {
+        putDeviceAncillary(DID1, SW1);
+        putDevice(DID1, SW1);
+        List<PortDescription> pds = Arrays.<PortDescription>asList(
+                new DefaultPortDescription(P1, true, A1)
+                );
+        deviceStore.updatePorts(PID, DID1, pds);
+
+        DeviceEvent event = deviceStore.updatePortStatus(PID, DID1,
+                new DefaultPortDescription(P1, false, A1_2));
+        assertEquals(PORT_UPDATED, event.type());
+        assertDevice(DID1, SW1, event.subject());
+        assertEquals(P1, event.port().number());
+        assertAnnotationsEquals(event.port().annotations(), A1, A1_2);
+        assertFalse("Port is disabled", event.port().isEnabled());
+
+        DeviceEvent event2 = deviceStore.updatePortStatus(PIDA, DID1,
+                new DefaultPortDescription(P1, true));
+        assertNull("Ancillary is ignored if primary exists", event2);
+
+        // but, Ancillary annotation update will be notified
+        DeviceEvent event3 = deviceStore.updatePortStatus(PIDA, DID1,
+                new DefaultPortDescription(P1, true, A2));
+        assertEquals(PORT_UPDATED, event3.type());
+        assertDevice(DID1, SW1, event3.subject());
+        assertEquals(P1, event3.port().number());
+        assertAnnotationsEquals(event3.port().annotations(), A1, A1_2, A2);
+        assertFalse("Port is disabled", event3.port().isEnabled());
+
+        // port only reported from Ancillary will be notified as down
+        DeviceEvent event4 = deviceStore.updatePortStatus(PIDA, DID1,
+                new DefaultPortDescription(P2, true));
+        assertEquals(PORT_ADDED, event4.type());
+        assertDevice(DID1, SW1, event4.subject());
+        assertEquals(P2, event4.port().number());
+        assertAnnotationsEquals(event4.port().annotations());
+        assertFalse("Port is disabled if not given from primary provider",
+                        event4.port().isEnabled());
+    }
+
+    @Test
+    public final void testGetPorts() {
+        putDevice(DID1, SW1);
+        putDevice(DID2, SW1);
+        List<PortDescription> pds = Arrays.<PortDescription>asList(
+                new DefaultPortDescription(P1, true),
+                new DefaultPortDescription(P2, true)
+                );
+        deviceStore.updatePorts(PID, DID1, pds);
+
+        Set<PortNumber> expectedPorts = Sets.newHashSet(P1, P2);
+        List<Port> ports = deviceStore.getPorts(DID1);
+        for (Port port : ports) {
+            assertTrue("Port is enabled", port.isEnabled());
+            assertTrue("PortNumber is one of expected",
+                    expectedPorts.remove(port.number()));
+        }
+        assertTrue("Event for all expectedport appeared", expectedPorts.isEmpty());
+
+
+        assertTrue("DID2 has no ports", deviceStore.getPorts(DID2).isEmpty());
+    }
+
+    @Test
+    public final void testGetPort() {
+        putDevice(DID1, SW1);
+        putDevice(DID2, SW1);
+        List<PortDescription> pds = Arrays.<PortDescription>asList(
+                new DefaultPortDescription(P1, true),
+                new DefaultPortDescription(P2, false)
+                );
+        deviceStore.updatePorts(PID, DID1, pds);
+
+        Port port1 = deviceStore.getPort(DID1, P1);
+        assertEquals(P1, port1.number());
+        assertTrue("Port is enabled", port1.isEnabled());
+
+        Port port2 = deviceStore.getPort(DID1, P2);
+        assertEquals(P2, port2.number());
+        assertFalse("Port is disabled", port2.isEnabled());
+
+        Port port3 = deviceStore.getPort(DID1, P3);
+        assertNull("P3 not expected", port3);
+    }
+
+    @Test
+    public final void testRemoveDevice() {
+        putDevice(DID1, SW1, A1);
+        List<PortDescription> pds = Arrays.<PortDescription>asList(
+                new DefaultPortDescription(P1, true, A2)
+                );
+        deviceStore.updatePorts(PID, DID1, pds);
+        putDevice(DID2, SW1);
+
+        assertEquals(2, deviceStore.getDeviceCount());
+        assertEquals(1, deviceStore.getPorts(DID1).size());
+        assertAnnotationsEquals(deviceStore.getDevice(DID1).annotations(), A1);
+        assertAnnotationsEquals(deviceStore.getPort(DID1, P1).annotations(), A2);
+
+        DeviceEvent event = deviceStore.removeDevice(DID1);
+        assertEquals(DEVICE_REMOVED, event.type());
+        assertDevice(DID1, SW1, event.subject());
+
+        assertEquals(1, deviceStore.getDeviceCount());
+        assertEquals(0, deviceStore.getPorts(DID1).size());
+
+        // putBack Device, Port w/o annotation
+        putDevice(DID1, SW1);
+        List<PortDescription> pds2 = Arrays.<PortDescription>asList(
+                new DefaultPortDescription(P1, true)
+                );
+        deviceStore.updatePorts(PID, DID1, pds2);
+
+        // annotations should not survive
+        assertEquals(2, deviceStore.getDeviceCount());
+        assertEquals(1, deviceStore.getPorts(DID1).size());
+        assertAnnotationsEquals(deviceStore.getDevice(DID1).annotations());
+        assertAnnotationsEquals(deviceStore.getPort(DID1, P1).annotations());
+    }
+
+    // If Delegates should be called only on remote events,
+    // then Simple* should never call them, thus not test required.
+    // TODO add test for Port events when we have them
+    @Ignore("Ignore until Delegate spec. is clear.")
+    @Test
+    public final void testEvents() throws InterruptedException {
+        final CountDownLatch addLatch = new CountDownLatch(1);
+        DeviceStoreDelegate checkAdd = new DeviceStoreDelegate() {
+            @Override
+            public void notify(DeviceEvent event) {
+                assertEquals(DEVICE_ADDED, event.type());
+                assertDevice(DID1, SW1, event.subject());
+                addLatch.countDown();
+            }
+        };
+        final CountDownLatch updateLatch = new CountDownLatch(1);
+        DeviceStoreDelegate checkUpdate = new DeviceStoreDelegate() {
+            @Override
+            public void notify(DeviceEvent event) {
+                assertEquals(DEVICE_UPDATED, event.type());
+                assertDevice(DID1, SW2, event.subject());
+                updateLatch.countDown();
+            }
+        };
+        final CountDownLatch removeLatch = new CountDownLatch(1);
+        DeviceStoreDelegate checkRemove = new DeviceStoreDelegate() {
+            @Override
+            public void notify(DeviceEvent event) {
+                assertEquals(DEVICE_REMOVED, event.type());
+                assertDevice(DID1, SW2, event.subject());
+                removeLatch.countDown();
+            }
+        };
+
+        DeviceDescription description =
+                new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
+                        HW, SW1, SN);
+        deviceStore.setDelegate(checkAdd);
+        deviceStore.createOrUpdateDevice(PID, DID1, description);
+        assertTrue("Add event fired", addLatch.await(1, TimeUnit.SECONDS));
+
+
+        DeviceDescription description2 =
+                new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
+                        HW, SW2, SN);
+        deviceStore.unsetDelegate(checkAdd);
+        deviceStore.setDelegate(checkUpdate);
+        deviceStore.createOrUpdateDevice(PID, DID1, description2);
+        assertTrue("Update event fired", updateLatch.await(1, TimeUnit.SECONDS));
+
+        deviceStore.unsetDelegate(checkUpdate);
+        deviceStore.setDelegate(checkRemove);
+        deviceStore.removeDevice(DID1);
+        assertTrue("Remove event fired", removeLatch.await(1, TimeUnit.SECONDS));
+    }
+
+    private static final class TestGossipDeviceStore extends GossipDeviceStore {
+
+        public TestGossipDeviceStore(
+                ClockService clockService,
+                ClusterService clusterService,
+                ClusterCommunicationService clusterCommunicator) {
+            this.clockService = clockService;
+            this.clusterService = clusterService;
+            this.clusterCommunicator = clusterCommunicator;
+        }
+    }
+
+    private static final class TestClusterCommunicationService implements ClusterCommunicationService {
+        @Override
+        public boolean broadcast(ClusterMessage message) throws IOException { return true; }
+        @Override
+        public boolean unicast(ClusterMessage message, NodeId nodeId) throws IOException { return true; }
+        @Override
+        public boolean multicast(ClusterMessage message, Set<NodeId> nodeIds) throws IOException { return true; }
+        @Override
+        public void addSubscriber(MessageSubject subject, ClusterMessageHandler subscriber) {}
+    }
+
+    private static final class TestClusterService implements ClusterService {
+
+        private static final ControllerNode ONOS1 =
+            new DefaultControllerNode(new NodeId("N1"), IpPrefix.valueOf("127.0.0.1"));
+        private final Map<NodeId, ControllerNode> nodes = new HashMap<>();
+        private final Map<NodeId, ControllerNode.State> nodeStates = new HashMap<>();
+
+        public TestClusterService() {
+            nodes.put(new NodeId("N1"), ONOS1);
+            nodeStates.put(new NodeId("N1"), ControllerNode.State.ACTIVE);
+        }
+
+        @Override
+        public ControllerNode getLocalNode() {
+            return ONOS1;
+        }
+
+        @Override
+        public Set<ControllerNode> getNodes() {
+            return Sets.newHashSet(nodes.values());
+        }
+
+        @Override
+        public ControllerNode getNode(NodeId nodeId) {
+            return nodes.get(nodeId);
+        }
+
+        @Override
+        public State getState(NodeId nodeId) {
+            return nodeStates.get(nodeId);
+        }
+
+        @Override
+        public void addListener(ClusterEventListener listener) {
+        }
+
+        @Override
+        public void removeListener(ClusterEventListener listener) {
+        }
+    }
+}
diff --git a/core/store/hz/cluster/src/main/java/org/onlab/onos/store/cluster/impl/DistributedClusterStore.java b/core/store/hz/cluster/src/main/java/org/onlab/onos/store/cluster/impl/DistributedClusterStore.java
index f83ac59..61a7374 100644
--- a/core/store/hz/cluster/src/main/java/org/onlab/onos/store/cluster/impl/DistributedClusterStore.java
+++ b/core/store/hz/cluster/src/main/java/org/onlab/onos/store/cluster/impl/DistributedClusterStore.java
@@ -57,7 +57,7 @@
 
         rawNodes = theInstance.getMap("nodes");
         OptionalCacheLoader<NodeId, DefaultControllerNode> nodeLoader
-                = new OptionalCacheLoader<>(kryoSerializationService, rawNodes);
+                = new OptionalCacheLoader<>(serializer, rawNodes);
         nodes = new AbsentInvalidatingLoadingCache<>(newBuilder().build(nodeLoader));
         rawNodes.addEntryListener(new RemoteCacheEventHandler<>(nodes), true);
 
diff --git a/core/store/hz/cluster/src/test/java/org/onlab/onos/store/cluster/impl/DistributedMastershipStoreTest.java b/core/store/hz/cluster/src/test/java/org/onlab/onos/store/cluster/impl/DistributedMastershipStoreTest.java
index 9178e90..bf1bb38 100644
--- a/core/store/hz/cluster/src/test/java/org/onlab/onos/store/cluster/impl/DistributedMastershipStoreTest.java
+++ b/core/store/hz/cluster/src/test/java/org/onlab/onos/store/cluster/impl/DistributedMastershipStoreTest.java
@@ -30,8 +30,7 @@
 import org.onlab.onos.store.common.StoreManager;
 import org.onlab.onos.store.common.StoreService;
 import org.onlab.onos.store.common.TestStoreManager;
-import org.onlab.onos.store.serializers.KryoSerializationManager;
-import org.onlab.onos.store.serializers.KryoSerializationService;
+import org.onlab.onos.store.serializers.KryoSerializer;
 import org.onlab.packet.IpPrefix;
 
 import com.google.common.collect.Sets;
@@ -57,7 +56,7 @@
 
     private DistributedMastershipStore dms;
     private TestDistributedMastershipStore testStore;
-    private KryoSerializationManager serializationMgr;
+    private KryoSerializer serializationMgr;
     private StoreManager storeMgr;
 
     @BeforeClass
@@ -76,8 +75,7 @@
         storeMgr = new TestStoreManager(Hazelcast.newHazelcastInstance(config));
         storeMgr.activate();
 
-        serializationMgr = new KryoSerializationManager();
-        serializationMgr.activate();
+        serializationMgr = new KryoSerializer();
 
         dms = new TestDistributedMastershipStore(storeMgr, serializationMgr);
         dms.clusterService = new TestClusterService();
@@ -90,8 +88,6 @@
     public void tearDown() throws Exception {
         dms.deactivate();
 
-        serializationMgr.deactivate();
-
         storeMgr.deactivate();
     }
 
@@ -234,9 +230,9 @@
     private class TestDistributedMastershipStore extends
             DistributedMastershipStore {
         public TestDistributedMastershipStore(StoreService storeService,
-                KryoSerializationService kryoSerializationService) {
+                KryoSerializer kryoSerialization) {
             this.storeService = storeService;
-            this.kryoSerializationService = kryoSerializationService;
+            this.serializer = kryoSerialization;
         }
 
         //helper to populate master/backup structures
@@ -260,6 +256,7 @@
             }
         }
 
+        //a dumb utility function.
         public void dump() {
             System.out.println("standbys");
             for (Map.Entry<byte [], byte []> e : standbys.entrySet()) {
diff --git a/core/store/hz/common/src/main/java/org/onlab/onos/store/common/AbstractHazelcastStore.java b/core/store/hz/common/src/main/java/org/onlab/onos/store/common/AbstractHazelcastStore.java
index 0302105..a22dd89 100644
--- a/core/store/hz/common/src/main/java/org/onlab/onos/store/common/AbstractHazelcastStore.java
+++ b/core/store/hz/common/src/main/java/org/onlab/onos/store/common/AbstractHazelcastStore.java
@@ -15,7 +15,8 @@
 import org.onlab.onos.event.Event;
 import org.onlab.onos.store.AbstractStore;
 import org.onlab.onos.store.StoreDelegate;
-import org.onlab.onos.store.serializers.KryoSerializationService;
+import org.onlab.onos.store.serializers.KryoSerializer;
+import org.onlab.onos.store.serializers.StoreSerializer;
 import org.slf4j.Logger;
 
 import static com.google.common.base.Preconditions.checkNotNull;
@@ -24,7 +25,7 @@
 /**
  * Abstraction of a distributed store based on Hazelcast.
  */
-@Component(componentAbstract = true)
+@Component
 public abstract class AbstractHazelcastStore<E extends Event, D extends StoreDelegate<E>>
         extends AbstractStore<E, D> {
 
@@ -33,13 +34,13 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected StoreService storeService;
 
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected KryoSerializationService kryoSerializationService;
+    protected StoreSerializer serializer;
 
     protected HazelcastInstance theInstance;
 
     @Activate
     public void activate() {
+        serializer = new KryoSerializer();
         theInstance = storeService.getHazelcastInstance();
     }
 
@@ -50,7 +51,7 @@
      * @return serialized object
      */
     protected byte[] serialize(Object obj) {
-        return kryoSerializationService.serialize(obj);
+        return serializer.encode(obj);
     }
 
     /**
@@ -61,7 +62,7 @@
      * @return deserialized object
      */
     protected <T> T deserialize(byte[] bytes) {
-        return kryoSerializationService.deserialize(bytes);
+        return serializer.decode(bytes);
     }
 
 
diff --git a/core/store/hz/common/src/main/java/org/onlab/onos/store/common/OptionalCacheLoader.java b/core/store/hz/common/src/main/java/org/onlab/onos/store/common/OptionalCacheLoader.java
index f96fdd8..d5fc380 100644
--- a/core/store/hz/common/src/main/java/org/onlab/onos/store/common/OptionalCacheLoader.java
+++ b/core/store/hz/common/src/main/java/org/onlab/onos/store/common/OptionalCacheLoader.java
@@ -2,7 +2,7 @@
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
-import org.onlab.onos.store.serializers.KryoSerializationService;
+import org.onlab.onos.store.serializers.StoreSerializer;
 
 import com.google.common.base.Optional;
 import com.google.common.cache.CacheLoader;
@@ -18,28 +18,28 @@
 public final class OptionalCacheLoader<K, V> extends
         CacheLoader<K, Optional<V>> {
 
-    private final KryoSerializationService kryoSerializationService;
+    private final StoreSerializer serializer;
     private IMap<byte[], byte[]> rawMap;
 
     /**
      * Constructor.
      *
-     * @param kryoSerializationService to use for serialization
+     * @param serializer to use for serialization
      * @param rawMap underlying IMap
      */
-    public OptionalCacheLoader(KryoSerializationService kryoSerializationService, IMap<byte[], byte[]> rawMap) {
-        this.kryoSerializationService = checkNotNull(kryoSerializationService);
+    public OptionalCacheLoader(StoreSerializer serializer, IMap<byte[], byte[]> rawMap) {
+        this.serializer = checkNotNull(serializer);
         this.rawMap = checkNotNull(rawMap);
     }
 
     @Override
     public Optional<V> load(K key) throws Exception {
-        byte[] keyBytes = kryoSerializationService.serialize(key);
+        byte[] keyBytes = serializer.encode(key);
         byte[] valBytes = rawMap.get(keyBytes);
         if (valBytes == null) {
             return Optional.absent();
         }
-        V dev = kryoSerializationService.deserialize(valBytes);
+        V dev = serializer.decode(valBytes);
         return Optional.of(dev);
     }
 }
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 5feb1ba..0016939 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
@@ -47,6 +47,7 @@
 import static org.onlab.onos.net.device.DeviceEvent.Type.*;
 import static org.slf4j.LoggerFactory.getLogger;
 
+//TODO: Add support for multiple provider and annotations
 /**
  * Manages inventory of infrastructure devices using Hazelcast-backed map.
  */
@@ -87,7 +88,7 @@
         // TODO decide on Map name scheme to avoid collision
         rawDevices = theInstance.getMap("devices");
         final OptionalCacheLoader<DeviceId, DefaultDevice> deviceLoader
-                = new OptionalCacheLoader<>(kryoSerializationService, rawDevices);
+                = new OptionalCacheLoader<>(serializer, rawDevices);
         devices = new AbsentInvalidatingLoadingCache<>(newBuilder().build(deviceLoader));
         // refresh/populate cache based on notification from other instance
         devicesListener = rawDevices.addEntryListener(new RemoteDeviceEventHandler(devices), includeValue);
@@ -97,7 +98,7 @@
 
         rawDevicePorts = theInstance.getMap("devicePorts");
         final OptionalCacheLoader<DeviceId, Map<PortNumber, Port>> devicePortLoader
-                = new OptionalCacheLoader<>(kryoSerializationService, rawDevicePorts);
+                = new OptionalCacheLoader<>(serializer, rawDevicePorts);
         devicePorts = new AbsentInvalidatingLoadingCache<>(newBuilder().build(devicePortLoader));
         // refresh/populate cache based on notification from other instance
         portsListener = rawDevicePorts.addEntryListener(new RemotePortEventHandler(devicePorts), includeValue);
diff --git a/core/store/hz/net/src/main/java/org/onlab/onos/store/device/impl/NoOpClockProviderService.java b/core/store/hz/net/src/main/java/org/onlab/onos/store/device/impl/NoOpClockProviderService.java
new file mode 100644
index 0000000..b68620a
--- /dev/null
+++ b/core/store/hz/net/src/main/java/org/onlab/onos/store/device/impl/NoOpClockProviderService.java
@@ -0,0 +1,20 @@
+package org.onlab.onos.store.device.impl;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.onos.cluster.MastershipTerm;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.store.ClockProviderService;
+
+// FIXME: Code clone in onos-core-trivial, onos-core-hz-net
+/**
+ * Dummy implementation of {@link ClockProviderService}.
+ */
+@Component(immediate = true)
+@Service
+public class NoOpClockProviderService implements ClockProviderService {
+
+    @Override
+    public void setMastershipTerm(DeviceId deviceId, MastershipTerm term) {
+    }
+}
diff --git a/core/store/hz/net/src/main/java/org/onlab/onos/store/device/impl/NoOpClockService.java b/core/store/hz/net/src/main/java/org/onlab/onos/store/device/impl/NoOpClockService.java
deleted file mode 100644
index 2c443e9..0000000
--- a/core/store/hz/net/src/main/java/org/onlab/onos/store/device/impl/NoOpClockService.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.onlab.onos.store.device.impl;
-
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Service;
-import org.onlab.onos.cluster.MastershipTerm;
-import org.onlab.onos.net.DeviceId;
-import org.onlab.onos.store.ClockService;
-import org.onlab.onos.store.Timestamp;
-
-// FIXME: Code clone in onos-core-trivial, onos-core-hz-net
-/**
- * Dummy implementation of {@link ClockService}.
- */
-@Component(immediate = true)
-@Service
-public class NoOpClockService implements ClockService {
-
-    @Override
-    public Timestamp getTimestamp(DeviceId deviceId) {
-        return new Timestamp() {
-
-            @Override
-            public int compareTo(Timestamp o) {
-                throw new IllegalStateException("Never expected to be used.");
-            }
-        };
-    }
-
-    @Override
-    public void setMastershipTerm(DeviceId deviceId, MastershipTerm term) {
-    }
-}
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 6ec7c51..d49e00b 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
@@ -1,6 +1,5 @@
 package org.onlab.onos.store.flow.impl;
 
-import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_ADDED;
 import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
 import static org.slf4j.LoggerFactory.getLogger;
 
@@ -13,9 +12,10 @@
 import org.apache.felix.scr.annotations.Service;
 import org.onlab.onos.ApplicationId;
 import org.onlab.onos.net.DeviceId;
-import org.onlab.onos.net.flow.DefaultFlowRule;
+import org.onlab.onos.net.flow.DefaultFlowEntry;
+import org.onlab.onos.net.flow.FlowEntry;
+import org.onlab.onos.net.flow.FlowEntry.FlowEntryState;
 import org.onlab.onos.net.flow.FlowRule;
-import org.onlab.onos.net.flow.FlowRule.FlowRuleState;
 import org.onlab.onos.net.flow.FlowRuleEvent;
 import org.onlab.onos.net.flow.FlowRuleEvent.Type;
 import org.onlab.onos.net.flow.FlowRuleStore;
@@ -28,20 +28,20 @@
 import com.google.common.collect.Multimap;
 
 /**
- * TEMPORARY: Manages inventory of flow rules using distributed store implementation.
+ * Manages inventory of flow rules using trivial in-memory implementation.
  */
-//FIXME: I LIE I AM NOT DISTRIBUTED
+//FIXME I LIE. I AIN'T DISTRIBUTED
 @Component(immediate = true)
 @Service
 public class DistributedFlowRuleStore
-extends AbstractStore<FlowRuleEvent, FlowRuleStoreDelegate>
-implements FlowRuleStore {
+        extends AbstractStore<FlowRuleEvent, FlowRuleStoreDelegate>
+        implements FlowRuleStore {
 
     private final Logger log = getLogger(getClass());
 
     // store entries as a pile of rules, no info about device tables
-    private final Multimap<DeviceId, FlowRule> flowEntries =
-            ArrayListMultimap.<DeviceId, FlowRule>create();
+    private final Multimap<DeviceId, FlowEntry> flowEntries =
+            ArrayListMultimap.<DeviceId, FlowEntry>create();
 
     private final Multimap<ApplicationId, FlowRule> flowEntriesById =
             ArrayListMultimap.<ApplicationId, FlowRule>create();
@@ -58,8 +58,13 @@
 
 
     @Override
-    public synchronized FlowRule getFlowRule(FlowRule rule) {
-        for (FlowRule f : flowEntries.get(rule.deviceId())) {
+    public int getFlowRuleCount() {
+        return flowEntries.size();
+    }
+
+    @Override
+    public synchronized FlowEntry getFlowEntry(FlowRule rule) {
+        for (FlowEntry f : flowEntries.get(rule.deviceId())) {
             if (f.equals(rule)) {
                 return f;
             }
@@ -68,8 +73,8 @@
     }
 
     @Override
-    public synchronized Iterable<FlowRule> getFlowEntries(DeviceId deviceId) {
-        Collection<FlowRule> rules = flowEntries.get(deviceId);
+    public synchronized Iterable<FlowEntry> getFlowEntries(DeviceId deviceId) {
+        Collection<FlowEntry> rules = flowEntries.get(deviceId);
         if (rules == null) {
             return Collections.emptyList();
         }
@@ -77,7 +82,7 @@
     }
 
     @Override
-    public synchronized Iterable<FlowRule> getFlowEntriesByAppId(ApplicationId appId) {
+    public synchronized Iterable<FlowRule> getFlowRulesByAppId(ApplicationId appId) {
         Collection<FlowRule> rules = flowEntriesById.get(appId);
         if (rules == null) {
             return Collections.emptyList();
@@ -87,7 +92,7 @@
 
     @Override
     public synchronized void storeFlowRule(FlowRule rule) {
-        FlowRule f = new DefaultFlowRule(rule, FlowRuleState.PENDING_ADD);
+        FlowEntry f = new DefaultFlowEntry(rule);
         DeviceId did = f.deviceId();
         if (!flowEntries.containsEntry(did, f)) {
             flowEntries.put(did, f);
@@ -97,57 +102,41 @@
 
     @Override
     public synchronized void deleteFlowRule(FlowRule rule) {
-        FlowRule f = new DefaultFlowRule(rule, FlowRuleState.PENDING_REMOVE);
-        DeviceId did = f.deviceId();
-
-        /*
-         *  find the rule and mark it for deletion.
-         *  Ultimately a flow removed will come remove it.
-         */
-
-        if (flowEntries.containsEntry(did, f)) {
-            //synchronized (flowEntries) {
-            flowEntries.remove(did, f);
-            flowEntries.put(did, f);
-            flowEntriesById.remove(rule.appId(), rule);
-            //}
+        FlowEntry entry = getFlowEntry(rule);
+        if (entry == null) {
+            return;
         }
+        entry.setState(FlowEntryState.PENDING_REMOVE);
     }
 
     @Override
-    public synchronized FlowRuleEvent addOrUpdateFlowRule(FlowRule rule) {
+    public synchronized FlowRuleEvent addOrUpdateFlowRule(FlowEntry rule) {
         DeviceId did = rule.deviceId();
 
         // check if this new rule is an update to an existing entry
-        if (flowEntries.containsEntry(did, rule)) {
-            //synchronized (flowEntries) {
-            // Multimaps support duplicates so we have to remove our rule
-            // and replace it with the current version.
-            flowEntries.remove(did, rule);
-            flowEntries.put(did, rule);
-            //}
+        FlowEntry stored = getFlowEntry(rule);
+        if (stored != null) {
+            stored.setBytes(rule.bytes());
+            stored.setLife(rule.life());
+            stored.setPackets(rule.packets());
+            if (stored.state() == FlowEntryState.PENDING_ADD) {
+                stored.setState(FlowEntryState.ADDED);
+                return new FlowRuleEvent(Type.RULE_ADDED, rule);
+            }
             return new FlowRuleEvent(Type.RULE_UPDATED, rule);
         }
 
         flowEntries.put(did, rule);
-        return new FlowRuleEvent(RULE_ADDED, rule);
+        return null;
     }
 
     @Override
-    public synchronized FlowRuleEvent removeFlowRule(FlowRule rule) {
-        //synchronized (this) {
+    public synchronized FlowRuleEvent removeFlowRule(FlowEntry rule) {
+        // This is where one could mark a rule as removed and still keep it in the store.
         if (flowEntries.remove(rule.deviceId(), rule)) {
             return new FlowRuleEvent(RULE_REMOVED, rule);
         } else {
             return null;
         }
-        //}
     }
-
-
-
-
-
-
-
 }
diff --git a/core/store/hz/net/src/main/java/org/onlab/onos/store/link/impl/DistributedLinkStore.java b/core/store/hz/net/src/main/java/org/onlab/onos/store/link/impl/DistributedLinkStore.java
index 5161f2f..3dd42a3 100644
--- a/core/store/hz/net/src/main/java/org/onlab/onos/store/link/impl/DistributedLinkStore.java
+++ b/core/store/hz/net/src/main/java/org/onlab/onos/store/link/impl/DistributedLinkStore.java
@@ -38,6 +38,7 @@
 import com.google.common.collect.ImmutableSet.Builder;
 import com.hazelcast.core.IMap;
 
+//TODO: Add support for multiple provider and annotations
 /**
  * Manages inventory of infrastructure links using Hazelcast-backed map.
  */
@@ -70,7 +71,7 @@
         // TODO decide on Map name scheme to avoid collision
         rawLinks = theInstance.getMap("links");
         final OptionalCacheLoader<LinkKey, DefaultLink> linkLoader
-                = new OptionalCacheLoader<>(kryoSerializationService, rawLinks);
+                = new OptionalCacheLoader<>(serializer, rawLinks);
         links = new AbsentInvalidatingLoadingCache<>(newBuilder().build(linkLoader));
         // refresh/populate cache based on notification from other instance
         linksListener = rawLinks.addEntryListener(new RemoteLinkEventHandler(links), includeValue);
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 4728850..04f5fce 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
@@ -125,7 +125,8 @@
         // Promote the new topology to current and return a ready-to-send event.
         synchronized (this) {
             current = newTopology;
-            return new TopologyEvent(TopologyEvent.Type.TOPOLOGY_CHANGED, current);
+            return new TopologyEvent(TopologyEvent.Type.TOPOLOGY_CHANGED,
+                                     current, reasons);
         }
     }
 
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 80c9464..7e2924b 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
@@ -36,9 +36,6 @@
 import org.onlab.onos.store.common.StoreManager;
 import org.onlab.onos.store.common.StoreService;
 import org.onlab.onos.store.common.TestStoreManager;
-import org.onlab.onos.store.serializers.KryoSerializationManager;
-import org.onlab.onos.store.serializers.KryoSerializationService;
-
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import com.hazelcast.config.Config;
@@ -63,7 +60,6 @@
     private static final PortNumber P3 = PortNumber.portNumber(3);
 
     private DistributedDeviceStore deviceStore;
-    private KryoSerializationManager serializationMgr;
 
     private StoreManager storeManager;
 
@@ -85,10 +81,7 @@
         storeManager = new TestStoreManager(Hazelcast.newHazelcastInstance(config));
         storeManager.activate();
 
-        serializationMgr = new KryoSerializationManager();
-        serializationMgr.activate();
-
-        deviceStore = new TestDistributedDeviceStore(storeManager, serializationMgr);
+        deviceStore = new TestDistributedDeviceStore(storeManager);
         deviceStore.activate();
     }
 
@@ -96,8 +89,6 @@
     public void tearDown() throws Exception {
         deviceStore.deactivate();
 
-        serializationMgr.deactivate();
-
         storeManager.deactivate();
     }
 
@@ -392,10 +383,8 @@
     }
 
     private class TestDistributedDeviceStore extends DistributedDeviceStore {
-        public TestDistributedDeviceStore(StoreService storeService,
-                                KryoSerializationService kryoSerializationService) {
+        public TestDistributedDeviceStore(StoreService storeService) {
             this.storeService = storeService;
-            this.kryoSerializationService = kryoSerializationService;
         }
     }
 }
diff --git a/core/store/hz/net/src/test/java/org/onlab/onos/store/link/impl/DistributedLinkStoreTest.java b/core/store/hz/net/src/test/java/org/onlab/onos/store/link/impl/DistributedLinkStoreTest.java
index a76e901..dd959b5 100644
--- a/core/store/hz/net/src/test/java/org/onlab/onos/store/link/impl/DistributedLinkStoreTest.java
+++ b/core/store/hz/net/src/test/java/org/onlab/onos/store/link/impl/DistributedLinkStoreTest.java
@@ -30,9 +30,6 @@
 import org.onlab.onos.store.common.StoreManager;
 import org.onlab.onos.store.common.StoreService;
 import org.onlab.onos.store.common.TestStoreManager;
-import org.onlab.onos.store.serializers.KryoSerializationManager;
-import org.onlab.onos.store.serializers.KryoSerializationService;
-
 import com.google.common.collect.Iterables;
 import com.hazelcast.config.Config;
 import com.hazelcast.core.Hazelcast;
@@ -51,7 +48,6 @@
     private static final PortNumber P3 = PortNumber.portNumber(3);
 
     private StoreManager storeManager;
-    private KryoSerializationManager serializationMgr;
 
     private DistributedLinkStore linkStore;
 
@@ -71,17 +67,13 @@
         storeManager = new TestStoreManager(Hazelcast.newHazelcastInstance(config));
         storeManager.activate();
 
-        serializationMgr = new KryoSerializationManager();
-        serializationMgr.activate();
-
-        linkStore = new TestDistributedLinkStore(storeManager, serializationMgr);
+        linkStore = new TestDistributedLinkStore(storeManager);
         linkStore.activate();
     }
 
     @After
     public void tearDown() throws Exception {
         linkStore.deactivate();
-        serializationMgr.deactivate();
         storeManager.deactivate();
     }
 
@@ -361,10 +353,8 @@
 
 
     class TestDistributedLinkStore extends DistributedLinkStore {
-        TestDistributedLinkStore(StoreService storeService,
-                            KryoSerializationService kryoSerializationService) {
+        TestDistributedLinkStore(StoreService storeService) {
             this.storeService = storeService;
-            this.kryoSerializationService = kryoSerializationService;
         }
     }
 }
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/ConnectPointSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/ConnectPointSerializer.java
index 46badcb..14a64d2 100644
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/ConnectPointSerializer.java
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/ConnectPointSerializer.java
@@ -3,7 +3,6 @@
 import org.onlab.onos.net.ConnectPoint;
 import org.onlab.onos.net.ElementId;
 import org.onlab.onos.net.PortNumber;
-
 import com.esotericsoftware.kryo.Kryo;
 import com.esotericsoftware.kryo.Serializer;
 import com.esotericsoftware.kryo.io.Input;
@@ -15,7 +14,7 @@
 public class ConnectPointSerializer extends Serializer<ConnectPoint> {
 
     /**
-     * Default constructor.
+     * Creates {@link ConnectPointSerializer} serializer instance.
      */
     public ConnectPointSerializer() {
         // non-null, immutable
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/DefaultLinkSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/DefaultLinkSerializer.java
index 5ee273d..06d01b5 100644
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/DefaultLinkSerializer.java
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/DefaultLinkSerializer.java
@@ -16,7 +16,7 @@
 public class DefaultLinkSerializer extends Serializer<DefaultLink> {
 
     /**
-     * Default constructor.
+     * Creates {@link DefaultLink} serializer instance.
      */
     public DefaultLinkSerializer() {
         // non-null, immutable
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/DefaultPortSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/DefaultPortSerializer.java
index 8455e80..5dc310b 100644
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/DefaultPortSerializer.java
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/DefaultPortSerializer.java
@@ -16,7 +16,7 @@
         Serializer<DefaultPort> {
 
     /**
-     * Default constructor.
+     * Creates {@link DefaultPort} serializer instance.
      */
     public DefaultPortSerializer() {
         // non-null, immutable
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/DeviceIdSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/DeviceIdSerializer.java
index c63b676..36d0a21 100644
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/DeviceIdSerializer.java
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/DeviceIdSerializer.java
@@ -14,6 +14,14 @@
 */
 public final class DeviceIdSerializer extends Serializer<DeviceId> {
 
+    /**
+     * Creates {@link DeviceId} serializer instance.
+     */
+    public DeviceIdSerializer() {
+        // non-null, immutable
+        super(false, true);
+    }
+
     @Override
     public void write(Kryo kryo, Output output, DeviceId object) {
         kryo.writeObject(output, object.uri());
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
index 244cc57..734033f 100644
--- 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
@@ -19,6 +19,9 @@
 
     private final MapSerializer mapSerializer = new MapSerializer();
 
+    /**
+     * Creates {@link ImmutableMap} serializer instance.
+     */
     public ImmutableMapSerializer() {
         // non-null, immutable
         super(false, true);
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
index c08bf9a..051a843 100644
--- 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
@@ -18,6 +18,9 @@
 
     private final CollectionSerializer serializer = new CollectionSerializer();
 
+    /**
+     * Creates {@link ImmutableSet} serializer instance.
+     */
     public ImmutableSetSerializer() {
         // non-null, immutable
         super(false, true);
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/IpAddressSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/IpAddressSerializer.java
new file mode 100644
index 0000000..b923df7
--- /dev/null
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/IpAddressSerializer.java
@@ -0,0 +1,41 @@
+package org.onlab.onos.store.serializers;
+
+import org.onlab.packet.IpAddress;
+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 IpAddress}.
+ */
+public class IpAddressSerializer extends Serializer<IpAddress> {
+
+    /**
+     * Creates {@link IpAddress} serializer instance.
+     */
+    public IpAddressSerializer() {
+        // non-null, immutable
+        super(false, true);
+    }
+
+    @Override
+    public void write(Kryo kryo, Output output,
+            IpAddress object) {
+        byte[] octs = object.toOctets();
+        output.writeInt(octs.length);
+        output.writeBytes(octs);
+        output.writeInt(object.prefixLength());
+    }
+
+    @Override
+    public IpAddress read(Kryo kryo, Input input,
+            Class<IpAddress> type) {
+        int octLen = input.readInt();
+        byte[] octs = new byte[octLen];
+        input.read(octs);
+        int prefLen = input.readInt();
+        return IpAddress.valueOf(octs, prefLen);
+    }
+
+}
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/IpPrefixSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/IpPrefixSerializer.java
index 2dbec57..2e92692 100644
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/IpPrefixSerializer.java
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/IpPrefixSerializer.java
@@ -13,7 +13,7 @@
 public final class IpPrefixSerializer extends Serializer<IpPrefix> {
 
     /**
-     * Default constructor.
+     * Creates {@link IpPrefix} serializer instance.
      */
     public IpPrefixSerializer() {
         // non-null, immutable
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoPoolUtil.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoPoolUtil.java
new file mode 100644
index 0000000..0c33cfe
--- /dev/null
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoPoolUtil.java
@@ -0,0 +1,85 @@
+package org.onlab.onos.store.serializers;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
+import org.onlab.onos.cluster.ControllerNode;
+import org.onlab.onos.cluster.DefaultControllerNode;
+import org.onlab.onos.cluster.MastershipTerm;
+import org.onlab.onos.cluster.NodeId;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DefaultAnnotations;
+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.Element;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.LinkKey;
+import org.onlab.onos.net.MastershipRole;
+import org.onlab.onos.net.Port;
+import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.device.DefaultDeviceDescription;
+import org.onlab.onos.net.device.DefaultPortDescription;
+import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.util.KryoPool;
+
+import de.javakaffee.kryoserializers.URISerializer;
+
+public final class KryoPoolUtil {
+
+    /**
+     * KryoPool which can serialize ON.lab misc classes.
+     */
+    public static final KryoPool MISC = KryoPool.newBuilder()
+            .register(IpPrefix.class, new IpPrefixSerializer())
+            .register(IpAddress.class, new IpAddressSerializer())
+            .build();
+
+    // TODO: Populate other classes
+    /**
+     * KryoPool which can serialize API bundle classes.
+     */
+    public static final KryoPool API = KryoPool.newBuilder()
+            .register(MISC)
+            .register(
+                    //
+                    ArrayList.class,
+                    Arrays.asList().getClass(),
+                    HashMap.class,
+                    //
+                    ControllerNode.State.class,
+                    Device.Type.class,
+                    DefaultAnnotations.class,
+                    DefaultControllerNode.class,
+                    DefaultDevice.class,
+                    DefaultDeviceDescription.class,
+                    MastershipRole.class,
+                    Port.class,
+                    DefaultPortDescription.class,
+                    Element.class,
+                    Link.Type.class
+                    )
+            .register(URI.class, new URISerializer())
+            .register(NodeId.class, new NodeIdSerializer())
+            .register(ProviderId.class, new ProviderIdSerializer())
+            .register(DeviceId.class, new DeviceIdSerializer())
+            .register(PortNumber.class, new PortNumberSerializer())
+            .register(DefaultPort.class, new DefaultPortSerializer())
+            .register(LinkKey.class, new LinkKeySerializer())
+            .register(ConnectPoint.class, new ConnectPointSerializer())
+            .register(DefaultLink.class, new DefaultLinkSerializer())
+            .register(MastershipTerm.class, new MastershipTermSerializer())
+            .register(MastershipRole.class, new MastershipRoleSerializer())
+
+            .build();
+
+
+    // not to be instantiated
+    private KryoPoolUtil() {}
+}
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
deleted file mode 100644
index 04d1a88..0000000
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoSerializationManager.java
+++ /dev/null
@@ -1,115 +0,0 @@
-package org.onlab.onos.store.serializers;
-
-import de.javakaffee.kryoserializers.URISerializer;
-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.NodeId;
-import org.onlab.onos.net.ConnectPoint;
-import org.onlab.onos.net.DefaultAnnotations;
-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.Element;
-import org.onlab.onos.net.Link;
-import org.onlab.onos.net.LinkKey;
-import org.onlab.onos.net.MastershipRole;
-import org.onlab.onos.net.Port;
-import org.onlab.onos.net.PortNumber;
-import org.onlab.onos.net.provider.ProviderId;
-import org.onlab.packet.IpPrefix;
-import org.onlab.util.KryoPool;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.net.URI;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.HashMap;
-
-/**
- * Serialization service using Kryo.
- */
-@Component(immediate = true)
-@Service
-public class KryoSerializationManager implements KryoSerializationService {
-
-    private final Logger log = LoggerFactory.getLogger(getClass());
-    private KryoPool serializerPool;
-
-
-    @Activate
-    public void activate() {
-        setupKryoPool();
-        log.info("Started");
-    }
-
-    @Deactivate
-    public void deactivate() {
-        log.info("Stopped");
-    }
-
-    /**
-     * Sets up the common serialzers pool.
-     */
-    protected void setupKryoPool() {
-        // FIXME Slice out types used in common to separate pool/namespace.
-        serializerPool = KryoPool.newBuilder()
-                .register(ArrayList.class,
-                          HashMap.class,
-
-                          ControllerNode.State.class,
-                          Device.Type.class,
-
-                          DefaultAnnotations.class,
-                          DefaultControllerNode.class,
-                          DefaultDevice.class,
-                          MastershipRole.class,
-                          Port.class,
-                          Element.class,
-
-                          Link.Type.class
-                )
-                .register(IpPrefix.class, new IpPrefixSerializer())
-                .register(URI.class, new URISerializer())
-                .register(NodeId.class, new NodeIdSerializer())
-                .register(ProviderId.class, new ProviderIdSerializer())
-                .register(DeviceId.class, new DeviceIdSerializer())
-                .register(PortNumber.class, new PortNumberSerializer())
-                .register(DefaultPort.class, new DefaultPortSerializer())
-                .register(LinkKey.class, new LinkKeySerializer())
-                .register(ConnectPoint.class, new ConnectPointSerializer())
-                .register(DefaultLink.class, new DefaultLinkSerializer())
-                .build()
-                .populate(1);
-    }
-
-    @Override
-    public byte[] serialize(final Object obj) {
-        return serializerPool.serialize(obj);
-    }
-
-    @Override
-    public <T> T deserialize(final byte[] bytes) {
-        if (bytes == null) {
-            return null;
-        }
-        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
deleted file mode 100644
index 385128c..0000000
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoSerializationService.java
+++ /dev/null
@@ -1,46 +0,0 @@
-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.
- */
-public interface KryoSerializationService {
-
-    /**
-     * Serializes the specified object into bytes using one of the
-     * pre-registered serializers.
-     *
-     * @param obj object to be serialized
-     * @return serialized bytes
-     */
-    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.
-     *
-     * @param bytes bytes to be deserialized
-     * @return deserialized object
-     */
-    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/KryoSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoSerializer.java
new file mode 100644
index 0000000..738086e
--- /dev/null
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoSerializer.java
@@ -0,0 +1,55 @@
+package org.onlab.onos.store.serializers;
+
+import org.onlab.util.KryoPool;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+
+/**
+ * StoreSerializer implementation using Kryo.
+ */
+public class KryoSerializer implements StoreSerializer {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    protected KryoPool serializerPool;
+
+
+    public KryoSerializer() {
+        setupKryoPool();
+    }
+
+    /**
+     * Sets up the common serialzers pool.
+     */
+    protected void setupKryoPool() {
+        serializerPool = KryoPool.newBuilder()
+                .register(KryoPoolUtil.API)
+                .build()
+                .populate(1);
+    }
+
+    @Override
+    public byte[] encode(final Object obj) {
+        return serializerPool.serialize(obj);
+    }
+
+    @Override
+    public <T> T decode(final byte[] bytes) {
+        if (bytes == null) {
+            return null;
+        }
+        return serializerPool.deserialize(bytes);
+    }
+
+    @Override
+    public void encode(Object obj, ByteBuffer buffer) {
+        serializerPool.serialize(obj, buffer);
+    }
+
+    @Override
+    public <T> T decode(ByteBuffer buffer) {
+        return serializerPool.deserialize(buffer);
+    }
+
+}
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/LinkKeySerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/LinkKeySerializer.java
index f635f3c..bafee4f 100644
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/LinkKeySerializer.java
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/LinkKeySerializer.java
@@ -2,6 +2,7 @@
 
 import org.onlab.onos.net.ConnectPoint;
 import org.onlab.onos.net.LinkKey;
+
 import com.esotericsoftware.kryo.Kryo;
 import com.esotericsoftware.kryo.Serializer;
 import com.esotericsoftware.kryo.io.Input;
@@ -13,7 +14,7 @@
 public class LinkKeySerializer extends Serializer<LinkKey> {
 
     /**
-     * Default constructor.
+     * Creates {@link LinkKey} serializer instance.
      */
     public LinkKeySerializer() {
         // non-null, immutable
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
index 3903491..dab5aa8 100644
--- 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
@@ -12,6 +12,14 @@
  */
 public class MastershipRoleSerializer extends Serializer<MastershipRole> {
 
+    /**
+     * Creates {@link MastershipRole} serializer instance.
+     */
+    public MastershipRoleSerializer() {
+        // non-null, immutable
+        super(false, true);
+    }
+
     @Override
     public MastershipRole read(Kryo kryo, Input input, Class<MastershipRole> type) {
         final String role = kryo.readObject(input, String.class);
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
index a5d6198..0ac61a8 100644
--- 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
@@ -2,7 +2,6 @@
 
 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;
@@ -13,9 +12,17 @@
  */
 public class MastershipTermSerializer extends Serializer<MastershipTerm> {
 
+    /**
+     * Creates {@link MastershipTerm} serializer instance.
+     */
+    public MastershipTermSerializer() {
+        // non-null, immutable
+        super(false, true);
+    }
+
     @Override
     public MastershipTerm read(Kryo kryo, Input input, Class<MastershipTerm> type) {
-        final NodeId node = new NodeId(kryo.readObject(input, String.class));
+        final NodeId node = new NodeId(input.readString());
         final int term = input.readInt();
         return MastershipTerm.of(node, term);
     }
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/NodeIdSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/NodeIdSerializer.java
index ef9d3f1..460b63d 100644
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/NodeIdSerializer.java
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/NodeIdSerializer.java
@@ -4,6 +4,7 @@
 import com.esotericsoftware.kryo.Serializer;
 import com.esotericsoftware.kryo.io.Input;
 import com.esotericsoftware.kryo.io.Output;
+
 import org.onlab.onos.cluster.NodeId;
 
 /**
@@ -11,14 +12,22 @@
  */
 public final class NodeIdSerializer extends Serializer<NodeId> {
 
+    /**
+     * Creates {@link NodeId} serializer instance.
+     */
+    public NodeIdSerializer() {
+        // non-null, immutable
+        super(false, true);
+    }
+
     @Override
     public void write(Kryo kryo, Output output, NodeId object) {
-        kryo.writeObject(output, object.toString());
+        output.writeString(object.toString());
     }
 
     @Override
     public NodeId read(Kryo kryo, Input input, Class<NodeId> type) {
-        final String id = kryo.readObject(input, String.class);
+        final String id = input.readString();
         return new NodeId(id);
     }
 }
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/PortNumberSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/PortNumberSerializer.java
index 02805bb..3792966 100644
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/PortNumberSerializer.java
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/PortNumberSerializer.java
@@ -14,7 +14,7 @@
         Serializer<PortNumber> {
 
     /**
-     * Default constructor.
+     * Creates {@link PortNumber} serializer instance.
      */
     public PortNumberSerializer() {
         // non-null, immutable
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/ProviderIdSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/ProviderIdSerializer.java
index 1a1c6f6..060ac7d 100644
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/ProviderIdSerializer.java
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/ProviderIdSerializer.java
@@ -13,7 +13,7 @@
 public class ProviderIdSerializer extends Serializer<ProviderId> {
 
     /**
-     * Default constructor.
+     * Creates {@link ProviderId} serializer instance.
      */
     public ProviderIdSerializer() {
         // non-null, immutable
@@ -24,13 +24,15 @@
     public void write(Kryo kryo, Output output, ProviderId object) {
         output.writeString(object.scheme());
         output.writeString(object.id());
+        output.writeBoolean(object.isAncillary());
     }
 
     @Override
     public ProviderId read(Kryo kryo, Input input, Class<ProviderId> type) {
         String scheme = input.readString();
         String id = input.readString();
-        return new ProviderId(scheme, id);
+        boolean isAncillary = input.readBoolean();
+        return new ProviderId(scheme, id, isAncillary);
     }
 
 }
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/StoreSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/StoreSerializer.java
new file mode 100644
index 0000000..6c43a1b
--- /dev/null
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/StoreSerializer.java
@@ -0,0 +1,42 @@
+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.
+ */
+public interface StoreSerializer {
+
+    /**
+     * Serializes the specified object into bytes.
+     *
+     * @param obj object to be serialized
+     * @return serialized bytes
+     */
+    public byte[] encode(final Object obj);
+
+    /**
+     * Serializes the specified object into bytes.
+     *
+     * @param obj object to be serialized
+     * @param buffer to write serialized bytes
+     */
+    public void encode(final Object obj, ByteBuffer buffer);
+
+    /**
+     * Deserializes the specified bytes into an object.
+     *
+     * @param bytes bytes to be deserialized
+     * @return deserialized object
+     */
+    public <T> T decode(final byte[] bytes);
+
+    /**
+     * Deserializes the specified bytes into an object.
+     *
+     * @param buffer bytes to be deserialized
+     * @return deserialized object
+     */
+    public <T> T decode(final ByteBuffer buffer);
+}
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/KryoSerializerTest.java
similarity index 61%
rename from core/store/serializers/src/test/java/org/onlab/onos/store/serializers/KryoSerializerTests.java
rename to core/store/serializers/src/test/java/org/onlab/onos/store/serializers/KryoSerializerTest.java
index c972d1a..d651d56 100644
--- 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/KryoSerializerTest.java
@@ -1,12 +1,10 @@
 package org.onlab.onos.store.serializers;
 
+import static org.junit.Assert.assertEquals;
 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;
@@ -14,7 +12,9 @@
 import org.junit.Test;
 import org.onlab.onos.cluster.MastershipTerm;
 import org.onlab.onos.cluster.NodeId;
+import org.onlab.onos.net.Annotations;
 import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DefaultAnnotations;
 import org.onlab.onos.net.DefaultDevice;
 import org.onlab.onos.net.DefaultLink;
 import org.onlab.onos.net.DefaultPort;
@@ -24,7 +24,9 @@
 import org.onlab.onos.net.LinkKey;
 import org.onlab.onos.net.MastershipRole;
 import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.SparseAnnotations;
 import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
 import org.onlab.util.KryoPool;
 
@@ -32,10 +34,10 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.testing.EqualsTester;
 
-import de.javakaffee.kryoserializers.URISerializer;
+public class KryoSerializerTest {
 
-public class KryoSerializerTests {
     private static final ProviderId PID = new ProviderId("of", "foo");
+    private static final ProviderId PIDA = new ProviderId("of", "foo", true);
     private static final DeviceId DID1 = deviceId("of:foo");
     private static final DeviceId DID2 = deviceId("of:bar");
     private static final PortNumber P1 = portNumber(1);
@@ -48,44 +50,23 @@
     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 final SparseAnnotations A1 = DefaultAnnotations.builder()
+            .set("A1", "a1")
+            .set("B1", "b1")
+            .build();
+    private static final SparseAnnotations A1_2 = DefaultAnnotations.builder()
+            .remove("A1")
+            .set("B3", "b3")
+            .build();
 
     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(KryoPoolUtil.API)
                 .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();
     }
 
@@ -112,10 +93,12 @@
 
 
     @Test
-    public final void test() {
+    public final void testSerialization() {
         testSerialized(new ConnectPoint(DID1, P1));
         testSerialized(new DefaultLink(PID, CP1, CP2, Link.Type.DIRECT));
         testSerialized(new DefaultPort(DEV1, P1, true));
+        testSerialized(new DefaultLink(PID, CP1, CP2, Link.Type.DIRECT, A1));
+        testSerialized(new DefaultPort(DEV1, P1, true, A1_2));
         testSerialized(DID1);
         testSerialized(ImmutableMap.of(DID1, DEV1, DID2, DEV1));
         testSerialized(ImmutableMap.of(DID1, DEV1));
@@ -124,10 +107,41 @@
         testSerialized(ImmutableSet.of(DID1));
         testSerialized(ImmutableSet.of());
         testSerialized(IpPrefix.valueOf("192.168.0.1/24"));
+        testSerialized(IpAddress.valueOf("192.168.0.1"));
         testSerialized(new LinkKey(CP1, CP2));
         testSerialized(new NodeId("SomeNodeIdentifier"));
         testSerialized(P1);
         testSerialized(PID);
+        testSerialized(PIDA);
+        testSerialized(new NodeId("bar"));
+        testSerialized(MastershipTerm.of(new NodeId("foo"), 2));
+        for (MastershipRole role : MastershipRole.values()) {
+            testSerialized(role);
+        }
+    }
+
+    @Test
+    public final void testAnnotations() {
+        // Annotations does not have equals defined, manually test equality
+        final byte[] a1Bytes = kryos.serialize(A1);
+        SparseAnnotations copiedA1 = kryos.deserialize(a1Bytes);
+        assertAnnotationsEquals(copiedA1, A1);
+
+        final byte[] a12Bytes = kryos.serialize(A1_2);
+        SparseAnnotations copiedA12 = kryos.deserialize(a12Bytes);
+        assertAnnotationsEquals(copiedA12, A1_2);
+    }
+
+    // code clone
+    public static void assertAnnotationsEquals(Annotations actual, SparseAnnotations... annotations) {
+        SparseAnnotations expected = DefaultAnnotations.builder().build();
+        for (SparseAnnotations a : annotations) {
+            expected = DefaultAnnotations.union(expected, a);
+        }
+        assertEquals(expected.keys(), actual.keys());
+        for (String key : expected.keys()) {
+            assertEquals(expected.value(key), actual.value(key));
+        }
     }
 
 }
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/NoOpClockProviderService.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/NoOpClockProviderService.java
new file mode 100644
index 0000000..ff4b31a
--- /dev/null
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/NoOpClockProviderService.java
@@ -0,0 +1,20 @@
+package org.onlab.onos.store.trivial.impl;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.onos.cluster.MastershipTerm;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.store.ClockProviderService;
+
+//FIXME: Code clone in onos-core-trivial, onos-core-hz-net
+/**
+ * Dummy implementation of {@link ClockProviderService}.
+ */
+@Component(immediate = true)
+@Service
+public class NoOpClockProviderService implements ClockProviderService {
+
+    @Override
+    public void setMastershipTerm(DeviceId deviceId, MastershipTerm term) {
+    }
+}
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/NoOpClockService.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/NoOpClockService.java
deleted file mode 100644
index b3f8320..0000000
--- a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/NoOpClockService.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.onlab.onos.store.trivial.impl;
-
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Service;
-import org.onlab.onos.cluster.MastershipTerm;
-import org.onlab.onos.net.DeviceId;
-import org.onlab.onos.store.ClockService;
-import org.onlab.onos.store.Timestamp;
-
-//FIXME: Code clone in onos-core-trivial, onos-core-hz-net
-/**
- * Dummy implementation of {@link ClockService}.
- */
-@Component(immediate = true)
-@Service
-public class NoOpClockService implements ClockService {
-
-    @Override
-    public Timestamp getTimestamp(DeviceId deviceId) {
-        return new Timestamp() {
-
-            @Override
-            public int compareTo(Timestamp o) {
-                throw new IllegalStateException("Never expected to be used.");
-            }
-        };
-    }
-
-    @Override
-    public void setMastershipTerm(DeviceId deviceId, MastershipTerm term) {
-    }
-}
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStore.java
index 0b0ae37..514a22e 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStore.java
@@ -2,6 +2,8 @@
 
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 
 import org.apache.commons.lang3.concurrent.ConcurrentException;
 import org.apache.commons.lang3.concurrent.ConcurrentInitializer;
@@ -9,7 +11,7 @@
 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.net.Annotations;
+import org.onlab.onos.net.AnnotationsUtil;
 import org.onlab.onos.net.DefaultAnnotations;
 import org.onlab.onos.net.DefaultDevice;
 import org.onlab.onos.net.DefaultPort;
@@ -28,10 +30,10 @@
 import org.onlab.onos.net.device.PortDescription;
 import org.onlab.onos.net.provider.ProviderId;
 import org.onlab.onos.store.AbstractStore;
+import org.onlab.util.NewConcurrentHashMap;
 import org.slf4j.Logger;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -47,12 +49,13 @@
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Predicates.notNull;
+import static com.google.common.base.Verify.verify;
 import static org.onlab.onos.net.device.DeviceEvent.Type.*;
 import static org.slf4j.LoggerFactory.getLogger;
 import static org.apache.commons.lang3.concurrent.ConcurrentUtils.createIfAbsentUnchecked;
+import static org.onlab.onos.net.DefaultAnnotations.union;
 import static org.onlab.onos.net.DefaultAnnotations.merge;
 
-// TODO: synchronization should be done in more fine-grained manner.
 /**
  * Manages inventory of infrastructure devices using trivial in-memory
  * structures implementation.
@@ -70,14 +73,14 @@
     // collection of Description given from various providers
     private final ConcurrentMap<DeviceId,
                             ConcurrentMap<ProviderId, DeviceDescriptions>>
-                                deviceDescs = new ConcurrentHashMap<>();
+                                deviceDescs = Maps.newConcurrentMap();
 
     // cache of Device and Ports generated by compositing descriptions from providers
-    private final ConcurrentMap<DeviceId, Device> devices = new ConcurrentHashMap<>();
-    private final ConcurrentMap<DeviceId, ConcurrentMap<PortNumber, Port>> devicePorts = new ConcurrentHashMap<>();
+    private final ConcurrentMap<DeviceId, Device> devices = Maps.newConcurrentMap();
+    private final ConcurrentMap<DeviceId, ConcurrentMap<PortNumber, Port>> devicePorts = Maps.newConcurrentMap();
 
     // available(=UP) devices
-    private final Set<DeviceId> availableDevices = new HashSet<>();
+    private final Set<DeviceId> availableDevices = Sets.newConcurrentHashSet();
 
 
     @Activate
@@ -87,6 +90,10 @@
 
     @Deactivate
     public void deactivate() {
+        deviceDescs.clear();
+        devices.clear();
+        devicePorts.clear();
+        availableDevices.clear();
         log.info("Stopped");
     }
 
@@ -106,117 +113,142 @@
     }
 
     @Override
-    public synchronized DeviceEvent createOrUpdateDevice(ProviderId providerId, DeviceId deviceId,
+    public DeviceEvent createOrUpdateDevice(ProviderId providerId,
+                                     DeviceId deviceId,
                                      DeviceDescription deviceDescription) {
+
         ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs
-            = createIfAbsentUnchecked(deviceDescs, deviceId,
-                    new InitConcurrentHashMap<ProviderId, DeviceDescriptions>());
+            = getDeviceDescriptions(deviceId);
 
-        Device oldDevice = devices.get(deviceId);
+        synchronized (providerDescs) {
+            // locking per device
 
-        DeviceDescriptions descs
-            = createIfAbsentUnchecked(providerDescs, providerId,
-                    new InitDeviceDescs(deviceDescription));
+            DeviceDescriptions descs
+                = createIfAbsentUnchecked(providerDescs, providerId,
+                        new InitDeviceDescs(deviceDescription));
 
-        // update description
-        descs.putDeviceDesc(deviceDescription);
-        Device newDevice = composeDevice(deviceId, providerDescs);
+            Device oldDevice = devices.get(deviceId);
+            // update description
+            descs.putDeviceDesc(deviceDescription);
+            Device newDevice = composeDevice(deviceId, providerDescs);
 
-        if (oldDevice == null) {
-            // ADD
-            return createDevice(providerId, newDevice);
-        } else {
-            // UPDATE or ignore (no change or stale)
-            return updateDevice(providerId, oldDevice, newDevice);
+            if (oldDevice == null) {
+                // ADD
+                return createDevice(providerId, newDevice);
+            } else {
+                // UPDATE or ignore (no change or stale)
+                return updateDevice(providerId, oldDevice, newDevice);
+            }
         }
     }
 
     // Creates the device and returns the appropriate event if necessary.
+    // Guarded by deviceDescs value (=Device lock)
     private DeviceEvent createDevice(ProviderId providerId, Device newDevice) {
 
         // update composed device cache
-        synchronized (this) {
-            devices.putIfAbsent(newDevice.id(), newDevice);
-            if (!providerId.isAncillary()) {
-                availableDevices.add(newDevice.id());
-            }
+        Device oldDevice = devices.putIfAbsent(newDevice.id(), newDevice);
+        verify(oldDevice == null,
+                "Unexpected Device in cache. PID:%s [old=%s, new=%s]",
+                providerId, oldDevice, newDevice);
+
+        if (!providerId.isAncillary()) {
+            availableDevices.add(newDevice.id());
         }
 
         return new DeviceEvent(DeviceEvent.Type.DEVICE_ADDED, newDevice, null);
     }
 
     // Updates the device and returns the appropriate event if necessary.
+    // Guarded by deviceDescs value (=Device lock)
     private DeviceEvent updateDevice(ProviderId providerId, Device oldDevice, Device newDevice) {
 
         // We allow only certain attributes to trigger update
         if (!Objects.equals(oldDevice.hwVersion(), newDevice.hwVersion()) ||
             !Objects.equals(oldDevice.swVersion(), newDevice.swVersion()) ||
-            !isAnnotationsEqual(oldDevice.annotations(), newDevice.annotations())) {
+            !AnnotationsUtil.isEqual(oldDevice.annotations(), newDevice.annotations())) {
 
-            synchronized (this) {
-                devices.replace(newDevice.id(), oldDevice, newDevice);
-                if (!providerId.isAncillary()) {
-                    availableDevices.add(newDevice.id());
-                }
+            boolean replaced = devices.replace(newDevice.id(), oldDevice, newDevice);
+            if (!replaced) {
+                verify(replaced,
+                        "Replacing devices cache failed. PID:%s [expected:%s, found:%s, new=%s]",
+                        providerId, oldDevice, devices.get(newDevice.id())
+                        , newDevice);
+            }
+            if (!providerId.isAncillary()) {
+                availableDevices.add(newDevice.id());
             }
             return new DeviceEvent(DeviceEvent.Type.DEVICE_UPDATED, newDevice, null);
         }
 
         // Otherwise merely attempt to change availability if primary provider
         if (!providerId.isAncillary()) {
-            synchronized (this) {
             boolean added = availableDevices.add(newDevice.id());
             return !added ? null :
                     new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, newDevice, null);
-            }
         }
         return null;
     }
 
     @Override
     public DeviceEvent markOffline(DeviceId deviceId) {
-        synchronized (this) {
+        ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs
+            = getDeviceDescriptions(deviceId);
+
+        // locking device
+        synchronized (providerDescs) {
             Device device = devices.get(deviceId);
-            boolean removed = (device != null) && availableDevices.remove(deviceId);
-            return !removed ? null :
-                    new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device, null);
+            if (device == null) {
+                return null;
+            }
+            boolean removed = availableDevices.remove(deviceId);
+            if (removed) {
+                // TODO: broadcast ... DOWN only?
+                return new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device, null);
+            }
+            return null;
         }
     }
 
     @Override
-    public synchronized List<DeviceEvent> updatePorts(ProviderId providerId, DeviceId deviceId,
-                                  List<PortDescription> portDescriptions) {
+    public List<DeviceEvent> updatePorts(ProviderId providerId,
+                                      DeviceId deviceId,
+                                      List<PortDescription> portDescriptions) {
 
-        // TODO: implement multi-provider
         Device device = devices.get(deviceId);
         checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
 
         ConcurrentMap<ProviderId, DeviceDescriptions> descsMap = deviceDescs.get(deviceId);
         checkArgument(descsMap != null, DEVICE_NOT_FOUND, deviceId);
 
-        DeviceDescriptions descs = descsMap.get(providerId);
-        checkArgument(descs != null,
-                "Device description for Device ID %s from Provider %s was not found",
-                deviceId, providerId);
-
-
         List<DeviceEvent> events = new ArrayList<>();
-        synchronized (this) {
-            ConcurrentMap<PortNumber, Port> ports = getPortMap(deviceId);
+        synchronized (descsMap) {
+            DeviceDescriptions descs = descsMap.get(providerId);
+            // every provider must provide DeviceDescription.
+            checkArgument(descs != null,
+                    "Device description for Device ID %s from Provider %s was not found",
+                    deviceId, providerId);
+
+            Map<PortNumber, Port> ports = getPortMap(deviceId);
 
             // Add new ports
             Set<PortNumber> processed = new HashSet<>();
             for (PortDescription portDescription : portDescriptions) {
-                PortNumber number = portDescription.portNumber();
-                Port oldPort = ports.get(number);
+                final PortNumber number = portDescription.portNumber();
+                processed.add(portDescription.portNumber());
+
+                final Port oldPort = ports.get(number);
+                final Port newPort;
+
+// event suppression hook?
+
                 // update description
                 descs.putPortDesc(portDescription);
-                Port newPort = composePort(device, number, descsMap);
+                newPort = composePort(device, number, descsMap);
 
                 events.add(oldPort == null ?
-                                   createPort(device, newPort, ports) :
-                                   updatePort(device, oldPort, newPort, ports));
-                processed.add(portDescription.portNumber());
+                        createPort(device, newPort, ports) :
+                        updatePort(device, oldPort, newPort, ports));
             }
 
             events.addAll(pruneOldPorts(device, ports, processed));
@@ -226,19 +258,21 @@
 
     // Creates a new port based on the port description adds it to the map and
     // Returns corresponding event.
+    // Guarded by deviceDescs value (=Device lock)
     private DeviceEvent createPort(Device device, Port newPort,
-                                   ConcurrentMap<PortNumber, Port> ports) {
+                                   Map<PortNumber, Port> ports) {
         ports.put(newPort.number(), newPort);
         return new DeviceEvent(PORT_ADDED, device, newPort);
     }
 
     // Checks if the specified port requires update and if so, it replaces the
     // existing entry in the map and returns corresponding event.
+    // Guarded by deviceDescs value (=Device lock)
     private DeviceEvent updatePort(Device device, Port oldPort,
                                    Port newPort,
-                                   ConcurrentMap<PortNumber, Port> ports) {
+                                   Map<PortNumber, Port> ports) {
         if (oldPort.isEnabled() != newPort.isEnabled() ||
-            !isAnnotationsEqual(oldPort.annotations(), newPort.annotations())) {
+            !AnnotationsUtil.isEqual(oldPort.annotations(), newPort.annotations())) {
 
             ports.put(oldPort.number(), newPort);
             return new DeviceEvent(PORT_UPDATED, device, newPort);
@@ -248,6 +282,7 @@
 
     // Prunes the specified list of ports based on which ports are in the
     // processed list and returns list of corresponding events.
+    // Guarded by deviceDescs value (=Device lock)
     private List<DeviceEvent> pruneOldPorts(Device device,
                                             Map<PortNumber, Port> ports,
                                             Set<PortNumber> processed) {
@@ -268,11 +303,17 @@
     // exist, it creates and registers a new one.
     private ConcurrentMap<PortNumber, Port> getPortMap(DeviceId deviceId) {
         return createIfAbsentUnchecked(devicePorts, deviceId,
-                new InitConcurrentHashMap<PortNumber, Port>());
+                NewConcurrentHashMap.<PortNumber, Port>ifNeeded());
+    }
+
+    private ConcurrentMap<ProviderId, DeviceDescriptions> getDeviceDescriptions(
+            DeviceId deviceId) {
+        return createIfAbsentUnchecked(deviceDescs, deviceId,
+                NewConcurrentHashMap.<ProviderId, DeviceDescriptions>ifNeeded());
     }
 
     @Override
-    public synchronized DeviceEvent updatePortStatus(ProviderId providerId, DeviceId deviceId,
+    public DeviceEvent updatePortStatus(ProviderId providerId, DeviceId deviceId,
                                  PortDescription portDescription) {
         Device device = devices.get(deviceId);
         checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
@@ -280,19 +321,22 @@
         ConcurrentMap<ProviderId, DeviceDescriptions> descsMap = deviceDescs.get(deviceId);
         checkArgument(descsMap != null, DEVICE_NOT_FOUND, deviceId);
 
-        DeviceDescriptions descs = descsMap.get(providerId);
-        // assuming all providers must to give DeviceDescription
-        checkArgument(descs != null,
-                "Device description for Device ID %s from Provider %s was not found",
-                deviceId, providerId);
+        synchronized (descsMap) {
+            DeviceDescriptions descs = descsMap.get(providerId);
+            // assuming all providers must to give DeviceDescription
+            checkArgument(descs != null,
+                    "Device description for Device ID %s from Provider %s was not found",
+                    deviceId, providerId);
 
-        synchronized (this) {
             ConcurrentMap<PortNumber, Port> ports = getPortMap(deviceId);
             final PortNumber number = portDescription.portNumber();
-            Port oldPort = ports.get(number);
+            final Port oldPort = ports.get(number);
+            final Port newPort;
+
             // update description
             descs.putPortDesc(portDescription);
-            Port newPort = composePort(device, number, descsMap);
+            newPort = composePort(device, number, descsMap);
+
             if (oldPort == null) {
                 return createPort(device, newPort, ports);
             } else {
@@ -323,31 +367,19 @@
 
     @Override
     public DeviceEvent removeDevice(DeviceId deviceId) {
-        synchronized (this) {
+        ConcurrentMap<ProviderId, DeviceDescriptions> descs = getDeviceDescriptions(deviceId);
+        synchronized (descs) {
             Device device = devices.remove(deviceId);
-            return device == null ? null :
-                    new DeviceEvent(DEVICE_REMOVED, device, null);
-        }
-    }
-
-    private static boolean isAnnotationsEqual(Annotations lhs, Annotations rhs) {
-        if (lhs == rhs) {
-            return true;
-        }
-        if (lhs == null || rhs == null) {
-            return false;
-        }
-
-        if (!lhs.keys().equals(rhs.keys())) {
-            return false;
-        }
-
-        for (String key : lhs.keys()) {
-            if (!lhs.value(key).equals(rhs.value(key))) {
-                return false;
+            // should DEVICE_REMOVED carry removed ports?
+            Map<PortNumber, Port> ports = devicePorts.get(deviceId);
+            if (ports != null) {
+                ports.clear();
             }
+            availableDevices.remove(deviceId);
+            descs.clear();
+            return device == null ? null :
+                new DeviceEvent(DEVICE_REMOVED, device, null);
         }
-        return true;
     }
 
     /**
@@ -366,14 +398,14 @@
 
         DeviceDescriptions desc = providerDescs.get(primary);
 
-        // base
-        Type type = desc.getDeviceDesc().type();
-        String manufacturer = desc.getDeviceDesc().manufacturer();
-        String hwVersion = desc.getDeviceDesc().hwVersion();
-        String swVersion = desc.getDeviceDesc().swVersion();
-        String serialNumber = desc.getDeviceDesc().serialNumber();
+        final DeviceDescription base = desc.getDeviceDesc();
+        Type type = base.type();
+        String manufacturer = base.manufacturer();
+        String hwVersion = base.hwVersion();
+        String swVersion = base.swVersion();
+        String serialNumber = base.serialNumber();
         DefaultAnnotations annotations = DefaultAnnotations.builder().build();
-        annotations = merge(annotations, desc.getDeviceDesc().annotations());
+        annotations = merge(annotations, base.annotations());
 
         for (Entry<ProviderId, DeviceDescriptions> e : providerDescs.entrySet()) {
             if (e.getKey().equals(primary)) {
@@ -392,7 +424,14 @@
                             hwVersion, swVersion, serialNumber, annotations);
     }
 
-    // probably want composePort"s" also
+    /**
+     * Returns a Port, merging description given from multiple Providers.
+     *
+     * @param device device the port is on
+     * @param number port number
+     * @param providerDescs Collection of Descriptions from multiple providers
+     * @return Port instance
+     */
     private Port composePort(Device device, PortNumber number,
                 ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs) {
 
@@ -445,18 +484,11 @@
         return fallBackPrimary;
     }
 
-    // TODO: can be made generic
-    private static final class InitConcurrentHashMap<K, V> implements
-            ConcurrentInitializer<ConcurrentMap<K, V>> {
-        @Override
-        public ConcurrentMap<K, V> get() throws ConcurrentException {
-            return new ConcurrentHashMap<>();
-        }
-    }
-
     public static final class InitDeviceDescs
         implements ConcurrentInitializer<DeviceDescriptions> {
+
         private final DeviceDescription deviceDesc;
+
         public InitDeviceDescs(DeviceDescription deviceDesc) {
             this.deviceDesc = checkNotNull(deviceDesc);
         }
@@ -471,8 +503,6 @@
      * Collection of Description of a Device and it's Ports given from a Provider.
      */
     private static class DeviceDescriptions {
-        //        private final DeviceId id;
-        //        private final ProviderId pid;
 
         private final AtomicReference<DeviceDescription> deviceDesc;
         private final ConcurrentMap<PortNumber, PortDescription> portDescs;
@@ -490,10 +520,6 @@
             return portDescs.get(number);
         }
 
-        public Collection<PortDescription> getPortDescs() {
-            return Collections.unmodifiableCollection(portDescs.values());
-        }
-
         /**
          * Puts DeviceDescription, merging annotations as necessary.
          *
@@ -504,7 +530,7 @@
             DeviceDescription oldOne = deviceDesc.get();
             DeviceDescription newOne = newDesc;
             if (oldOne != null) {
-                SparseAnnotations merged = merge(oldOne.annotations(),
+                SparseAnnotations merged = union(oldOne.annotations(),
                                                  newDesc.annotations());
                 newOne = new DefaultDeviceDescription(newOne, merged);
             }
@@ -521,7 +547,7 @@
             PortDescription oldOne = portDescs.get(newDesc.portNumber());
             PortDescription newOne = newDesc;
             if (oldOne != null) {
-                SparseAnnotations merged = merge(oldOne.annotations(),
+                SparseAnnotations merged = union(oldOne.annotations(),
                                                  newDesc.annotations());
                 newOne = new DefaultPortDescription(newOne, merged);
             }
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleFlowRuleStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleFlowRuleStore.java
index d12d00e..7ff797c 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleFlowRuleStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleFlowRuleStore.java
@@ -12,9 +12,10 @@
 import org.apache.felix.scr.annotations.Service;
 import org.onlab.onos.ApplicationId;
 import org.onlab.onos.net.DeviceId;
-import org.onlab.onos.net.flow.DefaultFlowRule;
+import org.onlab.onos.net.flow.DefaultFlowEntry;
+import org.onlab.onos.net.flow.FlowEntry;
+import org.onlab.onos.net.flow.FlowEntry.FlowEntryState;
 import org.onlab.onos.net.flow.FlowRule;
-import org.onlab.onos.net.flow.FlowRule.FlowRuleState;
 import org.onlab.onos.net.flow.FlowRuleEvent;
 import org.onlab.onos.net.flow.FlowRuleEvent.Type;
 import org.onlab.onos.net.flow.FlowRuleStore;
@@ -38,8 +39,8 @@
     private final Logger log = getLogger(getClass());
 
     // store entries as a pile of rules, no info about device tables
-    private final Multimap<DeviceId, FlowRule> flowEntries =
-            ArrayListMultimap.<DeviceId, FlowRule>create();
+    private final Multimap<DeviceId, FlowEntry> flowEntries =
+            ArrayListMultimap.<DeviceId, FlowEntry>create();
 
     private final Multimap<ApplicationId, FlowRule> flowEntriesById =
             ArrayListMultimap.<ApplicationId, FlowRule>create();
@@ -56,8 +57,13 @@
 
 
     @Override
-    public synchronized FlowRule getFlowRule(FlowRule rule) {
-        for (FlowRule f : flowEntries.get(rule.deviceId())) {
+    public int getFlowRuleCount() {
+        return flowEntries.size();
+    }
+
+    @Override
+    public synchronized FlowEntry getFlowEntry(FlowRule rule) {
+        for (FlowEntry f : flowEntries.get(rule.deviceId())) {
             if (f.equals(rule)) {
                 return f;
             }
@@ -66,8 +72,8 @@
     }
 
     @Override
-    public synchronized Iterable<FlowRule> getFlowEntries(DeviceId deviceId) {
-        Collection<FlowRule> rules = flowEntries.get(deviceId);
+    public synchronized Iterable<FlowEntry> getFlowEntries(DeviceId deviceId) {
+        Collection<FlowEntry> rules = flowEntries.get(deviceId);
         if (rules == null) {
             return Collections.emptyList();
         }
@@ -75,7 +81,7 @@
     }
 
     @Override
-    public synchronized Iterable<FlowRule> getFlowEntriesByAppId(ApplicationId appId) {
+    public synchronized Iterable<FlowRule> getFlowRulesByAppId(ApplicationId appId) {
         Collection<FlowRule> rules = flowEntriesById.get(appId);
         if (rules == null) {
             return Collections.emptyList();
@@ -85,7 +91,7 @@
 
     @Override
     public synchronized void storeFlowRule(FlowRule rule) {
-        FlowRule f = new DefaultFlowRule(rule, FlowRuleState.PENDING_ADD);
+        FlowEntry f = new DefaultFlowEntry(rule);
         DeviceId did = f.deviceId();
         if (!flowEntries.containsEntry(did, f)) {
             flowEntries.put(did, f);
@@ -95,51 +101,42 @@
 
     @Override
     public synchronized void deleteFlowRule(FlowRule rule) {
-        FlowRule f = new DefaultFlowRule(rule, FlowRuleState.PENDING_REMOVE);
-        DeviceId did = f.deviceId();
-
-        /*
-         *  find the rule and mark it for deletion.
-         *  Ultimately a flow removed will come remove it.
-         */
-
-        if (flowEntries.containsEntry(did, f)) {
-            flowEntries.remove(did, f);
-            flowEntries.put(did, f);
-            flowEntriesById.remove(rule.appId(), rule);
+        FlowEntry entry = getFlowEntry(rule);
+        if (entry == null) {
+            //log.warn("Cannot find rule {}", rule);
+            return;
         }
+        entry.setState(FlowEntryState.PENDING_REMOVE);
     }
 
     @Override
-    public synchronized FlowRuleEvent addOrUpdateFlowRule(FlowRule rule) {
+    public synchronized FlowRuleEvent addOrUpdateFlowRule(FlowEntry rule) {
         DeviceId did = rule.deviceId();
 
         // check if this new rule is an update to an existing entry
-        FlowRule stored = getFlowRule(rule);
+        FlowEntry stored = getFlowEntry(rule);
         if (stored != null) {
-            // Multimaps support duplicates so we have to remove our rule
-            // and replace it with the current version.
-            flowEntries.remove(did, rule);
-            flowEntries.put(did, rule);
-
-            if (stored.state() == FlowRuleState.PENDING_ADD) {
+            stored.setBytes(rule.bytes());
+            stored.setLife(rule.life());
+            stored.setPackets(rule.packets());
+            if (stored.state() == FlowEntryState.PENDING_ADD) {
+                stored.setState(FlowEntryState.ADDED);
                 return new FlowRuleEvent(Type.RULE_ADDED, rule);
             }
             return new FlowRuleEvent(Type.RULE_UPDATED, rule);
         }
 
-        flowEntries.put(did, rule);
+        //flowEntries.put(did, rule);
         return null;
     }
 
     @Override
-    public synchronized FlowRuleEvent removeFlowRule(FlowRule rule) {
-        //synchronized (this) {
+    public synchronized FlowRuleEvent removeFlowRule(FlowEntry rule) {
+        // This is where one could mark a rule as removed and still keep it in the store.
         if (flowEntries.remove(rule.deviceId(), rule)) {
             return new FlowRuleEvent(RULE_REMOVED, rule);
         } else {
             return null;
         }
-        //}
     }
 }
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleIntentStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleIntentStore.java
new file mode 100644
index 0000000..732d753
--- /dev/null
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleIntentStore.java
@@ -0,0 +1,108 @@
+package org.onlab.onos.store.trivial.impl;
+
+import com.google.common.collect.ImmutableSet;
+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.net.intent.InstallableIntent;
+import org.onlab.onos.net.intent.Intent;
+import org.onlab.onos.net.intent.IntentEvent;
+import org.onlab.onos.net.intent.IntentId;
+import org.onlab.onos.net.intent.IntentState;
+import org.onlab.onos.net.intent.IntentStore;
+import org.onlab.onos.net.intent.IntentStoreDelegate;
+import org.onlab.onos.store.AbstractStore;
+import org.slf4j.Logger;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.onlab.onos.net.intent.IntentState.*;
+import static org.slf4j.LoggerFactory.getLogger;
+
+@Component(immediate = true)
+@Service
+public class SimpleIntentStore
+        extends AbstractStore<IntentEvent, IntentStoreDelegate>
+        implements IntentStore {
+
+    private final Logger log = getLogger(getClass());
+    private final Map<IntentId, Intent> intents = new HashMap<>();
+    private final Map<IntentId, IntentState> states = new HashMap<>();
+    private final Map<IntentId, List<InstallableIntent>> installable = new HashMap<>();
+
+    @Activate
+    public void activate() {
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        log.info("Stopped");
+    }
+
+    @Override
+    public IntentEvent createIntent(Intent intent) {
+        intents.put(intent.id(), intent);
+        return this.setState(intent, IntentState.SUBMITTED);
+    }
+
+    @Override
+    public IntentEvent removeIntent(IntentId intentId) {
+        Intent intent = intents.remove(intentId);
+        installable.remove(intentId);
+        IntentEvent event = this.setState(intent, WITHDRAWN);
+        states.remove(intentId);
+        return event;
+    }
+
+    @Override
+    public long getIntentCount() {
+        return intents.size();
+    }
+
+    @Override
+    public Iterable<Intent> getIntents() {
+        return ImmutableSet.copyOf(intents.values());
+    }
+
+    @Override
+    public Intent getIntent(IntentId intentId) {
+        return intents.get(intentId);
+    }
+
+    @Override
+    public IntentState getIntentState(IntentId id) {
+        return states.get(id);
+    }
+
+    @Override
+    public IntentEvent setState(Intent intent, IntentState state) {
+        IntentId id = intent.id();
+        states.put(id, state);
+        IntentEvent.Type type = (state == SUBMITTED ? IntentEvent.Type.SUBMITTED :
+                (state == INSTALLED ? IntentEvent.Type.INSTALLED :
+                        (state == FAILED ? IntentEvent.Type.FAILED :
+                                state == WITHDRAWN ? IntentEvent.Type.WITHDRAWN :
+                                        null)));
+        return type == null ? null : new IntentEvent(type, intent);
+    }
+
+    @Override
+    public void addInstallableIntents(IntentId intentId, List<InstallableIntent> result) {
+        installable.put(intentId, result);
+    }
+
+    @Override
+    public List<InstallableIntent> getInstallableIntents(IntentId intentId) {
+        return installable.get(intentId);
+    }
+
+    @Override
+    public void removeInstalledIntents(IntentId intentId) {
+        installable.remove(intentId);
+    }
+
+}
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleLinkStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleLinkStore.java
index a0e569d..daf28df 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleLinkStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleLinkStore.java
@@ -1,36 +1,51 @@
 package org.onlab.onos.store.trivial.impl;
 
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
 import com.google.common.collect.HashMultimap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Multimap;
+import com.google.common.collect.SetMultimap;
 
+import org.apache.commons.lang3.concurrent.ConcurrentUtils;
 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.net.AnnotationsUtil;
 import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DefaultAnnotations;
 import org.onlab.onos.net.DefaultLink;
 import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.net.Link;
+import org.onlab.onos.net.SparseAnnotations;
+import org.onlab.onos.net.Link.Type;
 import org.onlab.onos.net.LinkKey;
+import org.onlab.onos.net.Provided;
+import org.onlab.onos.net.link.DefaultLinkDescription;
 import org.onlab.onos.net.link.LinkDescription;
 import org.onlab.onos.net.link.LinkEvent;
 import org.onlab.onos.net.link.LinkStore;
 import org.onlab.onos.net.link.LinkStoreDelegate;
 import org.onlab.onos.net.provider.ProviderId;
 import org.onlab.onos.store.AbstractStore;
+import org.onlab.util.NewConcurrentHashMap;
 import org.slf4j.Logger;
 
 import java.util.Collections;
 import java.util.HashSet;
-import java.util.Map;
 import java.util.Set;
+import java.util.Map.Entry;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 
+import static org.onlab.onos.net.DefaultAnnotations.union;
+import static org.onlab.onos.net.DefaultAnnotations.merge;
 import static org.onlab.onos.net.Link.Type.DIRECT;
 import static org.onlab.onos.net.Link.Type.INDIRECT;
 import static org.onlab.onos.net.link.LinkEvent.Type.*;
 import static org.slf4j.LoggerFactory.getLogger;
+import static com.google.common.collect.Multimaps.synchronizedSetMultimap;
+import static com.google.common.base.Predicates.notNull;
 
 /**
  * Manages inventory of infrastructure links using trivial in-memory structures
@@ -45,11 +60,17 @@
     private final Logger log = getLogger(getClass());
 
     // Link inventory
-    private final Map<LinkKey, DefaultLink> links = new ConcurrentHashMap<>();
+    private final ConcurrentMap<LinkKey,
+            ConcurrentMap<ProviderId, LinkDescription>>
+                    linkDescs = new ConcurrentHashMap<>();
+
+    // Link instance cache
+    private final ConcurrentMap<LinkKey, Link> links = new ConcurrentHashMap<>();
 
     // Egress and ingress link sets
-    private final Multimap<DeviceId, Link> srcLinks = HashMultimap.create();
-    private final Multimap<DeviceId, Link> dstLinks = HashMultimap.create();
+    private final SetMultimap<DeviceId, LinkKey> srcLinks = createSynchronizedHashMultiMap();
+    private final SetMultimap<DeviceId, LinkKey> dstLinks = createSynchronizedHashMultiMap();
+
 
     @Activate
     public void activate() {
@@ -58,6 +79,10 @@
 
     @Deactivate
     public void deactivate() {
+        linkDescs.clear();
+        links.clear();
+        srcLinks.clear();
+        dstLinks.clear();
         log.info("Stopped");
     }
 
@@ -68,17 +93,29 @@
 
     @Override
     public Iterable<Link> getLinks() {
-        return Collections.unmodifiableSet(new HashSet<Link>(links.values()));
+        return Collections.unmodifiableCollection(links.values());
     }
 
     @Override
     public Set<Link> getDeviceEgressLinks(DeviceId deviceId) {
-        return ImmutableSet.copyOf(srcLinks.get(deviceId));
+        // lock for iteration
+        synchronized (srcLinks) {
+            return FluentIterable.from(srcLinks.get(deviceId))
+            .transform(lookupLink())
+            .filter(notNull())
+            .toSet();
+        }
     }
 
     @Override
     public Set<Link> getDeviceIngressLinks(DeviceId deviceId) {
-        return ImmutableSet.copyOf(dstLinks.get(deviceId));
+        // lock for iteration
+        synchronized (dstLinks) {
+            return FluentIterable.from(dstLinks.get(deviceId))
+            .transform(lookupLink())
+            .filter(notNull())
+            .toSet();
+        }
     }
 
     @Override
@@ -89,9 +126,9 @@
     @Override
     public Set<Link> getEgressLinks(ConnectPoint src) {
         Set<Link> egress = new HashSet<>();
-        for (Link link : srcLinks.get(src.deviceId())) {
-            if (link.src().equals(src)) {
-                egress.add(link);
+        for (LinkKey linkKey : srcLinks.get(src.deviceId())) {
+            if (linkKey.src().equals(src)) {
+                egress.add(links.get(linkKey));
             }
         }
         return egress;
@@ -100,9 +137,9 @@
     @Override
     public Set<Link> getIngressLinks(ConnectPoint dst) {
         Set<Link> ingress = new HashSet<>();
-        for (Link link : dstLinks.get(dst.deviceId())) {
-            if (link.dst().equals(dst)) {
-                ingress.add(link);
+        for (LinkKey linkKey : dstLinks.get(dst.deviceId())) {
+            if (linkKey.dst().equals(dst)) {
+                ingress.add(links.get(linkKey));
             }
         }
         return ingress;
@@ -112,56 +149,172 @@
     public LinkEvent createOrUpdateLink(ProviderId providerId,
                                         LinkDescription linkDescription) {
         LinkKey key = new LinkKey(linkDescription.src(), linkDescription.dst());
-        DefaultLink link = links.get(key);
-        if (link == null) {
-            return createLink(providerId, key, linkDescription);
+
+        ConcurrentMap<ProviderId, LinkDescription> descs = getLinkDescriptions(key);
+        synchronized (descs) {
+            final Link oldLink = links.get(key);
+            // update description
+            createOrUpdateLinkDescription(descs, providerId, linkDescription);
+            final Link newLink = composeLink(descs);
+            if (oldLink == null) {
+                return createLink(key, newLink);
+            }
+            return updateLink(key, oldLink, newLink);
         }
-        return updateLink(providerId, link, key, linkDescription);
+    }
+
+    // Guarded by linkDescs value (=locking each Link)
+    private LinkDescription createOrUpdateLinkDescription(
+                             ConcurrentMap<ProviderId, LinkDescription> descs,
+                             ProviderId providerId,
+                             LinkDescription linkDescription) {
+
+        // merge existing attributes and merge
+        LinkDescription oldDesc = descs.get(providerId);
+        LinkDescription newDesc = linkDescription;
+        if (oldDesc != null) {
+            SparseAnnotations merged = union(oldDesc.annotations(),
+                    linkDescription.annotations());
+            newDesc = new DefaultLinkDescription(
+                        linkDescription.src(),
+                        linkDescription.dst(),
+                        linkDescription.type(), merged);
+        }
+        return descs.put(providerId, newDesc);
     }
 
     // Creates and stores the link and returns the appropriate event.
-    private LinkEvent createLink(ProviderId providerId, LinkKey key,
-                                 LinkDescription linkDescription) {
-        DefaultLink link = new DefaultLink(providerId, key.src(), key.dst(),
-                                           linkDescription.type());
-        synchronized (this) {
-            links.put(key, link);
-            srcLinks.put(link.src().deviceId(), link);
-            dstLinks.put(link.dst().deviceId(), link);
+    // Guarded by linkDescs value (=locking each Link)
+    private LinkEvent createLink(LinkKey key, Link newLink) {
+
+        if (newLink.providerId().isAncillary()) {
+            // TODO: revisit ancillary only Link handling
+
+            // currently treating ancillary only as down (not visible outside)
+            return null;
         }
-        return new LinkEvent(LINK_ADDED, link);
+
+        links.put(key, newLink);
+        srcLinks.put(newLink.src().deviceId(), key);
+        dstLinks.put(newLink.dst().deviceId(), key);
+        return new LinkEvent(LINK_ADDED, newLink);
     }
 
     // Updates, if necessary the specified link and returns the appropriate event.
-    private LinkEvent updateLink(ProviderId providerId, DefaultLink link,
-                                 LinkKey key, LinkDescription linkDescription) {
-        if (link.type() == INDIRECT && linkDescription.type() == DIRECT) {
-            synchronized (this) {
-                srcLinks.remove(link.src().deviceId(), link);
-                dstLinks.remove(link.dst().deviceId(), link);
+    // Guarded by linkDescs value (=locking each Link)
+    private LinkEvent updateLink(LinkKey key, Link oldLink, Link newLink) {
 
-                DefaultLink updated =
-                        new DefaultLink(providerId, link.src(), link.dst(),
-                                        linkDescription.type());
-                links.put(key, updated);
-                srcLinks.put(link.src().deviceId(), updated);
-                dstLinks.put(link.dst().deviceId(), updated);
-                return new LinkEvent(LINK_UPDATED, updated);
-            }
+        if (newLink.providerId().isAncillary()) {
+            // TODO: revisit ancillary only Link handling
+
+            // currently treating ancillary only as down (not visible outside)
+            return null;
+        }
+
+        if ((oldLink.type() == INDIRECT && newLink.type() == DIRECT) ||
+            !AnnotationsUtil.isEqual(oldLink.annotations(), newLink.annotations())) {
+
+            links.put(key, newLink);
+            // strictly speaking following can be ommitted
+            srcLinks.put(oldLink.src().deviceId(), key);
+            dstLinks.put(oldLink.dst().deviceId(), key);
+            return new LinkEvent(LINK_UPDATED, newLink);
         }
         return null;
     }
 
     @Override
     public LinkEvent removeLink(ConnectPoint src, ConnectPoint dst) {
-        synchronized (this) {
-            Link link = links.remove(new LinkKey(src, dst));
+        final LinkKey key = new LinkKey(src, dst);
+        ConcurrentMap<ProviderId, LinkDescription> descs = getLinkDescriptions(key);
+        synchronized (descs) {
+            Link link = links.remove(key);
+            descs.clear();
             if (link != null) {
-                srcLinks.remove(link.src().deviceId(), link);
-                dstLinks.remove(link.dst().deviceId(), link);
+                srcLinks.remove(link.src().deviceId(), key);
+                dstLinks.remove(link.dst().deviceId(), key);
                 return new LinkEvent(LINK_REMOVED, link);
             }
             return null;
         }
     }
+
+    private static <K, V> SetMultimap<K, V> createSynchronizedHashMultiMap() {
+        return synchronizedSetMultimap(HashMultimap.<K, V>create());
+    }
+
+    /**
+     * @return primary ProviderID, or randomly chosen one if none exists
+     */
+    private ProviderId pickPrimaryPID(
+            ConcurrentMap<ProviderId, LinkDescription> providerDescs) {
+
+        ProviderId fallBackPrimary = null;
+        for (Entry<ProviderId, LinkDescription> e : providerDescs.entrySet()) {
+            if (!e.getKey().isAncillary()) {
+                return e.getKey();
+            } else if (fallBackPrimary == null) {
+                // pick randomly as a fallback in case there is no primary
+                fallBackPrimary = e.getKey();
+            }
+        }
+        return fallBackPrimary;
+    }
+
+    private Link composeLink(ConcurrentMap<ProviderId, LinkDescription> descs) {
+        ProviderId primary = pickPrimaryPID(descs);
+        LinkDescription base = descs.get(primary);
+
+        ConnectPoint src = base.src();
+        ConnectPoint dst = base.dst();
+        Type type = base.type();
+        DefaultAnnotations annotations = DefaultAnnotations.builder().build();
+        annotations = merge(annotations, base.annotations());
+
+        for (Entry<ProviderId, LinkDescription> e : descs.entrySet()) {
+            if (primary.equals(e.getKey())) {
+                continue;
+            }
+
+            // TODO: should keep track of Description timestamp
+            // and only merge conflicting keys when timestamp is newer
+            // Currently assuming there will never be a key conflict between
+            // providers
+
+            // annotation merging. not so efficient, should revisit later
+            annotations = merge(annotations, e.getValue().annotations());
+        }
+
+        return new DefaultLink(primary , src, dst, type, annotations);
+    }
+
+    private ConcurrentMap<ProviderId, LinkDescription> getLinkDescriptions(LinkKey key) {
+        return ConcurrentUtils.createIfAbsentUnchecked(linkDescs, key,
+                NewConcurrentHashMap.<ProviderId, LinkDescription>ifNeeded());
+    }
+
+    private final Function<LinkKey, Link> lookupLink = new LookupLink();
+    private Function<LinkKey, Link> lookupLink() {
+        return lookupLink;
+    }
+
+    private final class LookupLink implements Function<LinkKey, Link> {
+        @Override
+        public Link apply(LinkKey input) {
+            return links.get(input);
+        }
+    }
+
+    private static final Predicate<Provided> IS_PRIMARY = new IsPrimary();
+    private static final Predicate<Provided> isPrimary() {
+        return IS_PRIMARY;
+    }
+
+    private static final class IsPrimary implements Predicate<Provided> {
+
+        @Override
+        public boolean apply(Provided input) {
+            return !input.providerId().isAncillary();
+        }
+    }
 }
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleTopologyStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleTopologyStore.java
index 4e7d5ed..bd7db8a 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleTopologyStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleTopologyStore.java
@@ -124,7 +124,8 @@
         // Promote the new topology to current and return a ready-to-send event.
         synchronized (this) {
             current = newTopology;
-            return new TopologyEvent(TopologyEvent.Type.TOPOLOGY_CHANGED, current);
+            return new TopologyEvent(TopologyEvent.Type.TOPOLOGY_CHANGED,
+                                     current, reasons);
         }
     }
 
diff --git a/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStoreTest.java b/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStoreTest.java
index a0d6e1c..146086a 100644
--- a/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStoreTest.java
+++ b/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStoreTest.java
@@ -103,17 +103,19 @@
         simpleDeviceStore.deactivate();
     }
 
-    private void putDevice(DeviceId deviceId, String swVersion) {
+    private void putDevice(DeviceId deviceId, String swVersion,
+                           SparseAnnotations... annotations) {
         DeviceDescription description =
                 new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR,
-                        HW, swVersion, SN);
+                        HW, swVersion, SN, annotations);
         deviceStore.createOrUpdateDevice(PID, deviceId, description);
     }
 
-    private void putDeviceAncillary(DeviceId deviceId, String swVersion) {
+    private void putDeviceAncillary(DeviceId deviceId, String swVersion,
+                                    SparseAnnotations... annotations) {
         DeviceDescription description =
                 new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR,
-                        HW, swVersion, SN);
+                        HW, swVersion, SN, annotations);
         deviceStore.createOrUpdateDevice(PIDA, deviceId, description);
     }
 
@@ -126,6 +128,7 @@
         assertEquals(SN, device.serialNumber());
     }
 
+    // TODO slice this out somewhere
     /**
      * Verifies that Annotations created by merging {@code annotations} is
      * equal to actual Annotations.
@@ -133,7 +136,7 @@
      * @param actual Annotations to check
      * @param annotations
      */
-    private static void assertAnnotationsEquals(Annotations actual, SparseAnnotations... annotations) {
+    public static void assertAnnotationsEquals(Annotations actual, SparseAnnotations... annotations) {
         DefaultAnnotations expected = DefaultAnnotations.builder().build();
         for (SparseAnnotations a : annotations) {
             expected = DefaultAnnotations.merge(expected, a);
@@ -347,6 +350,7 @@
         assertFalse("Port is disabled", event.port().isEnabled());
 
     }
+
     @Test
     public final void testUpdatePortStatusAncillary() {
         putDeviceAncillary(DID1, SW1);
@@ -435,16 +439,37 @@
 
     @Test
     public final void testRemoveDevice() {
-        putDevice(DID1, SW1);
+        putDevice(DID1, SW1, A1);
+        List<PortDescription> pds = Arrays.<PortDescription>asList(
+                new DefaultPortDescription(P1, true, A2)
+                );
+        deviceStore.updatePorts(PID, DID1, pds);
         putDevice(DID2, SW1);
 
         assertEquals(2, deviceStore.getDeviceCount());
+        assertEquals(1, deviceStore.getPorts(DID1).size());
+        assertAnnotationsEquals(deviceStore.getDevice(DID1).annotations(), A1);
+        assertAnnotationsEquals(deviceStore.getPort(DID1, P1).annotations(), A2);
 
         DeviceEvent event = deviceStore.removeDevice(DID1);
         assertEquals(DEVICE_REMOVED, event.type());
         assertDevice(DID1, SW1, event.subject());
 
         assertEquals(1, deviceStore.getDeviceCount());
+        assertEquals(0, deviceStore.getPorts(DID1).size());
+
+        // putBack Device, Port w/o annotation
+        putDevice(DID1, SW1);
+        List<PortDescription> pds2 = Arrays.<PortDescription>asList(
+                new DefaultPortDescription(P1, true)
+                );
+        deviceStore.updatePorts(PID, DID1, pds2);
+
+        // annotations should not survive
+        assertEquals(2, deviceStore.getDeviceCount());
+        assertEquals(1, deviceStore.getPorts(DID1).size());
+        assertAnnotationsEquals(deviceStore.getDevice(DID1).annotations());
+        assertAnnotationsEquals(deviceStore.getPort(DID1, P1).annotations());
     }
 
     // If Delegates should be called only on remote events,
diff --git a/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleLinkStoreTest.java b/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleLinkStoreTest.java
index eb4a312..8a16609 100644
--- a/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleLinkStoreTest.java
+++ b/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleLinkStoreTest.java
@@ -4,7 +4,9 @@
 import static org.onlab.onos.net.DeviceId.deviceId;
 import static org.onlab.onos.net.Link.Type.*;
 import static org.onlab.onos.net.link.LinkEvent.Type.*;
+import static org.onlab.onos.store.trivial.impl.SimpleDeviceStoreTest.assertAnnotationsEquals;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
@@ -18,10 +20,12 @@
 import org.junit.Ignore;
 import org.junit.Test;
 import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DefaultAnnotations;
 import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.net.Link;
 import org.onlab.onos.net.LinkKey;
 import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.SparseAnnotations;
 import org.onlab.onos.net.Link.Type;
 import org.onlab.onos.net.link.DefaultLinkDescription;
 import org.onlab.onos.net.link.LinkEvent;
@@ -37,6 +41,7 @@
 public class SimpleLinkStoreTest {
 
     private static final ProviderId PID = new ProviderId("of", "foo");
+    private static final ProviderId PIDA = new ProviderId("of", "bar", true);
     private static final DeviceId DID1 = deviceId("of:foo");
     private static final DeviceId DID2 = deviceId("of:bar");
 
@@ -44,6 +49,23 @@
     private static final PortNumber P2 = PortNumber.portNumber(2);
     private static final PortNumber P3 = PortNumber.portNumber(3);
 
+    private static final SparseAnnotations A1 = DefaultAnnotations.builder()
+            .set("A1", "a1")
+            .set("B1", "b1")
+            .build();
+    private static final SparseAnnotations A1_2 = DefaultAnnotations.builder()
+            .remove("A1")
+            .set("B3", "b3")
+            .build();
+    private static final SparseAnnotations A2 = DefaultAnnotations.builder()
+            .set("A2", "a2")
+            .set("B2", "b2")
+            .build();
+    private static final SparseAnnotations A2_2 = DefaultAnnotations.builder()
+            .remove("A2")
+            .set("B4", "b4")
+            .build();
+
 
     private SimpleLinkStore simpleLinkStore;
     private LinkStore linkStore;
@@ -69,16 +91,17 @@
     }
 
     private void putLink(DeviceId srcId, PortNumber srcNum,
-                         DeviceId dstId, PortNumber dstNum, Type type) {
+                         DeviceId dstId, PortNumber dstNum, Type type,
+                         SparseAnnotations... annotations) {
         ConnectPoint src = new ConnectPoint(srcId, srcNum);
         ConnectPoint dst = new ConnectPoint(dstId, dstNum);
-        linkStore.createOrUpdateLink(PID, new DefaultLinkDescription(src, dst, type));
+        linkStore.createOrUpdateLink(PID, new DefaultLinkDescription(src, dst, type, annotations));
     }
 
-    private void putLink(LinkKey key, Type type) {
+    private void putLink(LinkKey key, Type type, SparseAnnotations... annotations) {
         putLink(key.src().deviceId(), key.src().port(),
                 key.dst().deviceId(), key.dst().port(),
-                type);
+                type, annotations);
     }
 
     private static void assertLink(DeviceId srcId, PortNumber srcNum,
@@ -270,14 +293,67 @@
     }
 
     @Test
+    public final void testCreateOrUpdateLinkAncillary() {
+        ConnectPoint src = new ConnectPoint(DID1, P1);
+        ConnectPoint dst = new ConnectPoint(DID2, P2);
+
+        // add Ancillary link
+        LinkEvent event = linkStore.createOrUpdateLink(PIDA,
+                    new DefaultLinkDescription(src, dst, INDIRECT, A1));
+
+        assertNull("Ancillary only link is ignored", event);
+
+        // add Primary link
+        LinkEvent event2 = linkStore.createOrUpdateLink(PID,
+                new DefaultLinkDescription(src, dst, INDIRECT, A2));
+
+        assertLink(DID1, P1, DID2, P2, INDIRECT, event2.subject());
+        assertAnnotationsEquals(event2.subject().annotations(), A2, A1);
+        assertEquals(LINK_ADDED, event2.type());
+
+        // update link type
+        LinkEvent event3 = linkStore.createOrUpdateLink(PID,
+                new DefaultLinkDescription(src, dst, DIRECT, A2));
+        assertLink(DID1, P1, DID2, P2, DIRECT, event3.subject());
+        assertAnnotationsEquals(event3.subject().annotations(), A2, A1);
+        assertEquals(LINK_UPDATED, event3.type());
+
+
+        // no change
+        LinkEvent event4 = linkStore.createOrUpdateLink(PID,
+                new DefaultLinkDescription(src, dst, DIRECT));
+        assertNull("No change event expected", event4);
+
+        // update link annotation (Primary)
+        LinkEvent event5 = linkStore.createOrUpdateLink(PID,
+                new DefaultLinkDescription(src, dst, DIRECT, A2_2));
+        assertLink(DID1, P1, DID2, P2, DIRECT, event5.subject());
+        assertAnnotationsEquals(event5.subject().annotations(), A2, A2_2, A1);
+        assertEquals(LINK_UPDATED, event5.type());
+
+        // update link annotation (Ancillary)
+        LinkEvent event6 = linkStore.createOrUpdateLink(PIDA,
+                new DefaultLinkDescription(src, dst, DIRECT, A1_2));
+        assertLink(DID1, P1, DID2, P2, DIRECT, event6.subject());
+        assertAnnotationsEquals(event6.subject().annotations(), A2, A2_2, A1, A1_2);
+        assertEquals(LINK_UPDATED, event6.type());
+
+        // update link type (Ancillary) : ignored
+        LinkEvent event7 = linkStore.createOrUpdateLink(PIDA,
+                new DefaultLinkDescription(src, dst, EDGE));
+        assertNull("Ancillary change other than annotation is ignored", event7);
+    }
+
+
+    @Test
     public final void testRemoveLink() {
         final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
         final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
         LinkKey linkId1 = new LinkKey(d1P1, d2P2);
         LinkKey linkId2 = new LinkKey(d2P2, d1P1);
 
-        putLink(linkId1, DIRECT);
-        putLink(linkId2, DIRECT);
+        putLink(linkId1, DIRECT, A1);
+        putLink(linkId2, DIRECT, A2);
 
         // DID1,P1 => DID2,P2
         // DID2,P2 => DID1,P1
@@ -285,10 +361,41 @@
 
         LinkEvent event = linkStore.removeLink(d1P1, d2P2);
         assertEquals(LINK_REMOVED, event.type());
+        assertAnnotationsEquals(event.subject().annotations(), A1);
         LinkEvent event2 = linkStore.removeLink(d1P1, d2P2);
         assertNull(event2);
 
         assertLink(linkId2, DIRECT, linkStore.getLink(d2P2, d1P1));
+        assertAnnotationsEquals(linkStore.getLink(d2P2, d1P1).annotations(), A2);
+
+        // annotations, etc. should not survive remove
+        putLink(linkId1, DIRECT);
+        assertLink(linkId1, DIRECT, linkStore.getLink(d1P1, d2P2));
+        assertAnnotationsEquals(linkStore.getLink(d1P1, d2P2).annotations());
+    }
+
+    @Test
+    public final void testAncillaryOnlyNotVisible() {
+        ConnectPoint src = new ConnectPoint(DID1, P1);
+        ConnectPoint dst = new ConnectPoint(DID2, P2);
+
+        // add Ancillary link
+        linkStore.createOrUpdateLink(PIDA,
+                    new DefaultLinkDescription(src, dst, INDIRECT, A1));
+
+        // Ancillary only link should not be visible
+        assertEquals(0, linkStore.getLinkCount());
+
+        assertTrue(Iterables.isEmpty(linkStore.getLinks()));
+
+        assertNull(linkStore.getLink(src, dst));
+
+        assertEquals(Collections.emptySet(), linkStore.getIngressLinks(dst));
+
+        assertEquals(Collections.emptySet(), linkStore.getEgressLinks(src));
+
+        assertEquals(Collections.emptySet(), linkStore.getDeviceEgressLinks(DID1));
+        assertEquals(Collections.emptySet(), linkStore.getDeviceIngressLinks(DID2));
     }
 
     // If Delegates should be called only on remote events,
diff --git a/features/features.xml b/features/features.xml
index 68fa8c3..b240917 100644
--- a/features/features.xml
+++ b/features/features.xml
@@ -9,6 +9,12 @@
         <bundle>mvn:org.apache.commons/commons-lang3/3.3.2</bundle>
         <bundle>mvn:com.google.guava/guava/18.0</bundle>
         <bundle>mvn:io.netty/netty/3.9.2.Final</bundle>
+        <bundle>mvn:io.netty/netty-common/4.0.23.Final</bundle>
+        <bundle>mvn:io.netty/netty-buffer/4.0.23.Final</bundle>
+        <bundle>mvn:io.netty/netty-transport/4.0.23.Final</bundle>
+        <bundle>mvn:io.netty/netty-handler/4.0.23.Final</bundle>
+        <bundle>mvn:io.netty/netty-codec/4.0.23.Final</bundle>
+        <bundle>mvn:commons-pool/commons-pool/1.6</bundle>
 
         <bundle>mvn:com.hazelcast/hazelcast/3.3</bundle>
         <bundle>mvn:io.dropwizard.metrics/metrics-core/3.1.0</bundle>
@@ -53,6 +59,11 @@
         <feature>onos-api</feature>
         <bundle>mvn:org.onlab.onos/onos-core-net/1.0.0-SNAPSHOT</bundle>
         <bundle>mvn:org.onlab.onos/onos-core-dist/1.0.0-SNAPSHOT</bundle>
+        <bundle>mvn:org.onlab.onos/onos-core-serializers/1.0.0-SNAPSHOT</bundle>
+        <bundle>mvn:org.onlab.onos/onlab-netty/1.0.0-SNAPSHOT</bundle>
+
+        <bundle>mvn:org.onlab.onos/onos-core-hz-common/1.0.0-SNAPSHOT</bundle>
+        <bundle>mvn:org.onlab.onos/onos-core-hz-cluster/1.0.0-SNAPSHOT</bundle>
     </feature>
 
     <feature name="onos-core-hazelcast" version="1.0.0"
@@ -120,18 +131,29 @@
         <bundle>mvn:org.onlab.onos/onos-app-fwd/1.0.0-SNAPSHOT</bundle>
     </feature>
 
+    <feature name="onos-app-ifwd" version="1.0.0"
+             description="ONOS sample forwarding application using intents">
+        <feature>onos-api</feature>
+        <bundle>mvn:org.onlab.onos/onos-app-ifwd/1.0.0-SNAPSHOT</bundle>
+    </feature>
+
     <feature name="onos-app-mobility" version="1.0.0"
-             description="ONOS sample forwarding application">
+             description="ONOS sample mobility application">
         <feature>onos-api</feature>
         <bundle>mvn:org.onlab.onos/onos-app-mobility/1.0.0-SNAPSHOT</bundle>
     </feature>
 
-
+    <feature name="onos-app-proxyarp" version="1.0.0"
+             description="ONOS sample proxyarp application">
+        <feature>onos-api</feature>
+        <bundle>mvn:org.onlab.onos/onos-app-proxyarp/1.0.0-SNAPSHOT</bundle>
+    </feature>
 
     <feature name="onos-app-foo" version="1.0.0"
              description="ONOS sample playground application">
         <feature>onos-api</feature>
         <bundle>mvn:org.onlab.onos/onos-app-foo/1.0.0-SNAPSHOT</bundle>
+        <bundle>mvn:org.onlab.onos/onlab-netty/1.0.0-SNAPSHOT</bundle>
     </feature>
 
     <feature name="onos-app-config" version="1.0.0"
diff --git a/features/old-features.xml b/features/old-features.xml
deleted file mode 100644
index 46e68ca..0000000
--- a/features/old-features.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<!--
-  ~ Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
-  ~
-  ~ This program and the accompanying materials are made available under the
-  ~ terms of the Eclipse Public License v1.0 which accompanies this distribution,
-  ~ and is available at http://www.eclipse.org/legal/epl-v10.html
-  -->
-
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0"
-          name="net.onrc.onos-1.0.0">
-    <repository>mvn:net.onrc.onos/onos-features/1.0.0-SNAPSHOT/xml/features</repository>
-
-    <feature name="thirdparty" version="1.0.0"
-             description="ONOS 3rd party dependencies">
-        <bundle>mvn:com.google.code.findbugs/annotations/2.0.2</bundle>
-        <bundle>mvn:io.netty/netty/3.9.2.Final</bundle>
-        <bundle>mvn:com.google.guava/guava/17.0</bundle>  
-        <bundle>mvn:com.google.guava/guava/15.0</bundle>  
-
-    </feature>
-
-    <feature name="base" version="1.0.0"
-            description="ONOS Base">
-        <feature>scr</feature>
-        <feature>thirdparty</feature>
-        <bundle>mvn:net.onrc.onos.sb/onos-sb/0.0.1</bundle>
-        <bundle>mvn:org.projectfloodlight/openflowj/0.3.6-SNAPSHOT</bundle>
-    </feature>
-
-</features>
diff --git a/openflow/api/pom.xml b/openflow/api/pom.xml
index 2c58e47..afc2faf 100644
--- a/openflow/api/pom.xml
+++ b/openflow/api/pom.xml
@@ -17,14 +17,11 @@
     <description>ONOS OpenFlow controller subsystem API</description>
 
     <repositories>
-        <!-- FIXME: for Loxigen. Decide how to use Loxigen before release. -->
+        <!-- FIXME: for Loxigen + optical experimenter. Decide how to use Loxigen before release. -->
         <repository>
-            <id>sonatype-oss-snapshot</id>
-            <name>Sonatype OSS snapshot repository</name>
-            <url>https://oss.sonatype.org/content/repositories/snapshots</url>
-            <releases>
-                <enabled>false</enabled>
-            </releases>
+            <id>onlab-temp</id>
+            <name>ON.lab temporary repository</name>
+            <url>http://mavenrepo.onlab.us:8081/nexus/content/repositories/releases</url>
         </repository>
     </repositories>
 
@@ -32,7 +29,8 @@
         <dependency>
             <groupId>org.projectfloodlight</groupId>
             <artifactId>openflowj</artifactId>
-            <version>0.3.8-SNAPSHOT</version>
+            <!-- FIXME once experimenter gets merged to upstream -->
+            <version>0.3.8-optical_experimenter</version>
         </dependency>
         <dependency>
             <groupId>io.netty</groupId>
diff --git a/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OFChannelHandler.java b/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OFChannelHandler.java
index 0a3ee86..5be7c69 100644
--- a/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OFChannelHandler.java
+++ b/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OFChannelHandler.java
@@ -167,11 +167,11 @@
                 // TODO We could check for the optional bitmap, but for now
                 // we are just checking the version number.
                 if (m.getVersion() == OFVersion.OF_13) {
-                    log.info("Received {} Hello from {}", m.getVersion(),
+                    log.debug("Received {} Hello from {}", m.getVersion(),
                             h.channel.getRemoteAddress());
                     h.ofVersion = OFVersion.OF_13;
                 } else if (m.getVersion() == OFVersion.OF_10) {
-                    log.info("Received {} Hello from {} - switching to OF "
+                    log.debug("Received {} Hello from {} - switching to OF "
                             + "version 1.0", m.getVersion(),
                             h.channel.getRemoteAddress());
                     h.ofVersion = OFVersion.OF_10;
@@ -222,7 +222,7 @@
             void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply  m)
                     throws IOException {
                 h.thisdpid = m.getDatapathId().getLong();
-                log.info("Received features reply for switch at {} with dpid {}",
+                log.debug("Received features reply for switch at {} with dpid {}",
                         h.getSwitchInfoString(), h.thisdpid);
 
                 h.featuresReply = m; //temp store
@@ -409,7 +409,7 @@
 
 
 
-                log.info("Switch {} bound to class {}, description {}",
+                log.debug("Switch {} bound to class {}, description {}",
                         new Object[] {h.sw, h.sw.getClass(), drep });
                 //Put switch in EQUAL mode until we hear back from the global registry
                 //log.debug("Setting new switch {} to EQUAL and sending Role request",
@@ -651,7 +651,7 @@
          * @param error The error message
          */
         protected void logError(OFChannelHandler h, OFErrorMsg error) {
-            log.info("{} from switch {} in state {}",
+            log.error("{} from switch {} in state {}",
                     new Object[] {
                     error,
                     h.getSwitchInfoString(),
@@ -1052,7 +1052,7 @@
             throws Exception {
         OFFactory factory = (ofVersion == OFVersion.OF_13) ? factory13 : factory10;
         OFMessage m = factory.buildEchoRequest().build();
-        log.info("Sending Echo Request on idle channel: {}",
+        log.debug("Sending Echo Request on idle channel: {}",
                 e.getChannel().getPipeline().getLast().toString());
         e.getChannel().write(Collections.singletonList(m));
         // XXX S some problems here -- echo request has no transaction id, and
diff --git a/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/RoleManager.java b/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/RoleManager.java
index b47bada..992270f 100644
--- a/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/RoleManager.java
+++ b/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/RoleManager.java
@@ -122,7 +122,7 @@
                 //FIXME fix below when we actually use generation ids
                 .setGenerationId(U64.ZERO)
                 .build();
-        sw.sendMsg(rrm);
+        sw.write(rrm);
         return xid;
     }
 
diff --git a/openflow/ctl/src/main/java/org/onlab/onos/openflow/drivers/impl/OFSwitchImplOVS13.java b/openflow/ctl/src/main/java/org/onlab/onos/openflow/drivers/impl/OFSwitchImplOVS13.java
index 05ffdd1..6bb4586 100644
--- a/openflow/ctl/src/main/java/org/onlab/onos/openflow/drivers/impl/OFSwitchImplOVS13.java
+++ b/openflow/ctl/src/main/java/org/onlab/onos/openflow/drivers/impl/OFSwitchImplOVS13.java
@@ -138,7 +138,7 @@
                 .buildBarrierRequest()
                 .setXid(xid)
                 .build();
-        sendMsg(br);
+        write(br);
     }
 
     @Override
@@ -227,7 +227,7 @@
                 .setHardTimeout(0)
                 .setXid(getNextTransactionId())
                 .build();
-        sendMsg(tableMissEntry);
+        write(tableMissEntry);
     }
 
 }
diff --git a/openflow/openflowj/gen-src/main/java/org/projectfloodlight/openflow/protocol/OFType.java b/openflow/openflowj/gen-src/main/java/org/projectfloodlight/openflow/protocol/OFType.java
index aef779f..51de582 100644
--- a/openflow/openflowj/gen-src/main/java/org/projectfloodlight/openflow/protocol/OFType.java
+++ b/openflow/openflowj/gen-src/main/java/org/projectfloodlight/openflow/protocol/OFType.java
@@ -40,15 +40,15 @@
      PORT_STATUS,
      PACKET_OUT,
      FLOW_MOD,
+     GROUP_MOD,
      PORT_MOD,
+     TABLE_MOD,
      STATS_REQUEST,
      STATS_REPLY,
      BARRIER_REQUEST,
      BARRIER_REPLY,
      QUEUE_GET_CONFIG_REQUEST,
      QUEUE_GET_CONFIG_REPLY,
-     GROUP_MOD,
-     TABLE_MOD,
      ROLE_REQUEST,
      ROLE_REPLY,
      GET_ASYNC_REQUEST,
diff --git a/pom.xml b/pom.xml
index cb00f32..ad4ddcb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -69,6 +69,13 @@
 
             <dependency>
                 <groupId>org.slf4j</groupId>
+                <artifactId>slf4j-core</artifactId>
+                <version>1.7.6</version>
+                <scope>test</scope>
+            </dependency>
+
+            <dependency>
+                <groupId>org.slf4j</groupId>
                 <artifactId>slf4j-jdk14</artifactId>
                 <version>1.7.6</version>
                 <scope>test</scope>
@@ -122,6 +129,12 @@
                 <version>1.9.13</version>
             </dependency>
 
+	    <dependency>
+	        <groupId>org.easymock</groupId>
+	        <artifactId>easymock</artifactId>
+	        <version>3.2</version>
+	        <scope>test</scope>
+	    </dependency>
 
             <!-- Web related -->
             <dependency>
@@ -480,7 +493,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.store.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.*:org.onlab.onos.net.intent.impl
                             </packages>
                         </group>
                         <group>
diff --git a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowRuleBuilder.java b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowEntryBuilder.java
similarity index 78%
rename from providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowRuleBuilder.java
rename to providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowEntryBuilder.java
index eba2282..14c2c22 100644
--- a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowRuleBuilder.java
+++ b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowEntryBuilder.java
@@ -6,11 +6,13 @@
 
 import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.flow.DefaultFlowEntry;
 import org.onlab.onos.net.flow.DefaultFlowRule;
 import org.onlab.onos.net.flow.DefaultTrafficSelector;
 import org.onlab.onos.net.flow.DefaultTrafficTreatment;
+import org.onlab.onos.net.flow.FlowEntry;
+import org.onlab.onos.net.flow.FlowEntry.FlowEntryState;
 import org.onlab.onos.net.flow.FlowRule;
-import org.onlab.onos.net.flow.FlowRule.FlowRuleState;
 import org.onlab.onos.net.flow.TrafficSelector;
 import org.onlab.onos.net.flow.TrafficTreatment;
 import org.onlab.onos.openflow.controller.Dpid;
@@ -18,8 +20,8 @@
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
 import org.projectfloodlight.openflow.protocol.OFFlowRemoved;
-import org.projectfloodlight.openflow.protocol.OFFlowRemovedReason;
 import org.projectfloodlight.openflow.protocol.OFFlowStatsEntry;
+import org.projectfloodlight.openflow.protocol.OFInstructionType;
 import org.projectfloodlight.openflow.protocol.action.OFAction;
 import org.projectfloodlight.openflow.protocol.action.OFActionOutput;
 import org.projectfloodlight.openflow.protocol.action.OFActionSetDlDst;
@@ -28,12 +30,16 @@
 import org.projectfloodlight.openflow.protocol.action.OFActionSetNwSrc;
 import org.projectfloodlight.openflow.protocol.action.OFActionSetVlanPcp;
 import org.projectfloodlight.openflow.protocol.action.OFActionSetVlanVid;
+import org.projectfloodlight.openflow.protocol.instruction.OFInstruction;
+import org.projectfloodlight.openflow.protocol.instruction.OFInstructionApplyActions;
 import org.projectfloodlight.openflow.protocol.match.Match;
 import org.projectfloodlight.openflow.protocol.match.MatchField;
 import org.projectfloodlight.openflow.types.IPv4Address;
 import org.slf4j.Logger;
 
-public class FlowRuleBuilder {
+import com.google.common.collect.Lists;
+
+public class FlowEntryBuilder {
     private final Logger log = getLogger(getClass());
 
     private final OFFlowStatsEntry stat;
@@ -44,49 +50,70 @@
 
     private final Dpid dpid;
 
+    private final boolean addedRule;
 
 
-
-    public FlowRuleBuilder(Dpid dpid, OFFlowStatsEntry entry) {
+    public FlowEntryBuilder(Dpid dpid, OFFlowStatsEntry entry) {
         this.stat = entry;
         this.match = entry.getMatch();
-        this.actions = entry.getActions();
+        this.actions = getActions(entry);
         this.dpid = dpid;
         this.removed = null;
+        this.addedRule = true;
     }
 
-    public FlowRuleBuilder(Dpid dpid, OFFlowRemoved removed) {
+    public FlowEntryBuilder(Dpid dpid, OFFlowRemoved removed) {
         this.match = removed.getMatch();
         this.removed = removed;
 
         this.dpid = dpid;
         this.actions = null;
         this.stat = null;
+        this.addedRule = false;
 
     }
 
-    public FlowRule build() {
-        if (stat != null) {
-            return new DefaultFlowRule(DeviceId.deviceId(Dpid.uri(dpid)),
+    public FlowEntry build() {
+        if (addedRule) {
+            FlowRule rule = new DefaultFlowRule(DeviceId.deviceId(Dpid.uri(dpid)),
                     buildSelector(), buildTreatment(), stat.getPriority(),
-                    FlowRuleState.ADDED, stat.getDurationNsec() / 1000000,
-                    stat.getPacketCount().getValue(), stat.getByteCount().getValue(),
-                    stat.getCookie().getValue(), false, stat.getIdleTimeout());
+                    stat.getCookie().getValue(), stat.getIdleTimeout());
+            return new DefaultFlowEntry(rule, FlowEntryState.ADDED,
+                    stat.getDurationSec(), stat.getPacketCount().getValue(),
+                    stat.getByteCount().getValue());
+
         } else {
-            // TODO: revisit potentially.
-            return new DefaultFlowRule(DeviceId.deviceId(Dpid.uri(dpid)),
+            FlowRule rule = new DefaultFlowRule(DeviceId.deviceId(Dpid.uri(dpid)),
                     buildSelector(), null, removed.getPriority(),
-                    FlowRuleState.REMOVED, removed.getDurationNsec() / 1000000,
-                    removed.getPacketCount().getValue(), removed.getByteCount().getValue(),
-                    removed.getCookie().getValue(),
-                    removed.getReason() == OFFlowRemovedReason.IDLE_TIMEOUT.ordinal(),
-                    stat.getIdleTimeout());
+                   removed.getCookie().getValue(), removed.getIdleTimeout());
+            return new DefaultFlowEntry(rule, FlowEntryState.REMOVED, removed.getDurationSec(),
+                    removed.getPacketCount().getValue(), removed.getByteCount().getValue());
         }
     }
 
+    private List<OFAction> getActions(OFFlowStatsEntry entry) {
+        switch (entry.getVersion()) {
+            case OF_10:
+                return entry.getActions();
+            case OF_11:
+            case OF_12:
+            case OF_13:
+                List<OFInstruction> ins = entry.getInstructions();
+                for (OFInstruction in : ins) {
+                    if (in.getType().equals(OFInstructionType.APPLY_ACTIONS)) {
+                        OFInstructionApplyActions apply = (OFInstructionApplyActions) in;
+                        return apply.getActions();
+                    }
+                }
+                return Lists.newLinkedList();
+            default:
+                log.warn("Unknown OF version {}", entry.getVersion());
+        }
+        return Lists.newLinkedList();
+    }
 
     private TrafficTreatment buildTreatment() {
-        TrafficTreatment.Builder builder = new DefaultTrafficTreatment.Builder();
+        TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder();
         // If this is a drop rule
         if (actions.size() == 0) {
             builder.drop();
@@ -171,7 +198,7 @@
     }
 
     private TrafficSelector buildSelector() {
-        TrafficSelector.Builder builder = new DefaultTrafficSelector.Builder();
+        TrafficSelector.Builder builder = DefaultTrafficSelector.builder();
         for (MatchField<?> field : match.getMatchFields()) {
             switch (field.id) {
             case IN_PORT:
diff --git a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowModBuilder.java b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowModBuilder.java
index ade651e..78f5874 100644
--- a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowModBuilder.java
+++ b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowModBuilder.java
@@ -68,7 +68,7 @@
         this.cookie = flowRule.id();
     }
 
-    public OFFlowMod buildFlowMod() {
+    public OFFlowMod buildFlowAdd() {
         Match match = buildMatch();
         List<OFAction> actions = buildActions();
 
@@ -86,6 +86,24 @@
 
     }
 
+    public OFFlowMod buildFlowMod() {
+        Match match = buildMatch();
+        List<OFAction> actions = buildActions();
+
+        //TODO: what to do without bufferid? do we assume that there will be a pktout as well?
+        OFFlowMod fm = factory.buildFlowModify()
+                .setCookie(U64.of(cookie.value()))
+                .setBufferId(OFBufferId.NO_BUFFER)
+                .setActions(actions)
+                .setMatch(match)
+                .setFlags(Collections.singleton(OFFlowModFlags.SEND_FLOW_REM))
+                .setPriority(priority)
+                .build();
+
+        return fm;
+
+    }
+
     public OFFlowMod buildFlowDel() {
         Match match = buildMatch();
         List<OFAction> actions = buildActions();
diff --git a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/OpenFlowRuleProvider.java b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/OpenFlowRuleProvider.java
index 24a7ea8..19cfb6a 100644
--- a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/OpenFlowRuleProvider.java
+++ b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/OpenFlowRuleProvider.java
@@ -2,7 +2,17 @@
 
 import static org.slf4j.LoggerFactory.getLogger;
 
+import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
@@ -11,10 +21,13 @@
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.onlab.onos.ApplicationId;
 import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.flow.FlowEntry;
 import org.onlab.onos.net.flow.FlowRule;
+import org.onlab.onos.net.flow.FlowRuleBatchEntry;
 import org.onlab.onos.net.flow.FlowRuleProvider;
 import org.onlab.onos.net.flow.FlowRuleProviderRegistry;
 import org.onlab.onos.net.flow.FlowRuleProviderService;
+import org.onlab.onos.net.intent.BatchOperation;
 import org.onlab.onos.net.provider.AbstractProvider;
 import org.onlab.onos.net.provider.ProviderId;
 import org.onlab.onos.net.topology.TopologyService;
@@ -24,17 +37,29 @@
 import org.onlab.onos.openflow.controller.OpenFlowSwitch;
 import org.onlab.onos.openflow.controller.OpenFlowSwitchListener;
 import org.onlab.onos.openflow.controller.RoleState;
+import org.projectfloodlight.openflow.protocol.OFActionType;
+import org.projectfloodlight.openflow.protocol.OFBarrierRequest;
+import org.projectfloodlight.openflow.protocol.OFErrorMsg;
 import org.projectfloodlight.openflow.protocol.OFFlowRemoved;
 import org.projectfloodlight.openflow.protocol.OFFlowStatsEntry;
 import org.projectfloodlight.openflow.protocol.OFFlowStatsReply;
+import org.projectfloodlight.openflow.protocol.OFInstructionType;
 import org.projectfloodlight.openflow.protocol.OFMessage;
 import org.projectfloodlight.openflow.protocol.OFPortStatus;
 import org.projectfloodlight.openflow.protocol.OFStatsReply;
 import org.projectfloodlight.openflow.protocol.OFStatsReplyFlags;
 import org.projectfloodlight.openflow.protocol.OFStatsType;
+import org.projectfloodlight.openflow.protocol.OFVersion;
+import org.projectfloodlight.openflow.protocol.action.OFAction;
+import org.projectfloodlight.openflow.protocol.action.OFActionOutput;
+import org.projectfloodlight.openflow.protocol.instruction.OFInstruction;
+import org.projectfloodlight.openflow.protocol.instruction.OFInstructionApplyActions;
+import org.projectfloodlight.openflow.types.OFPort;
+import org.projectfloodlight.openflow.types.U32;
 import org.slf4j.Logger;
 
 import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
 
@@ -60,6 +85,9 @@
 
     private final InternalFlowProvider listener = new InternalFlowProvider();
 
+    private final Map<Long, InstallationFuture> pendingFutures =
+            new ConcurrentHashMap<Long, InstallationFuture>();
+
     /**
      * Creates an OpenFlow host provider.
      */
@@ -91,7 +119,7 @@
 
     private void applyRule(FlowRule flowRule) {
         OpenFlowSwitch sw = controller.getSwitch(Dpid.dpid(flowRule.deviceId().uri()));
-        sw.sendMsg(new FlowModBuilder(flowRule, sw.factory()).buildFlowMod());
+        sw.sendMsg(new FlowModBuilder(flowRule, sw.factory()).buildFlowAdd());
     }
 
 
@@ -122,7 +150,7 @@
     implements OpenFlowSwitchListener, OpenFlowEventListener {
 
         private final Map<Dpid, FlowStatsCollector> collectors = Maps.newHashMap();
-        private final Multimap<DeviceId, FlowRule> completeEntries =
+        private final Multimap<DeviceId, FlowEntry> completeEntries =
                 ArrayListMultimap.create();
 
         @Override
@@ -144,19 +172,30 @@
 
         @Override
         public void handleMessage(Dpid dpid, OFMessage msg) {
+            InstallationFuture future = null;
             switch (msg.getType()) {
             case FLOW_REMOVED:
                 //TODO: make this better
                 OFFlowRemoved removed = (OFFlowRemoved) msg;
 
-                FlowRule fr = new FlowRuleBuilder(dpid, removed).build();
+                FlowEntry fr = new FlowEntryBuilder(dpid, removed).build();
                 providerService.flowRemoved(fr);
                 break;
             case STATS_REPLY:
                 pushFlowMetrics(dpid, (OFStatsReply) msg);
                 break;
             case BARRIER_REPLY:
+                future = pendingFutures.get(msg.getXid());
+                if (future != null) {
+                    future.satisfyRequirement(dpid);
+                }
+                break;
             case ERROR:
+                future = pendingFutures.get(msg.getXid());
+                if (future != null) {
+                    future.fail((OFErrorMsg) msg, dpid);
+                }
+                break;
             default:
                 log.debug("Unhandled message type: {}", msg.getType());
             }
@@ -178,8 +217,9 @@
             //final List<FlowRule> entries = Lists.newLinkedList();
 
             for (OFFlowStatsEntry reply : replies.getEntries()) {
-                completeEntries.put(did, new FlowRuleBuilder(dpid, reply).build());
-                //entries.add(new FlowRuleBuilder(dpid, reply).build());
+                if (!tableMissRule(dpid, reply)) {
+                    completeEntries.put(did, new FlowEntryBuilder(dpid, reply).build());
+                }
             }
 
             if (!stats.getFlags().contains(OFStatsReplyFlags.REPLY_MORE)) {
@@ -189,9 +229,170 @@
             }
         }
 
+        private boolean tableMissRule(Dpid dpid, OFFlowStatsEntry reply) {
+            // TODO NEED TO FIND A BETTER WAY TO AVOID DOING THIS
+            if (reply.getVersion().equals(OFVersion.OF_10) ||
+                    reply.getMatch().getMatchFields().iterator().hasNext()) {
+                return false;
+            }
+            for (OFInstruction ins : reply.getInstructions()) {
+                if (ins.getType() == OFInstructionType.APPLY_ACTIONS) {
+                    OFInstructionApplyActions apply = (OFInstructionApplyActions) ins;
+                    List<OFAction> acts = apply.getActions();
+                    for (OFAction act : acts) {
+                        if (act.getType() == OFActionType.OUTPUT) {
+                            OFActionOutput out = (OFActionOutput) act;
+                            if (out.getPort() == OFPort.CONTROLLER) {
+                                return true;
+                            }
+                        }
+                    }
+                }
+            }
+            return false;
+        }
+
     }
 
 
+    @Override
+    public Future<Void> executeBatch(BatchOperation<FlowRuleBatchEntry> batch) {
+        final Set<Dpid> sws = new HashSet<Dpid>();
 
+        for (FlowRuleBatchEntry fbe : batch.getOperations()) {
+            FlowRule flowRule = fbe.getTarget();
+            OpenFlowSwitch sw = controller.getSwitch(Dpid.dpid(flowRule.deviceId().uri()));
+            sws.add(new Dpid(sw.getId()));
+            switch (fbe.getOperator()) {
+                case ADD:
+                  //TODO: Track XID for each flowmod
+                    sw.sendMsg(new FlowModBuilder(flowRule, sw.factory()).buildFlowAdd());
+                    break;
+                case REMOVE:
+                  //TODO: Track XID for each flowmod
+                    sw.sendMsg(new FlowModBuilder(flowRule, sw.factory()).buildFlowDel());
+                    break;
+                case MODIFY:
+                  //TODO: Track XID for each flowmod
+                    sw.sendMsg(new FlowModBuilder(flowRule, sw.factory()).buildFlowMod());
+                    break;
+                default:
+                    log.error("Unsupported batch operation {}", fbe.getOperator());
+            }
+        }
+        InstallationFuture installation = new InstallationFuture(sws);
+        pendingFutures.put(U32.f(batch.hashCode()), installation);
+        installation.verify(batch.hashCode());
+        return installation;
+    }
+
+    private class InstallationFuture implements Future<Void> {
+
+        private final Set<Dpid> sws;
+        private final AtomicBoolean ok = new AtomicBoolean(true);
+        private final List<FlowEntry> offendingFlowMods = Lists.newLinkedList();
+
+        private final CountDownLatch countDownLatch;
+
+        public InstallationFuture(Set<Dpid> sws) {
+            this.sws = sws;
+            countDownLatch = new CountDownLatch(sws.size());
+        }
+
+        public void fail(OFErrorMsg msg, Dpid dpid) {
+            ok.set(false);
+            //TODO add reason to flowentry
+            //TODO handle specific error msgs
+            //offendingFlowMods.add(new FlowEntryBuilder(dpid, msg.));
+            switch (msg.getErrType()) {
+                case BAD_ACTION:
+                    break;
+                case BAD_INSTRUCTION:
+                    break;
+                case BAD_MATCH:
+                    break;
+                case BAD_REQUEST:
+                    break;
+                case EXPERIMENTER:
+                    break;
+                case FLOW_MOD_FAILED:
+                    break;
+                case GROUP_MOD_FAILED:
+                    break;
+                case HELLO_FAILED:
+                    break;
+                case METER_MOD_FAILED:
+                    break;
+                case PORT_MOD_FAILED:
+                    break;
+                case QUEUE_OP_FAILED:
+                    break;
+                case ROLE_REQUEST_FAILED:
+                    break;
+                case SWITCH_CONFIG_FAILED:
+                    break;
+                case TABLE_FEATURES_FAILED:
+                    break;
+                case TABLE_MOD_FAILED:
+                    break;
+                default:
+                    break;
+
+            }
+
+        }
+
+        public void satisfyRequirement(Dpid dpid) {
+            log.warn("Satisfaction from switch {}", dpid);
+            sws.remove(dpid);
+            countDownLatch.countDown();
+        }
+
+        public void verify(Integer id) {
+            for (Dpid dpid : sws) {
+                OpenFlowSwitch sw = controller.getSwitch(dpid);
+                OFBarrierRequest.Builder builder = sw.factory()
+                        .buildBarrierRequest()
+                        .setXid(id);
+                sw.sendMsg(builder.build());
+            }
+
+
+        }
+
+        @Override
+        public boolean cancel(boolean mayInterruptIfRunning) {
+                // TODO Auto-generated method stub
+                return false;
+        }
+
+        @Override
+        public boolean isCancelled() {
+            // TODO Auto-generated method stub
+            return false;
+        }
+
+        @Override
+        public boolean isDone() {
+            return sws.isEmpty();
+        }
+
+        @Override
+        public Void get() throws InterruptedException, ExecutionException {
+            countDownLatch.await();
+            //return offendingFlowMods;
+            return null;
+        }
+
+        @Override
+        public Void get(long timeout, TimeUnit unit)
+                throws InterruptedException, ExecutionException,
+                TimeoutException {
+            countDownLatch.await(timeout, unit);
+            //return offendingFlowMods;
+            return null;
+        }
+
+    }
 
 }
diff --git a/providers/openflow/packet/src/test/java/org/onlab/onos/provider/of/packet/impl/OpenFlowPacketProviderTest.java b/providers/openflow/packet/src/test/java/org/onlab/onos/provider/of/packet/impl/OpenFlowPacketProviderTest.java
index 030563c..37971e3 100644
--- a/providers/openflow/packet/src/test/java/org/onlab/onos/provider/of/packet/impl/OpenFlowPacketProviderTest.java
+++ b/providers/openflow/packet/src/test/java/org/onlab/onos/provider/of/packet/impl/OpenFlowPacketProviderTest.java
@@ -181,7 +181,7 @@
     }
 
     private static TrafficTreatment treatment(Instruction ... insts) {
-        TrafficTreatment.Builder builder = new DefaultTrafficTreatment.Builder();
+        TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder();
         for (Instruction i : insts) {
             builder.add(i);
         }
diff --git a/tools/build/envDefaults b/tools/build/envDefaults
index 473095c..cbc6577 100644
--- a/tools/build/envDefaults
+++ b/tools/build/envDefaults
@@ -9,10 +9,14 @@
 export KARAF_TAR=${KARAF_TAR:-~/Downloads/apache-karaf-3.0.1.tar.gz}
 export KARAF_DIST=$(basename $KARAF_ZIP .zip)
 
+# Fallback build number us derived from from the user name & time
+export BUILD_NUMBER=${BUILD_NUMBER:-$(id -un)~$(date +'%Y/%m/%d@%H:%M')}
+
 # ONOS Version and onos.tar.gz staging environment
-export ONOS_VERSION=${ONOS_VERSION:-1.0.0-SNAPSHOT}
+export ONOS_POM_VERSION="1.0.0-SNAPSHOT"
+export ONOS_VERSION=${ONOS_VERSION:-1.0.0.$BUILD_NUMBER}
+export ONOS_BITS=onos-${ONOS_VERSION%~*}
 export ONOS_STAGE_ROOT=${ONOS_STAGE_ROOT:-/tmp}
-export ONOS_BITS=onos-$ONOS_VERSION
 export ONOS_STAGE=$ONOS_STAGE_ROOT/$ONOS_BITS
 export ONOS_TAR=$ONOS_STAGE.tar.gz
 
diff --git a/tools/build/onos-package b/tools/build/onos-package
index 2d6b954..a55a613 100755
--- a/tools/build/onos-package
+++ b/tools/build/onos-package
@@ -49,7 +49,7 @@
 # ONOS Patching ----------------------------------------------------------------
 
 # Patch the Apache Karaf distribution file to add ONOS features repository
-perl -pi.old -e "s|^(featuresRepositories=.*)|\1,mvn:org.onlab.onos/onos-features/$ONOS_VERSION/xml/features|" \
+perl -pi.old -e "s|^(featuresRepositories=.*)|\1,mvn:org.onlab.onos/onos-features/$ONOS_POM_VERSION/xml/features|" \
     $ONOS_STAGE/$KARAF_DIST/etc/org.apache.karaf.features.cfg 
 
 # Patch the Apache Karaf distribution file to load ONOS features
@@ -57,10 +57,14 @@
     $ONOS_STAGE/$KARAF_DIST/etc/org.apache.karaf.features.cfg
 
 # Patch the Apache Karaf distribution with ONOS branding bundle
-cp $M2_REPO/org/onlab/onos/onos-branding/$ONOS_VERSION/onos-branding-*.jar \
+cp $M2_REPO/org/onlab/onos/onos-branding/$ONOS_POM_VERSION/onos-branding-*.jar \
     $ONOS_STAGE/$KARAF_DIST/lib
 
+# Patch in the ONOS version file
+echo $ONOS_VERSION > $ONOS_STAGE/VERSION
+
 # Now package up the ONOS tar file
 cd $ONOS_STAGE_ROOT
 COPYFILE_DISABLE=1 tar zcf $ONOS_TAR $ONOS_BITS
 ls -l $ONOS_TAR >&2
+rm -r $ONOS_STAGE
diff --git a/tools/dev/bash_profile b/tools/dev/bash_profile
index 8332a47..6c1444f 100644
--- a/tools/dev/bash_profile
+++ b/tools/dev/bash_profile
@@ -21,7 +21,7 @@
 # e.g. 'o api', 'o dev', 'o'
 function o {
     cd $(find $ONOS_ROOT/ -type d | egrep -v '\.git|target' | \
-        egrep "${1:-$ONOS_ROOT}" | head -n 1)
+        egrep "${1:-$ONOS_ROOT}" | egrep -v "$ONOS_ROOT/.+/src/" | head -n 1)
 }
 
 # Short-hand for 'mvn clean install' for us lazy folk
@@ -32,6 +32,9 @@
 alias obs='onos-build-selective'
 alias op='onos-package'
 alias ot='onos-test'
+alias ol='onos-log'
+alias go='ob && ot && onos -w'
+alias pub='onos-push-update-bundle'
 
 # Short-hand for tailing the ONOS (karaf) log 
 alias tl='$ONOS_ROOT/tools/dev/bin/onos-local-log'
@@ -88,5 +91,5 @@
 }
 
 function nuke {
-    spy | cut -c7-11 | xargs kill
+    spy "$@" | cut -c7-11 | xargs kill
 }
diff --git a/tools/package/bin/onos-service b/tools/package/bin/onos-service
index a5de5bd..c030887 100755
--- a/tools/package/bin/onos-service
+++ b/tools/package/bin/onos-service
@@ -4,6 +4,7 @@
 #-------------------------------------------------------------------------------
 
 export JAVA_HOME=${JAVA_HOME:-/usr/lib/jvm/java-7-openjdk-amd64/}
+export JAVA_OPTS="-Xms256M -Xmx2048M"
 
 cd /opt/onos
 /opt/onos/apache-karaf-3.0.1/bin/karaf "$@"
diff --git a/tools/test/bin/onos-install b/tools/test/bin/onos-install
index a87ff17..38934fd 100755
--- a/tools/test/bin/onos-install
+++ b/tools/test/bin/onos-install
@@ -32,6 +32,10 @@
 
     # Remove any previous ON.Lab bits from ~/.m2 repo
     rm -fr ~/.m2/repository/org/onlab
+
+    # Drop log level for the console
+    echo "log4j.logger.org.apache.sshd = WARN" >> $ONOS_INSTALL_DIR/$KARAF_DIST/etc/org.ops4j.pax.logging.cfg
+
 "
 
 # Configure the ONOS installation
diff --git a/tools/test/bin/onos-patch-vm b/tools/test/bin/onos-patch-vm
index f80f349..ccc4007 100755
--- a/tools/test/bin/onos-patch-vm
+++ b/tools/test/bin/onos-patch-vm
@@ -15,7 +15,7 @@
 
 ssh $remote "
     sudo perl -pi.bak -e \"s/127.0.1.1.*/127.0.1.1       $name/g\" /etc/hosts
-    sudo perl -pi.bak -e \"s/.*/$name/g\" /etc/hostname
+    sudo bash -c \"echo $name >/etc/hostname\"
     sudo hostname $name
 " 2>/dev/null
 
diff --git a/tools/test/bin/onos-push-keys b/tools/test/bin/onos-push-keys
index fd49f86..247d331 100755
--- a/tools/test/bin/onos-push-keys
+++ b/tools/test/bin/onos-push-keys
@@ -9,5 +9,9 @@
 remote=$ONOS_USER@${1:-$OCI}
 
 scp -q ~/.ssh/id_rsa.pub $remote:/tmp
-ssh $remote "cat /tmp/id_rsa.pub >> ~/.ssh/authorized_keys"
+ssh $remote "
+    cat /tmp/id_rsa.pub >> ~/.ssh/authorized_keys
+    sort -u ~/.ssh/authorized_keys > ~/.ssh/authorized_keys.bak
+    mv ~/.ssh/authorized_keys.bak ~/.ssh/authorized_keys
+"
 ssh -n -o PasswordAuthentication=no $remote true
diff --git a/tools/test/bin/onos-push-update-bundle b/tools/test/bin/onos-push-update-bundle
new file mode 100755
index 0000000..4f8ca7d
--- /dev/null
+++ b/tools/test/bin/onos-push-update-bundle
@@ -0,0 +1,21 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# Pushes the specified bundle to the remote ONOS cell machines and updates it.
+#-------------------------------------------------------------------------------
+
+[ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1
+. $ONOS_ROOT/tools/build/envDefaults
+
+cd ~/.m2/repository
+jar=$(find org/onlab -type f -name '*.jar' | grep $1 | grep -v -e -tests | head -n 1)
+
+[ -z "$jar" ] && echo "No bundle $1 found for" && exit 1
+
+bundle=$(echo $(basename $jar .jar) | sed 's/-[0-9].*//g')
+
+nodes=$(env | sort | egrep "OC[0-9]+" | cut -d= -f2)
+for node in $nodes; do
+    scp -q $jar $ONOS_USER@$node:.m2/repository/$jar
+    scp -q $jar $ONOS_USER@$node:$ONOS_INSTALL_DIR/$KARAF_DIST/system/$jar
+    ssh $ONOS_USER@$node "$ONOS_INSTALL_DIR/bin/onos \"bundle:update -f $bundle\"" 2>/dev/null
+done
diff --git a/tools/test/topos/tower.py b/tools/test/topos/tower.py
new file mode 100644
index 0000000..b8a5f7c
--- /dev/null
+++ b/tools/test/topos/tower.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python
+from mininet.cli import CLI
+from mininet.net import Mininet
+from mininet.node import RemoteController, OVSKernelSwitch
+
+MAC = 12
+DPID = 16
+
+def string_to_hex(s, length):
+    """ Convert a string like 00:00 in to hex 0x0000 format"""
+    tmp = '{0:#x}'.format(int(s.replace(':', '').lstrip('0'),length))
+    return tmp
+
+def hex_to_string(h, length):
+    """Convert a hex number from 0x0000 to 00:00 format"""
+    tmp = h.lstrip('0x').zfill(length)
+    tmp = ':'.join(a+b for a,b in zip(tmp[::2], tmp[1::2]))
+    return tmp
+
+class Tower(object):
+    """ Create a tower topology from semi-scratch in Mininet """
+
+    def __init__(self, cname='flare', cip='15.255.126.183', k=4, h=6, 
+                 proto=None):
+        """Create tower topology for mininet
+            cname: controller name
+            cip: controller ip
+            k: number of leaf switches
+            h: number of hosts perl leaf switch
+        """
+
+        # We are creating the controller with local-loopback on purpose to avoid
+        # having the switches connect immediately. Instead, we'll set controller
+        # explicitly for each switch after configuring it as we want.
+        self.flare = RemoteController(cname, '127.0.0.1', 6633)
+        self.net = Mininet(controller=self.flare, switch = OVSKernelSwitch, 
+                           build=False)
+
+        self.cip = cip
+        self.spines = []
+        self.leaves = []
+        self.hosts = []
+        self.proto = proto
+
+        # Create the two spine switches
+        self.spines.append(self.net.addSwitch('s1'))
+        self.spines.append(self.net.addSwitch('s2'))
+
+        # Create two links between the spine switches
+        self.net.addLink(self.spines[0], self.spines[1])
+        self.net.addLink(self.spines[1], self.spines[0])
+        
+        # Now create the leaf switches, their hosts and connect them together
+        i = 1
+        c = 0
+        while i <= k:
+            self.leaves.append(self.net.addSwitch('s1%d' % i))
+            for spine in self.spines:
+                self.net.addLink(self.leaves[i-1], spine)
+
+            j = 1
+            while j <= h:
+                self.hosts.append(self.net.addHost('h%d%d' % (i, j)))
+                self.net.addLink(self.hosts[c], self.leaves[i-1])
+                j+=1
+                c+=1
+
+            i+=1
+
+    def run(self):
+        """ Runs the created network topology and launches mininet cli"""
+        self.run_silent()
+        CLI(self.net)
+        self.net.stop()
+
+    def run_silent(self):
+        """ Runs silently - for unit testing """
+        self.net.build()
+
+        # Start the switches, configure them with desired protocols and only 
+        # then set the controller
+        for sw in self.spines:
+            sw.start([self.flare])
+            if self.proto:
+                sw.cmd('ovs-vsctl set bridge %(sw)s protocols=%(proto)s' % \
+                           { 'sw': sw.name, 'proto': self.proto})
+            sw.cmdPrint('ovs-vsctl set-controller %(sw)s tcp:%(ctl)s:6633' % \
+                            {'sw': sw.name, 'ctl': self.cip})
+
+        for sw in self.leaves:
+            sw.start([self.flare])
+            sw.cmdPrint('ovs-vsctl set-controller %(sw)s tcp:%(ctl)s:6633' % \
+                            {'sw': sw.name, 'ctl': self.cip})
+
+    def pingAll(self):
+        """ PingAll to create flows - for unit testing """
+        self.net.pingAll()
+
+    def stop(self):
+        "Stops the topology. You should call this after run_silent"
+        self.net.stop()
diff --git a/tools/test/topos/tt.py b/tools/test/topos/tt.py
new file mode 100644
index 0000000..b74d446
--- /dev/null
+++ b/tools/test/topos/tt.py
@@ -0,0 +1,5 @@
+#!/usr/bin/python
+# Launches mininet with Tower topology configuration.
+import sys, tower
+net = tower.Tower(cip=sys.argv[1])
+net.run()
diff --git a/utils/misc/src/main/java/org/onlab/metrics/MetricsFeature.java b/utils/misc/src/main/java/org/onlab/metrics/MetricsFeature.java
index 7a97b08..75c1018 100644
--- a/utils/misc/src/main/java/org/onlab/metrics/MetricsFeature.java
+++ b/utils/misc/src/main/java/org/onlab/metrics/MetricsFeature.java
@@ -11,7 +11,7 @@
      *
      * @param newName name of the Feature
      */
-    MetricsFeature(final String newName) {
+    public MetricsFeature(final String newName) {
         name = newName;
     }
 
diff --git a/utils/misc/src/main/java/org/onlab/metrics/MetricsManager.java b/utils/misc/src/main/java/org/onlab/metrics/MetricsManager.java
index e07d3f9..98e8310 100644
--- a/utils/misc/src/main/java/org/onlab/metrics/MetricsManager.java
+++ b/utils/misc/src/main/java/org/onlab/metrics/MetricsManager.java
@@ -1,7 +1,5 @@
 package org.onlab.metrics;
 
-import java.io.File;
-import java.util.Locale;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
@@ -10,9 +8,11 @@
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+import com.codahale.metrics.ConsoleReporter;
 import com.codahale.metrics.Counter;
-import com.codahale.metrics.CsvReporter;
 import com.codahale.metrics.Gauge;
 import com.codahale.metrics.Histogram;
 import com.codahale.metrics.Meter;
@@ -56,6 +56,7 @@
 @Component(immediate = true)
 public final class MetricsManager implements MetricsService {
 
+    private final Logger log = LoggerFactory.getLogger(getClass());
     /**
      * Registry to hold the Components defined in the system.
      */
@@ -69,27 +70,31 @@
     /**
      * Default Reporter for this metrics manager.
      */
-    private final CsvReporter reporter;
+    //private final Slf4jReporter reporter;
+    private final ConsoleReporter reporter;
 
     public MetricsManager() {
-        this.componentsRegistry = new ConcurrentHashMap<>();
         this.metricsRegistry = new MetricRegistry();
-
-        this.reporter = CsvReporter.forRegistry(metricsRegistry)
-                .formatFor(Locale.US)
+//        this.reporter = Slf4jReporter.forRegistry(this.metricsRegistry)
+//                .outputTo(log)
+//                .convertRatesTo(TimeUnit.SECONDS)
+//                .convertDurationsTo(TimeUnit.NANOSECONDS)
+//                .build();
+        this.reporter = ConsoleReporter.forRegistry(this.metricsRegistry)
                 .convertRatesTo(TimeUnit.SECONDS)
-                .convertDurationsTo(TimeUnit.MICROSECONDS)
-                .build(new File("/tmp/"));
-
-        reporter.start(10, TimeUnit.SECONDS);
+                .convertDurationsTo(TimeUnit.NANOSECONDS)
+                .build();
     }
 
     @Activate
     public void activate() {
+        this.componentsRegistry = new ConcurrentHashMap<>();
+        reporter.start(10, TimeUnit.SECONDS);
     }
 
     @Deactivate
     public void deactivate() {
+        reporter.stop();
     }
 
     /**
diff --git a/utils/misc/src/main/java/org/onlab/packet/MacAddress.java b/utils/misc/src/main/java/org/onlab/packet/MacAddress.java
index 33764bb..814660b 100644
--- a/utils/misc/src/main/java/org/onlab/packet/MacAddress.java
+++ b/utils/misc/src/main/java/org/onlab/packet/MacAddress.java
@@ -22,6 +22,12 @@
  *
  */
 public class MacAddress {
+    public static final byte[] ZERO_MAC_ADDRESS =
+            MacAddress.valueOf("00:00:00:00:00:00").getAddress();
+
+    public static final byte[] BROADCAST_MAC =
+            MacAddress.valueOf("ff:ff:ff:ff:ff:ff").getAddress();
+
     public static final int MAC_ADDRESS_LENGTH = 6;
     private byte[] address = new byte[MacAddress.MAC_ADDRESS_LENGTH];
 
diff --git a/utils/misc/src/main/java/org/onlab/util/NewConcurrentHashMap.java b/utils/misc/src/main/java/org/onlab/util/NewConcurrentHashMap.java
new file mode 100644
index 0000000..bd17867
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/util/NewConcurrentHashMap.java
@@ -0,0 +1,33 @@
+package org.onlab.util;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.commons.lang3.concurrent.ConcurrentException;
+import org.apache.commons.lang3.concurrent.ConcurrentInitializer;
+
+/**
+ * Creates an instance of new ConcurrentHashMap on each {@link #get()} call.
+ * <p>
+ * To be used with
+ * {@link org.apache.commons.lang3.concurrent.ConcurrentUtils#createIfAbsent()
+ *  ConcurrentUtils#createIfAbsent}
+ *
+ * @param <K> ConcurrentHashMap key type
+ * @param <V> ConcurrentHashMap value type
+ */
+public final class NewConcurrentHashMap<K, V>
+    implements  ConcurrentInitializer<ConcurrentMap<K, V>> {
+
+    public static final NewConcurrentHashMap<?, ?> INSTANCE = new NewConcurrentHashMap<>();
+
+    @SuppressWarnings("unchecked")
+    public static <K, V> NewConcurrentHashMap<K, V> ifNeeded() {
+        return (NewConcurrentHashMap<K, V>) INSTANCE;
+    }
+
+    @Override
+    public ConcurrentMap<K, V> get() throws ConcurrentException {
+        return new ConcurrentHashMap<>();
+    }
+}
diff --git a/utils/misc/src/main/java/org/onlab/util/Timer.java b/utils/misc/src/main/java/org/onlab/util/Timer.java
index 276138f..6a3be49 100644
--- a/utils/misc/src/main/java/org/onlab/util/Timer.java
+++ b/utils/misc/src/main/java/org/onlab/util/Timer.java
@@ -8,7 +8,7 @@
  */
 public final class Timer {
 
-    private static HashedWheelTimer timer;
+    private static volatile HashedWheelTimer timer;
 
     // Ban public construction
     private Timer() {
@@ -21,10 +21,17 @@
      */
     public static HashedWheelTimer getTimer() {
         if (Timer.timer == null) {
-            Timer.timer = new HashedWheelTimer();
-            Timer.timer.start();
+            initTimer();
         }
         return Timer.timer;
     }
 
+    private static synchronized  void initTimer() {
+        if (Timer.timer == null) {
+            HashedWheelTimer hwTimer = new HashedWheelTimer();
+            hwTimer.start();
+            Timer.timer = hwTimer;
+        }
+    }
+
 }
diff --git a/utils/misc/src/main/java/org/onlab/util/Tools.java b/utils/misc/src/main/java/org/onlab/util/Tools.java
index c5162f6..5643098 100644
--- a/utils/misc/src/main/java/org/onlab/util/Tools.java
+++ b/utils/misc/src/main/java/org/onlab/util/Tools.java
@@ -4,6 +4,12 @@
 import com.google.common.primitives.UnsignedLongs;
 import com.google.common.util.concurrent.ThreadFactoryBuilder;
 
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.ThreadFactory;
 
 public abstract class Tools {
@@ -66,4 +72,24 @@
         }
     }
 
+    /**
+     * Slurps the contents of a file into a list of strings, one per line.
+     *
+     * @param path file path
+     * @return file contents
+     */
+    public static List<String> slurp(File path) {
+        try (BufferedReader br = new BufferedReader(new FileReader(path))) {
+            List<String> lines = new ArrayList<>();
+            String line;
+            while ((line = br.readLine()) != null) {
+                lines.add(line);
+            }
+            return lines;
+
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
 }
diff --git a/utils/netty/src/main/java/org/onlab/netty/AsyncResponse.java b/utils/netty/src/main/java/org/onlab/netty/AsyncResponse.java
index b2b490e..1772a3c 100644
--- a/utils/netty/src/main/java/org/onlab/netty/AsyncResponse.java
+++ b/utils/netty/src/main/java/org/onlab/netty/AsyncResponse.java
@@ -8,17 +8,16 @@
  * This class provides a base implementation of Response, with methods to retrieve the
  * result and query to see if the result is ready. The result can only be retrieved when
  * it is ready and the get methods will block if the result is not ready yet.
- * @param <T> type of response.
  */
-public class AsyncResponse<T> implements Response<T> {
+public class AsyncResponse implements Response {
 
-    private T value;
+    private byte[] value;
     private boolean done = false;
     private final long start = System.nanoTime();
 
     @Override
-    public T get(long timeout, TimeUnit tu) throws TimeoutException {
-        timeout = tu.toNanos(timeout);
+    public byte[] get(long timeout, TimeUnit timeUnit) throws TimeoutException {
+        timeout = timeUnit.toNanos(timeout);
         boolean interrupted = false;
         try {
             synchronized (this) {
@@ -43,7 +42,7 @@
     }
 
     @Override
-    public T get() throws InterruptedException {
+    public byte[] get() throws InterruptedException {
         throw new UnsupportedOperationException();
     }
 
@@ -57,11 +56,10 @@
      * available.
      * @param data response data.
      */
-    @SuppressWarnings("unchecked")
-    public synchronized void setResponse(Object data) {
+    public synchronized void setResponse(byte[] data) {
         if (!done) {
             done = true;
-            value = (T) data;
+            value = data;
             this.notifyAll();
         }
     }
diff --git a/utils/netty/src/main/java/org/onlab/netty/DecoderState.java b/utils/netty/src/main/java/org/onlab/netty/DecoderState.java
new file mode 100644
index 0000000..638fe6c
--- /dev/null
+++ b/utils/netty/src/main/java/org/onlab/netty/DecoderState.java
@@ -0,0 +1,12 @@
+package org.onlab.netty;
+
+/**
+ * State transitions a decoder goes through as it is decoding an incoming message.
+ */
+public enum DecoderState {
+    READ_HEADER_VERSION,
+    READ_PREAMBLE,
+    READ_CONTENT_LENGTH,
+    READ_SERIALIZER_VERSION,
+    READ_CONTENT
+}
diff --git a/utils/netty/src/main/java/org/onlab/netty/EchoHandler.java b/utils/netty/src/main/java/org/onlab/netty/EchoHandler.java
index 313a448..b038db8 100644
--- a/utils/netty/src/main/java/org/onlab/netty/EchoHandler.java
+++ b/utils/netty/src/main/java/org/onlab/netty/EchoHandler.java
@@ -2,14 +2,20 @@
 
 import java.io.IOException;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+//FIXME: Should be move out to test or app
 /**
  * Message handler that echos the message back to the sender.
  */
 public class EchoHandler implements MessageHandler {
 
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
     @Override
     public void handle(Message message) throws IOException {
-        System.out.println("Received: " + message.payload() + ". Echoing it back to the sender.");
+        log.info("Received message. Echoing it back to the sender.");
         message.respond(message.payload());
     }
 }
diff --git a/utils/netty/src/main/java/org/onlab/netty/Endpoint.java b/utils/netty/src/main/java/org/onlab/netty/Endpoint.java
index 8681093..660f2b9 100644
--- a/utils/netty/src/main/java/org/onlab/netty/Endpoint.java
+++ b/utils/netty/src/main/java/org/onlab/netty/Endpoint.java
@@ -1,5 +1,9 @@
 package org.onlab.netty;
 
+import java.util.Objects;
+
+import com.google.common.base.MoreObjects;
+
 /**
  * Representation of a TCP/UDP communication end point.
  */
@@ -8,6 +12,15 @@
     private final int port;
     private final String host;
 
+    /**
+     * Used for serialization.
+     */
+    @SuppressWarnings("unused")
+    private Endpoint() {
+        port = 0;
+        host = null;
+    }
+
     public Endpoint(String host, int port) {
         this.host = host;
         this.port = port;
@@ -23,16 +36,15 @@
 
     @Override
     public String toString() {
-        return "Endpoint [port=" + port + ", host=" + host + "]";
+        return MoreObjects.toStringHelper(getClass())
+                .add("port", port)
+                .add("host", host)
+                .toString();
     }
 
     @Override
     public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result + ((host == null) ? 0 : host.hashCode());
-        result = prime * result + port;
-        return result;
+        return Objects.hash(host, port);
     }
 
     @Override
@@ -46,17 +58,8 @@
         if (getClass() != obj.getClass()) {
             return false;
         }
-        Endpoint other = (Endpoint) obj;
-        if (host == null) {
-            if (other.host != null) {
-                return false;
-            }
-        } else if (!host.equals(other.host)) {
-            return false;
-        }
-        if (port != other.port) {
-            return false;
-        }
-        return true;
+        Endpoint that = (Endpoint) obj;
+        return Objects.equals(this.port, that.port) &&
+               Objects.equals(this.host, that.host);
     }
 }
diff --git a/utils/netty/src/main/java/org/onlab/netty/InternalMessage.java b/utils/netty/src/main/java/org/onlab/netty/InternalMessage.java
index bcf6f52..938ec7b 100644
--- a/utils/netty/src/main/java/org/onlab/netty/InternalMessage.java
+++ b/utils/netty/src/main/java/org/onlab/netty/InternalMessage.java
@@ -8,12 +8,13 @@
  */
 public final class InternalMessage implements Message {
 
+    public static final String REPLY_MESSAGE_TYPE = "NETTY_MESSAGIG_REQUEST_REPLY";
+
     private long id;
     private Endpoint sender;
     private String type;
-    private Object payload;
+    private byte[] payload;
     private transient NettyMessagingService messagingService;
-    public static final String REPLY_MESSAGE_TYPE = "NETTY_MESSAGIG_REQUEST_REPLY";
 
     // Must be created using the Builder.
     private InternalMessage() {}
@@ -31,12 +32,16 @@
     }
 
     @Override
-    public Object payload() {
+    public byte[] payload() {
         return payload;
     }
 
+    protected void setMessagingService(NettyMessagingService messagingService) {
+        this.messagingService = messagingService;
+    }
+
     @Override
-    public void respond(Object data) throws IOException {
+    public void respond(byte[] data) throws IOException {
         Builder builder = new Builder(messagingService);
         InternalMessage message = builder.withId(this.id)
              // FIXME: Sender should be messagingService.localEp.
@@ -51,7 +56,7 @@
     /**
      * Builder for InternalMessages.
      */
-    public static class Builder {
+    public static final class Builder {
         private InternalMessage message;
 
         public Builder(NettyMessagingService messagingService) {
@@ -73,7 +78,7 @@
             message.sender = sender;
             return this;
         }
-        public Builder withPayload(Object payload) {
+        public Builder withPayload(byte[] payload) {
             message.payload = payload;
             return this;
         }
diff --git a/utils/netty/src/main/java/org/onlab/netty/KryoSerializer.java b/utils/netty/src/main/java/org/onlab/netty/KryoSerializer.java
index 73c01a0..b8efb51 100644
--- a/utils/netty/src/main/java/org/onlab/netty/KryoSerializer.java
+++ b/utils/netty/src/main/java/org/onlab/netty/KryoSerializer.java
@@ -1,18 +1,16 @@
 package org.onlab.netty;
 
 import org.onlab.util.KryoPool;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.HashMap;
 
+//FIXME: Should be move out to test or app
 /**
  * Kryo Serializer.
  */
-public class KryoSerializer implements Serializer {
-
-    private final Logger log = LoggerFactory.getLogger(getClass());
+public class KryoSerializer {
 
     private KryoPool serializerPool;
 
@@ -28,20 +26,29 @@
         serializerPool = KryoPool.newBuilder()
                 .register(ArrayList.class,
                           HashMap.class,
-                          ArrayList.class
+                          ArrayList.class,
+                          InternalMessage.class,
+                          Endpoint.class,
+                          byte[].class
                 )
                 .build()
                 .populate(1);
     }
 
 
-    @Override
-    public Object decode(byte[] data) {
+    public <T> T decode(byte[] data) {
         return serializerPool.deserialize(data);
     }
 
-    @Override
     public byte[] encode(Object payload) {
         return serializerPool.serialize(payload);
     }
+
+    public <T> T decode(ByteBuffer buffer) {
+        return serializerPool.deserialize(buffer);
+    }
+
+    public void encode(Object obj, ByteBuffer buffer) {
+        serializerPool.serialize(obj, buffer);
+    }
 }
diff --git a/utils/netty/src/main/java/org/onlab/netty/LoggingHandler.java b/utils/netty/src/main/java/org/onlab/netty/LoggingHandler.java
index ed6cdb4..366898b 100644
--- a/utils/netty/src/main/java/org/onlab/netty/LoggingHandler.java
+++ b/utils/netty/src/main/java/org/onlab/netty/LoggingHandler.java
@@ -1,12 +1,17 @@
 package org.onlab.netty;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 /**
  * A MessageHandler that simply logs the information.
  */
 public class LoggingHandler implements MessageHandler {
 
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
     @Override
     public void handle(Message message) {
-        System.out.println("Received: " + message.payload());
+        log.info("Received message. Payload has {} bytes", message.payload().length);
     }
 }
diff --git a/utils/netty/src/main/java/org/onlab/netty/Message.java b/utils/netty/src/main/java/org/onlab/netty/Message.java
index 54b9526..87a8bb6 100644
--- a/utils/netty/src/main/java/org/onlab/netty/Message.java
+++ b/utils/netty/src/main/java/org/onlab/netty/Message.java
@@ -12,12 +12,12 @@
      * Returns the payload of this message.
      * @return message payload.
      */
-    public Object payload();
+    public byte[] payload();
 
     /**
-     * Sends a reply back to the sender of this messge.
+     * Sends a reply back to the sender of this message.
      * @param data payload of the response.
      * @throws IOException if there is a communication error.
      */
-    public void respond(Object data) throws IOException;
+    public void respond(byte[] data) throws IOException;
 }
diff --git a/utils/netty/src/main/java/org/onlab/netty/MessageDecoder.java b/utils/netty/src/main/java/org/onlab/netty/MessageDecoder.java
index ecf2d62..b494ce9 100644
--- a/utils/netty/src/main/java/org/onlab/netty/MessageDecoder.java
+++ b/utils/netty/src/main/java/org/onlab/netty/MessageDecoder.java
@@ -1,58 +1,71 @@
 package org.onlab.netty;
 
+import static com.google.common.base.Preconditions.checkState;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ReplayingDecoder;
+
 import java.util.Arrays;
 import java.util.List;
 
-import static com.google.common.base.Preconditions.checkState;
-
-import io.netty.buffer.ByteBuf;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.handler.codec.ByteToMessageDecoder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
- * Decode bytes into a InternalMessage.
+ * Decoder for inbound messages.
  */
-public class MessageDecoder extends ByteToMessageDecoder {
+public class MessageDecoder extends ReplayingDecoder<DecoderState> {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
 
     private final NettyMessagingService messagingService;
-    private final Serializer serializer;
 
-    public MessageDecoder(NettyMessagingService messagingService, Serializer serializer) {
+    private static final KryoSerializer SERIALIZER = new KryoSerializer();
+
+    private int contentLength;
+
+    public MessageDecoder(NettyMessagingService messagingService) {
+        super(DecoderState.READ_HEADER_VERSION);
         this.messagingService = messagingService;
-        this.serializer = serializer;
     }
 
     @Override
-    protected void decode(ChannelHandlerContext context, ByteBuf in,
-            List<Object> messages) throws Exception {
+    protected void decode(
+            ChannelHandlerContext context,
+            ByteBuf buffer,
+            List<Object> out) throws Exception {
 
-        byte[] preamble = in.readBytes(MessageEncoder.PREAMBLE.length).array();
-        checkState(Arrays.equals(MessageEncoder.PREAMBLE, preamble), "Message has wrong preamble");
+        switch(state()) {
+        case READ_HEADER_VERSION:
+            int headerVersion = buffer.readInt();
+            checkState(headerVersion == MessageEncoder.HEADER_VERSION, "Unexpected header version");
+            checkpoint(DecoderState.READ_PREAMBLE);
+        case READ_PREAMBLE:
+            byte[] preamble = new byte[MessageEncoder.PREAMBLE.length];
+            buffer.readBytes(preamble);
+            checkState(Arrays.equals(MessageEncoder.PREAMBLE, preamble), "Message has wrong preamble");
+            checkpoint(DecoderState.READ_CONTENT_LENGTH);
+        case READ_CONTENT_LENGTH:
+            contentLength = buffer.readInt();
+            checkpoint(DecoderState.READ_SERIALIZER_VERSION);
+        case READ_SERIALIZER_VERSION:
+            int serializerVersion = buffer.readInt();
+            checkState(serializerVersion == MessageEncoder.SERIALIZER_VERSION, "Unexpected serializer version");
+            checkpoint(DecoderState.READ_CONTENT);
+        case READ_CONTENT:
+            InternalMessage message = SERIALIZER.decode(buffer.readBytes(contentLength).nioBuffer());
+            message.setMessagingService(messagingService);
+            out.add(message);
+            checkpoint(DecoderState.READ_HEADER_VERSION);
+            break;
+         default:
+            checkState(false, "Must not be here");
+        }
+    }
 
-        // read message Id.
-        long id = in.readLong();
-
-        // read message type; first read size and then bytes.
-        String type = new String(in.readBytes(in.readInt()).array());
-
-        // read sender host name; first read size and then bytes.
-        String host = new String(in.readBytes(in.readInt()).array());
-
-        // read sender port.
-        int port = in.readInt();
-
-        Endpoint sender = new Endpoint(host, port);
-
-        // read message payload; first read size and then bytes.
-        Object payload = serializer.decode(in.readBytes(in.readInt()).array());
-
-        InternalMessage message = new InternalMessage.Builder(messagingService)
-                .withId(id)
-                .withSender(sender)
-                .withType(type)
-                .withPayload(payload)
-                .build();
-
-        messages.add(message);
+    @Override
+    public void exceptionCaught(ChannelHandlerContext context, Throwable cause) {
+        log.error("Exception inside channel handling pipeline.", cause);
+        context.close();
     }
 }
diff --git a/utils/netty/src/main/java/org/onlab/netty/MessageEncoder.java b/utils/netty/src/main/java/org/onlab/netty/MessageEncoder.java
index 1b52a0f..d026dec 100644
--- a/utils/netty/src/main/java/org/onlab/netty/MessageEncoder.java
+++ b/utils/netty/src/main/java/org/onlab/netty/MessageEncoder.java
@@ -1,60 +1,56 @@
 package org.onlab.netty;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandler.Sharable;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.handler.codec.MessageToByteEncoder;
 
 /**
  * Encode InternalMessage out into a byte buffer.
  */
+@Sharable
 public class MessageEncoder extends MessageToByteEncoder<InternalMessage> {
 
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
     // onosiscool in ascii
     public static final byte[] PREAMBLE = "onosiscool".getBytes();
+    public static final int HEADER_VERSION = 1;
+    public static final int SERIALIZER_VERSION = 1;
 
-    private final Serializer serializer;
 
-    public MessageEncoder(Serializer serializer) {
-        this.serializer = serializer;
-    }
+    private static final KryoSerializer SERIALIZER = new KryoSerializer();
 
     @Override
-    protected void encode(ChannelHandlerContext context, InternalMessage message,
+    protected void encode(
+            ChannelHandlerContext context,
+            InternalMessage message,
             ByteBuf out) throws Exception {
 
+        // write version
+        out.writeInt(HEADER_VERSION);
+
         // write preamble
         out.writeBytes(PREAMBLE);
 
-        // write id
-        out.writeLong(message.id());
+        byte[] payload = SERIALIZER.encode(message);
 
-        // write type length
-        out.writeInt(message.type().length());
-
-        // write type
-        out.writeBytes(message.type().getBytes());
-
-        // write sender host name size
-        out.writeInt(message.sender().host().length());
-
-        // write sender host name.
-        out.writeBytes(message.sender().host().getBytes());
-
-        // write port
-        out.writeInt(message.sender().port());
-
-        try {
-            serializer.encode(message.payload());
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-
-        byte[] payload = serializer.encode(message.payload());
-
-        // write payload length.
+        // write payload length
         out.writeInt(payload.length);
 
-        // write payload bytes
+        // write payloadSerializer version
+        out.writeInt(SERIALIZER_VERSION);
+
+        // write payload.
         out.writeBytes(payload);
     }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext context, Throwable cause) {
+        log.error("Exception inside channel handling pipeline.", cause);
+        context.close();
+    }
 }
diff --git a/utils/netty/src/main/java/org/onlab/netty/MessagingService.java b/utils/netty/src/main/java/org/onlab/netty/MessagingService.java
index ebad442..08676ac 100644
--- a/utils/netty/src/main/java/org/onlab/netty/MessagingService.java
+++ b/utils/netty/src/main/java/org/onlab/netty/MessagingService.java
@@ -11,10 +11,10 @@
      * The message is specified using the type and payload.
      * @param ep end point to send the message to.
      * @param type type of message.
-     * @param payload message payload.
+     * @param payload message payload bytes.
      * @throws IOException
      */
-    public void sendAsync(Endpoint ep, String type, Object payload) throws IOException;
+    public void sendAsync(Endpoint ep, String type, byte[] payload) throws IOException;
 
     /**
      * Sends a message synchronously and waits for a response.
@@ -24,7 +24,7 @@
      * @return a response future
      * @throws IOException
      */
-    public <T> Response<T> sendAndReceive(Endpoint ep, String type, Object payload) throws IOException;
+    public Response sendAndReceive(Endpoint ep, String type, byte[] payload) throws IOException;
 
     /**
      * Registers a new message handler for message type.
@@ -38,4 +38,4 @@
      * @param type message type
      */
     public void unregisterHandler(String type);
-}
+}
\ No newline at end of file
diff --git a/utils/netty/src/main/java/org/onlab/netty/NettyMessagingService.java b/utils/netty/src/main/java/org/onlab/netty/NettyMessagingService.java
index 54da8cc..5a51ad4 100644
--- a/utils/netty/src/main/java/org/onlab/netty/NettyMessagingService.java
+++ b/utils/netty/src/main/java/org/onlab/netty/NettyMessagingService.java
@@ -11,6 +11,7 @@
 import io.netty.buffer.PooledByteBufAllocator;
 import io.netty.channel.Channel;
 import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandler;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.ChannelInitializer;
 import io.netty.channel.ChannelOption;
@@ -22,7 +23,6 @@
 import io.netty.channel.socket.nio.NioSocketChannel;
 
 import org.apache.commons.lang.math.RandomUtils;
-import org.apache.commons.pool.KeyedObjectPool;
 import org.apache.commons.pool.KeyedPoolableObjectFactory;
 import org.apache.commons.pool.impl.GenericKeyedObjectPool;
 import org.slf4j.Logger;
@@ -38,16 +38,19 @@
 
     private final Logger log = LoggerFactory.getLogger(getClass());
 
-    private KeyedObjectPool<Endpoint, Channel> channels =
-            new GenericKeyedObjectPool<Endpoint, Channel>(new OnosCommunicationChannelFactory());
     private final int port;
+    private final Endpoint localEp;
     private final EventLoopGroup bossGroup = new NioEventLoopGroup();
     private final EventLoopGroup workerGroup = new NioEventLoopGroup();
     private final ConcurrentMap<String, MessageHandler> handlers = new ConcurrentHashMap<>();
-    private Cache<Long, AsyncResponse<?>> responseFutures;
-    private final Endpoint localEp;
-
-    protected Serializer serializer;
+    private final Cache<Long, AsyncResponse> responseFutures = CacheBuilder.newBuilder()
+            .maximumSize(100000)
+            .weakValues()
+            // TODO: Once the entry expires, notify blocking threads (if any).
+            .expireAfterWrite(10, TimeUnit.MINUTES)
+            .build();
+    private final GenericKeyedObjectPool<Endpoint, Channel> channels
+            = new GenericKeyedObjectPool<Endpoint, Channel>(new OnosCommunicationChannelFactory());
 
     public NettyMessagingService() {
         // TODO: Default port should be configurable.
@@ -66,12 +69,8 @@
     }
 
     public void activate() throws Exception {
-        responseFutures = CacheBuilder.newBuilder()
-                .maximumSize(100000)
-                .weakValues()
-                // TODO: Once the entry expires, notify blocking threads (if any).
-                .expireAfterWrite(10, TimeUnit.MINUTES)
-                .build();
+        channels.setTestOnBorrow(true);
+        channels.setTestOnReturn(true);
         startAcceptingConnections();
     }
 
@@ -82,7 +81,7 @@
     }
 
     @Override
-    public void sendAsync(Endpoint ep, String type, Object payload) throws IOException {
+    public void sendAsync(Endpoint ep, String type, byte[] payload) throws IOException {
         InternalMessage message = new InternalMessage.Builder(this)
             .withId(RandomUtils.nextLong())
             .withSender(localEp)
@@ -95,24 +94,21 @@
     protected void sendAsync(Endpoint ep, InternalMessage message) throws IOException {
         Channel channel = null;
         try {
-            channel = channels.borrowObject(ep);
-            channel.eventLoop().execute(new WriteTask(channel, message));
+            try {
+                channel = channels.borrowObject(ep);
+                channel.eventLoop().execute(new WriteTask(channel, message));
+            } finally {
+                channels.returnObject(ep, channel);
+            }
         } catch (Exception e) {
             throw new IOException(e);
-        } finally {
-            try {
-                channels.returnObject(ep, channel);
-            } catch (Exception e) {
-                log.warn("Error returning object back to the pool", e);
-                // ignored.
-            }
         }
     }
 
     @Override
-    public <T> Response<T> sendAndReceive(Endpoint ep, String type, Object payload)
+    public Response sendAndReceive(Endpoint ep, String type, byte[] payload)
             throws IOException {
-        AsyncResponse<T> futureResponse = new AsyncResponse<T>();
+        AsyncResponse futureResponse = new AsyncResponse();
         Long messageId = RandomUtils.nextLong();
         responseFutures.put(messageId, futureResponse);
         InternalMessage message = new InternalMessage.Builder(this)
@@ -141,6 +137,9 @@
 
     private void startAcceptingConnections() throws InterruptedException {
         ServerBootstrap b = new ServerBootstrap();
+        b.option(ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK, 32 * 1024);
+        b.option(ChannelOption.WRITE_BUFFER_LOW_WATER_MARK, 8 * 1024);
+        // TODO: Need JVM options to configure PooledByteBufAllocator.
         b.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
         b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
@@ -167,17 +166,18 @@
 
         @Override
         public Channel makeObject(Endpoint ep) throws Exception {
-            Bootstrap b = new Bootstrap();
-            b.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
-            b.group(workerGroup);
+            Bootstrap bootstrap = new Bootstrap();
+            bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
+            bootstrap.option(ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK, 32 * 1024);
+            bootstrap.option(ChannelOption.WRITE_BUFFER_LOW_WATER_MARK, 8 * 1024);
+            bootstrap.group(workerGroup);
             // TODO: Make this faster:
             // http://normanmaurer.me/presentations/2014-facebook-eng-netty/slides.html#37.0
-            b.channel(NioSocketChannel.class);
-            b.option(ChannelOption.SO_KEEPALIVE, true);
-            b.handler(new OnosCommunicationChannelInitializer());
-
+            bootstrap.channel(NioSocketChannel.class);
+            bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
+            bootstrap.handler(new OnosCommunicationChannelInitializer());
             // Start the client.
-            ChannelFuture f = b.connect(ep.host(), ep.port()).sync();
+            ChannelFuture f = bootstrap.connect(ep.host(), ep.port()).sync();
             return f.channel();
         }
 
@@ -194,31 +194,35 @@
 
     private class OnosCommunicationChannelInitializer extends ChannelInitializer<SocketChannel> {
 
+        private final ChannelHandler dispatcher = new InboundMessageDispatcher();
+        private final ChannelHandler encoder = new MessageEncoder();
+
         @Override
         protected void initChannel(SocketChannel channel) throws Exception {
             channel.pipeline()
-                .addLast(new MessageEncoder(serializer))
-                .addLast(new MessageDecoder(NettyMessagingService.this, serializer))
-                .addLast(new NettyMessagingService.InboundMessageDispatcher());
+                .addLast("encoder", encoder)
+                .addLast("decoder", new MessageDecoder(NettyMessagingService.this))
+                .addLast("handler", dispatcher);
         }
     }
 
     private class WriteTask implements Runnable {
 
-        private final Object message;
+        private final InternalMessage message;
         private final Channel channel;
 
-        public WriteTask(Channel channel, Object message) {
-            this.message = message;
+        public WriteTask(Channel channel, InternalMessage message) {
             this.channel = channel;
+            this.message = message;
         }
 
         @Override
         public void run() {
-            channel.writeAndFlush(message);
+            channel.writeAndFlush(message, channel.voidPromise());
         }
     }
 
+    @ChannelHandler.Sharable
     private class InboundMessageDispatcher extends SimpleChannelInboundHandler<InternalMessage> {
 
         @Override
@@ -226,12 +230,13 @@
             String type = message.type();
             if (type.equals(InternalMessage.REPLY_MESSAGE_TYPE)) {
                 try {
-                    AsyncResponse<?> futureResponse =
+                    AsyncResponse futureResponse =
                         NettyMessagingService.this.responseFutures.getIfPresent(message.id());
                     if (futureResponse != null) {
                         futureResponse.setResponse(message.payload());
+                    } else {
+                        log.warn("Received a reply. But was unable to locate the request handle");
                     }
-                    log.warn("Received a reply. But was unable to locate the request handle");
                 } finally {
                     NettyMessagingService.this.responseFutures.invalidate(message.id());
                 }
@@ -240,5 +245,11 @@
             MessageHandler handler = NettyMessagingService.this.getMessageHandler(type);
             handler.handle(message);
         }
+
+        @Override
+        public void exceptionCaught(ChannelHandlerContext context, Throwable cause) {
+            log.error("Exception inside channel handling pipeline.", cause);
+            context.close();
+        }
     }
 }
diff --git a/utils/netty/src/main/java/org/onlab/netty/Response.java b/utils/netty/src/main/java/org/onlab/netty/Response.java
index 04675ce..150755e 100644
--- a/utils/netty/src/main/java/org/onlab/netty/Response.java
+++ b/utils/netty/src/main/java/org/onlab/netty/Response.java
@@ -7,26 +7,24 @@
  * Response object returned when making synchronous requests.
  * Can you used to check is a response is ready and/or wait for a response
  * to become available.
- *
- * @param <T> type of response.
  */
-public interface Response<T> {
+public interface Response {
 
     /**
      * Gets the response waiting for a designated timeout period.
      * @param timeout timeout period (since request was sent out)
      * @param tu unit of time.
-     * @return response
+     * @return response payload
      * @throws TimeoutException if the timeout expires before the response arrives.
      */
-    public T get(long timeout, TimeUnit tu) throws TimeoutException;
+    public byte[] get(long timeout, TimeUnit tu) throws TimeoutException;
 
     /**
      * Gets the response waiting for indefinite timeout period.
-     * @return response
+     * @return response payload
      * @throws InterruptedException if the thread is interrupted before the response arrives.
      */
-    public T get() throws InterruptedException;
+    public byte[] get() throws InterruptedException;
 
     /**
      * Checks if the response is ready without blocking.
diff --git a/utils/netty/src/main/java/org/onlab/netty/Serializer.java b/utils/netty/src/main/java/org/onlab/netty/Serializer.java
deleted file mode 100644
index ac55f5a..0000000
--- a/utils/netty/src/main/java/org/onlab/netty/Serializer.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package org.onlab.netty;
-
-/**
- * Interface for encoding/decoding message payloads.
- */
-public interface Serializer {
-
-    /**
-     * Decodes the specified byte array to a POJO.
-     *
-     * @param data byte array.
-     * @return POJO
-     */
-    Object decode(byte[] data);
-
-    /**
-     * Encodes the specified POJO into a byte array.
-     *
-     * @param data POJO to be encoded
-     * @return byte array.
-     */
-    byte[] encode(Object message);
-
-}
diff --git a/utils/netty/src/main/java/org/onlab/netty/SimpleClient.java b/utils/netty/src/main/java/org/onlab/netty/SimpleClient.java
deleted file mode 100644
index 1573780..0000000
--- a/utils/netty/src/main/java/org/onlab/netty/SimpleClient.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package org.onlab.netty;
-
-import java.util.concurrent.TimeUnit;
-
-public final class SimpleClient {
-    private SimpleClient() {}
-
-    public static void main(String... args) throws Exception {
-        NettyMessagingService messaging = new TestNettyMessagingService(9081);
-        messaging.activate();
-
-        messaging.sendAsync(new Endpoint("localhost", 8080), "simple", "Hello World");
-        Response<String> response = messaging.sendAndReceive(new Endpoint("localhost", 8080), "echo", "Hello World");
-        System.out.println("Got back:" + response.get(2, TimeUnit.SECONDS));
-    }
-
-    public static class TestNettyMessagingService extends NettyMessagingService {
-        public TestNettyMessagingService(int port) throws Exception {
-            super(port);
-            Serializer serializer = new KryoSerializer();
-            this.serializer = serializer;
-        }
-    }
-}
diff --git a/utils/netty/src/main/java/org/onlab/netty/SimpleServer.java b/utils/netty/src/main/java/org/onlab/netty/SimpleServer.java
deleted file mode 100644
index 12fa025..0000000
--- a/utils/netty/src/main/java/org/onlab/netty/SimpleServer.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package org.onlab.netty;
-
-public final class SimpleServer {
-    private SimpleServer() {}
-
-    public static void main(String... args) throws Exception {
-        NettyMessagingService server = new TestNettyMessagingService();
-        server.activate();
-        server.registerHandler("simple", new LoggingHandler());
-        server.registerHandler("echo", new EchoHandler());
-    }
-
-    public static class TestNettyMessagingService extends NettyMessagingService {
-        protected TestNettyMessagingService() {
-            Serializer serializer = new KryoSerializer();
-            this.serializer = serializer;
-        }
-    }
-}
diff --git a/utils/netty/src/main/java/org/onlab/netty/package-info.java b/utils/netty/src/main/java/org/onlab/netty/package-info.java
index b1b90a3..fee7b04 100644
--- a/utils/netty/src/main/java/org/onlab/netty/package-info.java
+++ b/utils/netty/src/main/java/org/onlab/netty/package-info.java
@@ -1,4 +1,4 @@
 /**
  * Asynchronous messaging APIs implemented using the Netty framework.
  */
-package org.onlab.netty;
\ No newline at end of file
+package org.onlab.netty;
diff --git a/utils/netty/src/test/java/org/onlab/netty/PingPongTest.java b/utils/netty/src/test/java/org/onlab/netty/PingPongTest.java
new file mode 100644
index 0000000..36d2a1e
--- /dev/null
+++ b/utils/netty/src/test/java/org/onlab/netty/PingPongTest.java
@@ -0,0 +1,30 @@
+package org.onlab.netty;
+
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang3.RandomUtils;
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+/**
+ * Simple ping-pong test that exercises NettyMessagingService.
+ */
+public class PingPongTest {
+
+    @Test
+    public void testPingPong() throws Exception {
+        NettyMessagingService pinger = new NettyMessagingService(8085);
+        NettyMessagingService ponger = new NettyMessagingService(9086);
+        try {
+            pinger.activate();
+            ponger.activate();
+            ponger.registerHandler("echo", new EchoHandler());
+            byte[] payload = RandomUtils.nextBytes(100);
+            Response response = pinger.sendAndReceive(new Endpoint("localhost", 9086), "echo", payload);
+            assertArrayEquals(payload, response.get(10000, TimeUnit.MILLISECONDS));
+        } finally {
+            pinger.deactivate();
+            ponger.deactivate();
+        }
+    }
+}