Merge branch 'optical_path_provisioner'

Conflicts:
	core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentManager.java

Change-Id: I3449d508668835307d9b00a87d047599a83de81d
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
index 6f77924..a14c568 100644
--- a/apps/foo/src/main/java/org/onlab/onos/foo/SimpleNettyClient.java
+++ b/apps/foo/src/main/java/org/onlab/onos/foo/SimpleNettyClient.java
@@ -4,6 +4,7 @@
 
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
@@ -12,7 +13,6 @@
 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;
 
@@ -74,10 +74,10 @@
 
         for (int i = 0; i < warmup; i++) {
             messaging.sendAsync(endpoint, "simple", "Hello World".getBytes());
-            Response response = messaging
+            Future<byte[]> responseFuture = messaging
                     .sendAndReceive(endpoint, "echo",
                             "Hello World".getBytes());
-            response.get(100000, TimeUnit.MILLISECONDS);
+            responseFuture.get(100000, TimeUnit.MILLISECONDS);
         }
 
         log.info("measuring round-trip send & receive");
@@ -85,13 +85,13 @@
         int timeouts = 0;
 
         for (int i = 0; i < iterations; i++) {
-            Response response;
+            Future<byte[]> responseFuture;
             Timer.Context context = sendAndReceiveTimer.time();
             try {
-                response = messaging
+                responseFuture = messaging
                         .sendAndReceive(endpoint, "echo",
                                 "Hello World".getBytes());
-                response.get(10000, TimeUnit.MILLISECONDS);
+                responseFuture.get(10000, TimeUnit.MILLISECONDS);
             } catch (TimeoutException e) {
                 timeouts++;
                 log.info("timeout:" + timeouts + " at iteration:" + i);
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 0300079..c3fd6ed 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
@@ -201,7 +201,7 @@
         @Override
         public TrafficTreatment build() {
 
-            //If we are dropping should we just return an emptry list?
+            //If we are dropping should we just return an empty list?
             List<Instruction> instructions = new LinkedList<Instruction>();
             instructions.addAll(modifications);
             instructions.addAll(groups);
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 11bd4ad..736dc9d 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
@@ -20,7 +20,6 @@
 
 import java.util.concurrent.Future;
 
-import org.onlab.onos.ApplicationId;
 import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.store.Store;
 
@@ -53,14 +52,6 @@
     Iterable<FlowEntry> getFlowEntries(DeviceId deviceId);
 
     /**
-     * Returns the flow entries associated with an application.
-     *
-     * @param appId the application id
-     * @return the flow entries
-     */
-    Iterable<FlowRule> getFlowRulesByAppId(ApplicationId appId);
-
-    /**
      // TODO: Better description of method behavior.
      * Stores a new flow rule without generating events.
      *
diff --git a/core/api/src/main/java/org/onlab/onos/net/host/InterfaceIpAddress.java b/core/api/src/main/java/org/onlab/onos/net/host/InterfaceIpAddress.java
new file mode 100644
index 0000000..afdfdde
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/host/InterfaceIpAddress.java
@@ -0,0 +1,157 @@
+package org.onlab.onos.net.host;
+
+import java.util.Objects;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Represents a single IP address information on an interface.
+ *
+ * TODO:
+ *  - Add computation for the default broadcast address if it is not
+ *    specified
+ *  - Add explicit checks that each IP address or prefix belong to the
+ *    same IP version: IPv4/IPv6.
+ *  - Inside the copy constructor we should use copy constructors for each
+ *    field
+ */
+public class InterfaceIpAddress {
+    private final IpAddress ipAddress;
+    private final IpPrefix subnetAddress;
+    private final IpAddress broadcastAddress;
+    private final IpAddress peerAddress;
+
+    /**
+     * Copy constructor.
+     *
+     * @param other the object to copy from
+     */
+    public InterfaceIpAddress(InterfaceIpAddress other) {
+        // TODO: we should use copy constructors for each field
+        this.ipAddress = other.ipAddress;
+        this.subnetAddress = other.subnetAddress;
+        this.broadcastAddress = other.broadcastAddress;
+        this.peerAddress = other.peerAddress;
+    }
+
+    /**
+     * Constructor for a given IP address and a subnet address.
+     *
+     * @param ipAddress the IP address
+     * @param subnetAddress the IP subnet address
+     */
+    public InterfaceIpAddress(IpAddress ipAddress, IpPrefix subnetAddress) {
+        this.ipAddress = checkNotNull(ipAddress);
+        this.subnetAddress = checkNotNull(subnetAddress);
+        // TODO: Recompute the default broadcast address from the subnet
+        // address
+        this.broadcastAddress = null;
+        this.peerAddress = null;
+    }
+
+    /**
+     * Constructor for a given IP address and a subnet address.
+     *
+     * @param ipAddress the IP address
+     * @param subnetAddress the IP subnet address
+     * @param broadcastAddress the IP broadcast address. It can be used
+     * to specify non-default broadcast address
+     */
+    public InterfaceIpAddress(IpAddress ipAddress, IpPrefix subnetAddress,
+                              IpAddress broadcastAddress) {
+        this.ipAddress = checkNotNull(ipAddress);
+        this.subnetAddress = checkNotNull(subnetAddress);
+        this.broadcastAddress = broadcastAddress;
+        this.peerAddress = null;
+    }
+
+    /**
+     * Constructor for a given IP address and a subnet address.
+     *
+     * @param ipAddress the IP address
+     * @param subnetAddress the IP subnet address
+     * @param broadcastAddress the IP broadcast address. It can be used
+     * to specify non-default broadcast address. It should be null for
+     * point-to-point interfaces with a peer address
+     * @param peerAddress the peer IP address for point-to-point interfaces
+     */
+    public InterfaceIpAddress(IpAddress ipAddress, IpPrefix subnetAddress,
+                              IpAddress broadcastAddress,
+                              IpAddress peerAddress) {
+        this.ipAddress = checkNotNull(ipAddress);
+        this.subnetAddress = checkNotNull(subnetAddress);
+        this.broadcastAddress = broadcastAddress;
+        this.peerAddress = peerAddress;
+    }
+
+    /**
+     * Gets the IP address.
+     *
+     * @return the IP address
+     */
+    public IpAddress ipAddress() {
+        return ipAddress;
+    }
+
+    /**
+     * Gets the IP subnet address.
+     *
+     * @return the IP subnet address
+     */
+    public IpPrefix subnetAddress() {
+        return subnetAddress;
+    }
+
+    /**
+     * Gets the subnet IP broadcast address.
+     *
+     * @return the subnet IP broadcast address
+     */
+    public IpAddress broadcastAddress() {
+        return broadcastAddress;
+    }
+
+    /**
+     * Gets the IP point-to-point interface peer address.
+     *
+     * @return the IP point-to-point interface peer address
+     */
+    public IpAddress peerAddress() {
+        return peerAddress;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other == this) {
+            return true;
+        }
+        if (!(other instanceof InterfaceIpAddress)) {
+            return false;
+        }
+        InterfaceIpAddress otherAddr = (InterfaceIpAddress) other;
+
+        return Objects.equals(this.ipAddress, otherAddr.ipAddress)
+            && Objects.equals(this.subnetAddress, otherAddr.subnetAddress)
+            && Objects.equals(this.broadcastAddress,
+                              otherAddr.broadcastAddress)
+            && Objects.equals(this.peerAddress, otherAddr.peerAddress);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(ipAddress, subnetAddress, broadcastAddress,
+                            peerAddress);
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this).add("ipAddress", ipAddress)
+            .add("subnetAddress", subnetAddress)
+            .add("broadcastAddress", broadcastAddress)
+            .add("peerAddress", peerAddress)
+            .omitNullValues().toString();
+    }
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/resource/LinkResourceService.java b/core/api/src/main/java/org/onlab/onos/net/resource/LinkResourceService.java
index 5ae5187..43cc61c 100644
--- a/core/api/src/main/java/org/onlab/onos/net/resource/LinkResourceService.java
+++ b/core/api/src/main/java/org/onlab/onos/net/resource/LinkResourceService.java
@@ -24,6 +24,16 @@
     void releaseResources(LinkResourceAllocations allocations);
 
     /**
+     * Updates previously made allocations with a new resource request.
+     *
+     * @param req            updated resource request
+     * @param oldAllocations old resource allocations
+     * @return new resource allocations
+     */
+    LinkResourceAllocations updateResources(LinkResourceRequest req,
+                                            LinkResourceAllocations oldAllocations);
+
+    /**
      * Returns all allocated resources.
      *
      * @return allocated resources
@@ -61,4 +71,15 @@
      * @return available resources for the target link
      */
     ResourceRequest getAvailableResources(Link link);
+
+    /**
+     * Returns available resources for given link.
+     *
+     * @param link        a target link
+     * @param allocations allocations to be included as available
+     * @return available resources for the target link
+     */
+    ResourceRequest getAvailableResources(Link link,
+                                          LinkResourceAllocations allocations);
+
 }
diff --git a/core/api/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterCommunicationService.java b/core/api/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterCommunicationService.java
index 6fc150c..2cff64a 100644
--- a/core/api/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterCommunicationService.java
+++ b/core/api/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterCommunicationService.java
@@ -5,6 +5,8 @@
 
 import org.onlab.onos.cluster.NodeId;
 
+import com.google.common.util.concurrent.ListenableFuture;
+
 // TODO: remove IOExceptions?
 /**
  * Service for assisting communications between controller cluster nodes.
@@ -40,10 +42,10 @@
      * Sends a message synchronously.
      * @param message message to send
      * @param toNodeId recipient node identifier
-     * @return ClusterMessageResponse which is reply future.
+     * @return reply future.
      * @throws IOException
      */
-    ClusterMessageResponse sendAndReceive(ClusterMessage message, NodeId toNodeId) throws IOException;
+    ListenableFuture<byte[]> sendAndReceive(ClusterMessage message, NodeId toNodeId) throws IOException;
 
     /**
      * Adds a new subscriber for the specified message subject.
diff --git a/core/api/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterMessageResponse.java b/core/api/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterMessageResponse.java
deleted file mode 100644
index d2a0039..0000000
--- a/core/api/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterMessageResponse.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.onlab.onos.store.cluster.messaging;
-
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import org.onlab.onos.cluster.NodeId;
-
-public interface ClusterMessageResponse extends Future<byte[]> {
-
-    public NodeId sender();
-
-    // TODO InterruptedException, ExecutionException removed from original
-    // Future declaration. Revisit if we ever need those.
-    @Override
-    public byte[] get(long timeout, TimeUnit unit) throws TimeoutException;
-
-}
diff --git a/core/api/src/test/java/org/onlab/onos/net/host/DefualtHostDecriptionTest.java b/core/api/src/test/java/org/onlab/onos/net/host/DefaultHostDecriptionTest.java
similarity index 96%
rename from core/api/src/test/java/org/onlab/onos/net/host/DefualtHostDecriptionTest.java
rename to core/api/src/test/java/org/onlab/onos/net/host/DefaultHostDecriptionTest.java
index f2b9475..2cd0edc 100644
--- a/core/api/src/test/java/org/onlab/onos/net/host/DefualtHostDecriptionTest.java
+++ b/core/api/src/test/java/org/onlab/onos/net/host/DefaultHostDecriptionTest.java
@@ -16,7 +16,7 @@
 /**
  * Test for the default host description.
  */
-public class DefualtHostDecriptionTest {
+public class DefaultHostDecriptionTest {
 
     private static final MacAddress MAC = MacAddress.valueOf("00:00:11:00:00:01");
     private static final VlanId VLAN = VlanId.vlanId((short) 10);
diff --git a/core/api/src/test/java/org/onlab/onos/net/host/InterfaceIpAddressTest.java b/core/api/src/test/java/org/onlab/onos/net/host/InterfaceIpAddressTest.java
new file mode 100644
index 0000000..f436eca
--- /dev/null
+++ b/core/api/src/test/java/org/onlab/onos/net/host/InterfaceIpAddressTest.java
@@ -0,0 +1,241 @@
+package org.onlab.onos.net.host;
+
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Tests for class {@link InterfaceIpAddress}.
+ */
+public class InterfaceIpAddressTest {
+    private static final IpAddress IP_ADDRESS = IpAddress.valueOf("1.2.3.4");
+    private static final IpPrefix SUBNET_ADDRESS =
+        IpPrefix.valueOf("1.2.0.0/16");
+    private static final IpAddress BROADCAST_ADDRESS =
+        IpAddress.valueOf("1.2.0.255");         // NOTE: non-default broadcast
+    private static final IpAddress PEER_ADDRESS = IpAddress.valueOf("5.6.7.8");
+
+    private static final IpAddress IP_ADDRESS2 = IpAddress.valueOf("10.2.3.4");
+    private static final IpPrefix SUBNET_ADDRESS2 =
+        IpPrefix.valueOf("10.2.0.0/16");
+    private static final IpAddress BROADCAST_ADDRESS2 =
+        IpAddress.valueOf("10.2.0.255");        // NOTE: non-default broadcast
+    private static final IpAddress PEER_ADDRESS2 =
+        IpAddress.valueOf("50.6.7.8");
+
+    /**
+     * Tests valid class copy constructor.
+     */
+    @Test
+    public void testCopyConstructor() {
+        InterfaceIpAddress fromAddr;
+        InterfaceIpAddress toAddr;
+
+        // Regular interface address with default broadcast address
+        fromAddr = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS);
+        toAddr = new InterfaceIpAddress(fromAddr);
+        assertThat(toAddr.toString(),
+                   is("InterfaceIpAddress{ipAddress=1.2.3.4, subnetAddress=1.2.0.0/16}"));
+
+        // Interface address with non-default broadcast address
+        fromAddr = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS,
+                                          BROADCAST_ADDRESS);
+        toAddr = new InterfaceIpAddress(fromAddr);
+        assertThat(toAddr.toString(),
+                   is("InterfaceIpAddress{ipAddress=1.2.3.4, subnetAddress=1.2.0.0/16, broadcastAddress=1.2.0.255}"));
+
+        // Point-to-point address with peer IP address
+        fromAddr = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS, null,
+                                          PEER_ADDRESS);
+        toAddr = new InterfaceIpAddress(fromAddr);
+        assertThat(toAddr.toString(),
+                   is("InterfaceIpAddress{ipAddress=1.2.3.4, subnetAddress=1.2.0.0/16, peerAddress=5.6.7.8}"));
+    }
+
+    /**
+     * Tests invalid class copy constructor for a null object to copy from.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testInvalidConstructorNullObject() {
+        InterfaceIpAddress fromAddr = null;
+        InterfaceIpAddress toAddr = new InterfaceIpAddress(fromAddr);
+    }
+
+    /**
+     * Tests valid class constructor for regular interface address with
+     * default broadcast address.
+     */
+    @Test
+    public void testConstructorForDefaultBroadcastAddress() {
+        InterfaceIpAddress addr =
+            new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS);
+        assertThat(addr.toString(),
+                   is("InterfaceIpAddress{ipAddress=1.2.3.4, subnetAddress=1.2.0.0/16}"));
+    }
+
+    /**
+     * Tests valid class constructor for interface address with
+     * non-default broadcast address.
+     */
+    @Test
+    public void testConstructorForNonDefaultBroadcastAddress() {
+        InterfaceIpAddress addr =
+            new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS,
+                                   BROADCAST_ADDRESS);
+        assertThat(addr.toString(),
+                   is("InterfaceIpAddress{ipAddress=1.2.3.4, subnetAddress=1.2.0.0/16, broadcastAddress=1.2.0.255}"));
+    }
+
+    /**
+     * Tests valid class constructor for point-to-point interface address with
+     * peer address.
+     */
+    @Test
+    public void testConstructorForPointToPointAddress() {
+        InterfaceIpAddress addr =
+            new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS, null,
+                                   PEER_ADDRESS);
+        assertThat(addr.toString(),
+                   is("InterfaceIpAddress{ipAddress=1.2.3.4, subnetAddress=1.2.0.0/16, peerAddress=5.6.7.8}"));
+    }
+
+    /**
+     * Tests getting the fields of an interface address.
+     */
+    @Test
+    public void testGetFields() {
+        InterfaceIpAddress addr;
+
+        // Regular interface address with default broadcast address
+        addr = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS);
+        assertThat(addr.ipAddress().toString(), is("1.2.3.4"));
+        assertThat(addr.subnetAddress().toString(), is("1.2.0.0/16"));
+        assertThat(addr.broadcastAddress(), is(nullValue()));   // TODO: Fix
+        assertThat(addr.peerAddress(), is(nullValue()));
+
+        // Interface address with non-default broadcast address
+        addr = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS,
+                                      BROADCAST_ADDRESS);
+        assertThat(addr.ipAddress().toString(), is("1.2.3.4"));
+        assertThat(addr.subnetAddress().toString(), is("1.2.0.0/16"));
+        assertThat(addr.broadcastAddress().toString(), is("1.2.0.255"));
+        assertThat(addr.peerAddress(), is(nullValue()));
+
+        // Point-to-point address with peer IP address
+        addr = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS, null,
+                                      PEER_ADDRESS);
+        assertThat(addr.ipAddress().toString(), is("1.2.3.4"));
+        assertThat(addr.subnetAddress().toString(), is("1.2.0.0/16"));
+        assertThat(addr.broadcastAddress(), is(nullValue()));
+        assertThat(addr.peerAddress().toString(), is("5.6.7.8"));
+    }
+
+    /**
+     * Tests equality of {@link InterfaceIpAddress}.
+     */
+    @Test
+    public void testEquality() {
+        InterfaceIpAddress addr1, addr2;
+
+        // Regular interface address with default broadcast address
+        addr1 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS);
+        addr2 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS);
+        assertThat(addr1, is(addr2));
+
+        // Interface address with non-default broadcast address
+        addr1 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS,
+                                       BROADCAST_ADDRESS);
+        addr2 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS,
+                                       BROADCAST_ADDRESS);
+        assertThat(addr1, is(addr2));
+
+        // Point-to-point address with peer IP address
+        addr1 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS, null,
+                                       PEER_ADDRESS);
+        addr2 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS, null,
+                                       PEER_ADDRESS);
+        assertThat(addr1, is(addr2));
+    }
+
+    /**
+     * Tests non-equality of {@link InterfaceIpAddress}.
+     */
+    @Test
+    public void testNonEquality() {
+        InterfaceIpAddress addr1, addr2, addr3, addr4;
+
+        // Regular interface address with default broadcast address
+        addr1 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS);
+        // Interface address with non-default broadcast address
+        addr2 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS,
+                                       BROADCAST_ADDRESS);
+        // Point-to-point address with peer IP address
+        addr3 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS, null,
+                                       PEER_ADDRESS);
+
+        // Test interface addresses with different properties:
+        //  - default-broadcast vs non-default broadcast
+        //   - regular vs point-to-point
+        assertThat(addr1, is(not(addr2)));
+        assertThat(addr1, is(not(addr3)));
+        assertThat(addr2, is(not(addr3)));
+
+        // Test regular interface address with default broadcast address
+        addr4 = new InterfaceIpAddress(IP_ADDRESS2, SUBNET_ADDRESS);
+        assertThat(addr1, is(not(addr4)));
+        addr4 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS2);
+        assertThat(addr1, is(not(addr4)));
+
+        // Test interface address with non-default broadcast address
+        addr4 = new InterfaceIpAddress(IP_ADDRESS2, SUBNET_ADDRESS,
+                                       BROADCAST_ADDRESS);
+        assertThat(addr2, is(not(addr4)));
+        addr4 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS2,
+                                       BROADCAST_ADDRESS);
+        assertThat(addr2, is(not(addr4)));
+        addr4 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS,
+                                       BROADCAST_ADDRESS2);
+        assertThat(addr2, is(not(addr4)));
+
+        // Test point-to-point address with peer IP address
+        addr4 = new InterfaceIpAddress(IP_ADDRESS2, SUBNET_ADDRESS, null,
+                                       PEER_ADDRESS);
+        assertThat(addr3, is(not(addr4)));
+        addr4 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS2, null,
+                                       PEER_ADDRESS);
+        assertThat(addr3, is(not(addr4)));
+        addr4 = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS, null,
+                                       PEER_ADDRESS2);
+        assertThat(addr3, is(not(addr4)));
+    }
+
+    /**
+     * Tests object string representation.
+     */
+    @Test
+    public void testToString() {
+        InterfaceIpAddress addr;
+
+        // Regular interface address with default broadcast address
+        addr = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS);
+        assertThat(addr.toString(),
+                   is("InterfaceIpAddress{ipAddress=1.2.3.4, subnetAddress=1.2.0.0/16}"));
+
+        // Interface address with non-default broadcast address
+        addr = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS,
+                                      BROADCAST_ADDRESS);
+        assertThat(addr.toString(),
+                   is("InterfaceIpAddress{ipAddress=1.2.3.4, subnetAddress=1.2.0.0/16, broadcastAddress=1.2.0.255}"));
+
+        // Point-to-point address with peer IP address
+        addr = new InterfaceIpAddress(IP_ADDRESS, SUBNET_ADDRESS, null,
+                                      PEER_ADDRESS);
+        assertThat(addr.toString(),
+                   is("InterfaceIpAddress{ipAddress=1.2.3.4, subnetAddress=1.2.0.0/16, peerAddress=5.6.7.8}"));
+    }
+}
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 60ab307..0c07bab 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
@@ -50,6 +50,7 @@
 import org.slf4j.Logger;
 
 import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
@@ -116,71 +117,38 @@
 
     @Override
     public void applyFlowRules(FlowRule... flowRules) {
+        Set<FlowRuleBatchEntry> toAddBatchEntries = Sets.newHashSet();
         for (int i = 0; i < flowRules.length; i++) {
-            FlowRule f = flowRules[i];
-            store.storeFlowRule(f);
+            toAddBatchEntries.add(new FlowRuleBatchEntry(FlowRuleOperation.ADD, flowRules[i]));
         }
-    }
-
-    private void applyFlowRulesToProviders(FlowRule... flowRules) {
-        DeviceId did = null;
-        FlowRuleProvider frp = null;
-        for (FlowRule f : flowRules) {
-            if (!f.deviceId().equals(did)) {
-                did = f.deviceId();
-                final Device device = deviceService.getDevice(did);
-                frp = getProvider(device.providerId());
-            }
-            if (frp != null) {
-                frp.applyFlowRule(f);
-            }
-        }
+        applyBatch(new FlowRuleBatchOperation(toAddBatchEntries));
     }
 
     @Override
     public void removeFlowRules(FlowRule... flowRules) {
-        FlowRule f;
+        Set<FlowRuleBatchEntry> toRemoveBatchEntries = Sets.newHashSet();
         for (int i = 0; i < flowRules.length; i++) {
-            f = flowRules[i];
-            store.deleteFlowRule(f);
+            toRemoveBatchEntries.add(new FlowRuleBatchEntry(FlowRuleOperation.REMOVE, flowRules[i]));
         }
-    }
-
-    private void removeFlowRulesFromProviders(FlowRule... flowRules) {
-        DeviceId did = null;
-        FlowRuleProvider frp = null;
-        for (FlowRule f : flowRules) {
-            if (!f.deviceId().equals(did)) {
-                did = f.deviceId();
-                final Device device = deviceService.getDevice(did);
-                frp = getProvider(device.providerId());
-            }
-            if (frp != null) {
-                frp.removeFlowRule(f);
-            }
-        }
+        applyBatch(new FlowRuleBatchOperation(toRemoveBatchEntries));
     }
 
     @Override
     public void removeFlowRulesById(ApplicationId id) {
-        Iterable<FlowRule> rules = getFlowRulesById(id);
-        FlowRuleProvider frp;
-        Device device;
-
-        for (FlowRule f : rules) {
-            store.deleteFlowRule(f);
-            // FIXME: only accept request and push to provider on internal event
-            device = deviceService.getDevice(f.deviceId());
-            frp = getProvider(device.providerId());
-            // FIXME: flows removed from store and flows removed from might diverge
-            //        get rid of #removeRulesById?
-            frp.removeRulesById(id, f);
-        }
+        removeFlowRules(Iterables.toArray(getFlowRulesById(id), FlowRule.class));
     }
 
     @Override
     public Iterable<FlowRule> getFlowRulesById(ApplicationId id) {
-        return store.getFlowRulesByAppId(id);
+        Set<FlowRule> flowEntries = Sets.newHashSet();
+        for (Device d : deviceService.getDevices()) {
+            for (FlowEntry flowEntry : store.getFlowEntries(d.id())) {
+                if (flowEntry.appId() == id.id()) {
+                    flowEntries.add(flowEntry);
+                }
+            }
+        }
+        return flowEntries;
     }
 
     @Override
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
index a877b76..d528ed6 100644
--- 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
@@ -1,8 +1,30 @@
 package org.onlab.onos.net.intent.impl;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
+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.Iterator;
+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.ExecutionException;
+import java.util.concurrent.ExecutorService;
+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;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -29,24 +51,9 @@
 import org.onlab.onos.net.intent.IntentStoreDelegate;
 import org.slf4j.Logger;
 
-import java.util.ArrayList;
-import java.util.Iterator;
-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.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static java.util.concurrent.Executors.newSingleThreadExecutor;
-import static org.onlab.onos.net.intent.IntentState.*;
-import static org.onlab.util.Tools.namedThreads;
-import static org.slf4j.LoggerFactory.getLogger;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
 
 /**
  * An implementation of Intent Manager.
diff --git a/core/net/src/main/java/org/onlab/onos/net/resource/impl/LinkResourceManager.java b/core/net/src/main/java/org/onlab/onos/net/resource/impl/LinkResourceManager.java
index ad15f56..75e0818 100644
--- a/core/net/src/main/java/org/onlab/onos/net/resource/impl/LinkResourceManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/resource/impl/LinkResourceManager.java
@@ -79,6 +79,12 @@
     }
 
     @Override
+    public LinkResourceAllocations updateResources(LinkResourceRequest req,
+                                                   LinkResourceAllocations oldAllocations) {
+        return null;
+    }
+
+    @Override
     public Iterable<LinkResourceAllocations> getAllocations() {
         // TODO Auto-generated method stub
         return null;
@@ -108,4 +114,10 @@
         return null;
     }
 
+    @Override
+    public ResourceRequest getAvailableResources(Link link,
+                                                 LinkResourceAllocations allocations) {
+        return null;
+    }
+
 }
diff --git a/core/net/src/test/java/org/onlab/onos/cluster/impl/MastershipManagerTest.java b/core/net/src/test/java/org/onlab/onos/cluster/impl/MastershipManagerTest.java
index c5c152c..96b8ae0 100644
--- a/core/net/src/test/java/org/onlab/onos/cluster/impl/MastershipManagerTest.java
+++ b/core/net/src/test/java/org/onlab/onos/cluster/impl/MastershipManagerTest.java
@@ -68,15 +68,15 @@
 
     @Test
     public void relinquishMastership() {
-        //no backups - should turn to standby and no master for device
+        //no backups - should just turn to NONE for device.
         mgr.setRole(NID_LOCAL, DEV_MASTER, MASTER);
         assertEquals("wrong role:", MASTER, mgr.getLocalRole(DEV_MASTER));
         mgr.relinquishMastership(DEV_MASTER);
         assertNull("wrong master:", mgr.getMasterFor(DEV_OTHER));
-        assertEquals("wrong role:", STANDBY, mgr.getLocalRole(DEV_MASTER));
+        assertEquals("wrong role:", NONE, mgr.getLocalRole(DEV_MASTER));
 
         //not master, nothing should happen
-        mgr.setRole(NID_LOCAL, DEV_OTHER, STANDBY);
+        mgr.setRole(NID_LOCAL, DEV_OTHER, NONE);
         mgr.relinquishMastership(DEV_OTHER);
         assertNull("wrong role:", mgr.getMasterFor(DEV_OTHER));
 
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 659a2c4..85ceb3f 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 static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_UPDATED;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -461,12 +462,12 @@
 
         @Override
         public int getDeviceCount() {
-            return 0;
+            return 1;
         }
 
         @Override
         public Iterable<Device> getDevices() {
-            return null;
+            return Arrays.asList(DEV);
         }
 
         @Override
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 b2f679c..d8e5fab 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
@@ -4,9 +4,7 @@
 
 import java.io.IOException;
 import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
+
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -20,7 +18,6 @@
 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.ClusterMessageResponse;
 import org.onlab.onos.store.cluster.messaging.MessageSubject;
 import org.onlab.onos.store.serializers.ClusterMessageSerializer;
 import org.onlab.onos.store.serializers.KryoNamespaces;
@@ -32,10 +29,11 @@
 import org.onlab.netty.MessageHandler;
 import org.onlab.netty.MessagingService;
 import org.onlab.netty.NettyMessagingService;
-import org.onlab.netty.Response;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.util.concurrent.ListenableFuture;
+
 @Component(immediate = true)
 @Service
 public class ClusterCommunicationManager
@@ -133,14 +131,12 @@
     }
 
     @Override
-    public ClusterMessageResponse sendAndReceive(ClusterMessage message, NodeId toNodeId) throws IOException {
+    public ListenableFuture<byte[]> sendAndReceive(ClusterMessage message, NodeId toNodeId) throws IOException {
         ControllerNode node = clusterService.getNode(toNodeId);
         checkArgument(node != null, "Unknown nodeId: %s", toNodeId);
         Endpoint nodeEp = new Endpoint(node.ip().toString(), node.tcpPort());
         try {
-            Response responseFuture =
-                    messagingService.sendAndReceive(nodeEp, message.subject().value(), SERIALIZER.encode(message));
-            return new InternalClusterMessageResponse(toNodeId, responseFuture);
+            return messagingService.sendAndReceive(nodeEp, message.subject().value(), SERIALIZER.encode(message));
 
         } catch (IOException e) {
             log.error("Failed interaction with remote nodeId: " + toNodeId, e);
@@ -188,60 +184,4 @@
             rawMessage.respond(response);
         }
     }
-
-    private static final class InternalClusterMessageResponse
-        implements ClusterMessageResponse {
-
-        private final NodeId sender;
-        private final Response responseFuture;
-        private volatile boolean isCancelled = false;
-        private volatile boolean isDone = false;
-
-        public InternalClusterMessageResponse(NodeId sender, Response responseFuture) {
-            this.sender = sender;
-            this.responseFuture = responseFuture;
-        }
-        @Override
-        public NodeId sender() {
-            return sender;
-        }
-
-        @Override
-        public byte[] get(long timeout, TimeUnit timeunit)
-                throws TimeoutException {
-            final byte[] result = responseFuture.get(timeout, timeunit);
-            isDone = true;
-            return result;
-        }
-
-        @Override
-        public boolean cancel(boolean mayInterruptIfRunning) {
-            if (isDone()) {
-                return false;
-            }
-            // doing nothing for now
-            // when onlab.netty Response support cancel, call them.
-            isCancelled = true;
-            return true;
-        }
-
-        @Override
-        public boolean isCancelled() {
-            return isCancelled;
-        }
-
-        @Override
-        public boolean isDone() {
-            return this.isDone || isCancelled();
-        }
-
-        @Override
-        public byte[] get() throws InterruptedException, ExecutionException {
-            // TODO: consider forbidding this call and force the use of timed get
-            //       to enforce handling of remote peer failure scenario
-            final byte[] result = responseFuture.get();
-            isDone = true;
-            return result;
-        }
-    }
-}
+}
\ No newline at end of file
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 cac31c2..dbd2688 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
@@ -12,6 +12,7 @@
 import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
@@ -26,7 +27,6 @@
 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.ApplicationId;
 import org.onlab.onos.cluster.ClusterService;
 import org.onlab.onos.net.Device;
 import org.onlab.onos.net.DeviceId;
@@ -50,7 +50,6 @@
 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.ClusterMessageResponse;
 import org.onlab.onos.store.flow.ReplicaInfo;
 import org.onlab.onos.store.flow.ReplicaInfoService;
 import org.onlab.onos.store.serializers.DistributedStoreSerializers;
@@ -58,6 +57,7 @@
 import org.onlab.util.KryoNamespace;
 import org.slf4j.Logger;
 
+import com.google.common.base.Function;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
@@ -214,9 +214,9 @@
                 SERIALIZER.encode(rule));
 
         try {
-            ClusterMessageResponse response = clusterCommunicator.sendAndReceive(message, replicaInfo.master().get());
-            return SERIALIZER.decode(response.get(FLOW_RULE_STORE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
-        } catch (IOException | TimeoutException e) {
+            Future<byte[]> responseFuture = clusterCommunicator.sendAndReceive(message, replicaInfo.master().get());
+            return SERIALIZER.decode(responseFuture.get(FLOW_RULE_STORE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
+        } catch (IOException | TimeoutException | ExecutionException | InterruptedException e) {
             // FIXME: throw a FlowStoreException
             throw new RuntimeException(e);
         }
@@ -248,9 +248,9 @@
                 SERIALIZER.encode(deviceId));
 
         try {
-            ClusterMessageResponse response = clusterCommunicator.sendAndReceive(message, replicaInfo.master().get());
-            return SERIALIZER.decode(response.get(FLOW_RULE_STORE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
-        } catch (IOException | TimeoutException e) {
+            Future<byte[]> responseFuture = clusterCommunicator.sendAndReceive(message, replicaInfo.master().get());
+            return SERIALIZER.decode(responseFuture.get(FLOW_RULE_STORE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
+        } catch (IOException | TimeoutException | ExecutionException | InterruptedException e) {
             // FIXME: throw a FlowStoreException
             throw new RuntimeException(e);
         }
@@ -265,15 +265,6 @@
     }
 
     @Override
-    public synchronized Iterable<FlowRule> getFlowRulesByAppId(ApplicationId appId) {
-        Collection<FlowRule> rules = flowEntriesById.get(appId.id());
-        if (rules == null) {
-            return Collections.emptyList();
-        }
-        return ImmutableSet.copyOf(rules);
-    }
-
-    @Override
     public void storeFlowRule(FlowRule rule) {
         storeBatch(new FlowRuleBatchOperation(Arrays.asList(new FlowRuleBatchEntry(FlowRuleOperation.ADD, rule))));
     }
@@ -301,14 +292,17 @@
                 SERIALIZER.encode(operation));
 
         try {
-            ClusterMessageResponse response = clusterCommunicator.sendAndReceive(message, replicaInfo.master().get());
-            response.get(FLOW_RULE_STORE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-        } catch (IOException | TimeoutException e) {
-            // FIXME: throw a FlowStoreException
-            throw new RuntimeException(e);
+            ListenableFuture<byte[]> responseFuture =
+                    clusterCommunicator.sendAndReceive(message, replicaInfo.master().get());
+            return Futures.transform(responseFuture, new Function<byte[], CompletedBatchOperation>() {
+                @Override
+                public CompletedBatchOperation apply(byte[] input) {
+                    return SERIALIZER.decode(input);
+                }
+            });
+        } catch (IOException e) {
+            return Futures.immediateFailedFuture(e);
         }
-
-        return null;
     }
 
     private ListenableFuture<CompletedBatchOperation> storeBatchInternal(FlowRuleBatchOperation operation) {
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/statistic/impl/DistributedStatisticStore.java b/core/store/dist/src/main/java/org/onlab/onos/store/statistic/impl/DistributedStatisticStore.java
index 273e3cc..7106aef 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/statistic/impl/DistributedStatisticStore.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/statistic/impl/DistributedStatisticStore.java
@@ -4,6 +4,7 @@
 import static org.slf4j.LoggerFactory.getLogger;
 
 import com.google.common.collect.Sets;
+
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -21,7 +22,6 @@
 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.ClusterMessageResponse;
 import org.onlab.onos.store.flow.ReplicaInfo;
 import org.onlab.onos.store.flow.ReplicaInfoService;
 import org.onlab.onos.store.serializers.KryoNamespaces;
@@ -34,6 +34,8 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+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.AtomicInteger;
@@ -184,11 +186,11 @@
                     SERIALIZER.encode(connectPoint));
 
             try {
-                ClusterMessageResponse response =
+                Future<byte[]> response =
                         clusterCommunicator.sendAndReceive(message, replicaInfo.master().get());
                 return SERIALIZER.decode(response.get(STATISTIC_STORE_TIMEOUT_MILLIS,
                                                       TimeUnit.MILLISECONDS));
-            } catch (IOException | TimeoutException e) {
+            } catch (IOException | TimeoutException | ExecutionException | InterruptedException e) {
                 // FIXME: throw a StatsStoreException
                 throw new RuntimeException(e);
             }
@@ -212,11 +214,11 @@
                     SERIALIZER.encode(connectPoint));
 
             try {
-                ClusterMessageResponse response =
+                Future<byte[]> response =
                         clusterCommunicator.sendAndReceive(message, replicaInfo.master().get());
                 return SERIALIZER.decode(response.get(STATISTIC_STORE_TIMEOUT_MILLIS,
                                                       TimeUnit.MILLISECONDS));
-            } catch (IOException | TimeoutException e) {
+            } catch (IOException | TimeoutException | ExecutionException | InterruptedException e) {
                 // FIXME: throw a StatsStoreException
                 throw new RuntimeException(e);
             }
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 82ea4e2..8d925bd 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
@@ -7,7 +7,6 @@
 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.ApplicationId;
 import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.net.flow.CompletedBatchOperation;
 import org.onlab.onos.net.flow.DefaultFlowEntry;
@@ -31,9 +30,7 @@
 
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -143,20 +140,6 @@
     }
 
     @Override
-    public Iterable<FlowRule> getFlowRulesByAppId(ApplicationId appId) {
-
-        Set<FlowRule> rules = new HashSet<>();
-        for (DeviceId did : flowEntries.keySet()) {
-            for (FlowEntry fe : getFlowEntries(did)) {
-                if (fe.appId() == appId.id()) {
-                    rules.add(fe);
-                }
-            }
-        }
-        return rules;
-    }
-
-    @Override
     public void storeFlowRule(FlowRule rule) {
         storeFlowRuleInternal(rule);
     }
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleMastershipStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleMastershipStore.java
index 709c95a..a0ac4c7 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleMastershipStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleMastershipStore.java
@@ -226,12 +226,34 @@
                 break;
             }
         }
+        backups.remove(backup);
         return backup;
     }
 
     @Override
     public MastershipEvent relinquishRole(NodeId nodeId, DeviceId deviceId) {
-        return setStandby(nodeId, deviceId);
+        MastershipRole role = getRole(nodeId, deviceId);
+        synchronized (this) {
+            switch (role) {
+                case MASTER:
+                    NodeId backup = reelect(nodeId);
+                    backups.remove(nodeId);
+                    if (backup == null) {
+                        masterMap.remove(deviceId);
+                    } else {
+                        masterMap.put(deviceId, backup);
+                        termMap.get(deviceId).incrementAndGet();
+                        return new MastershipEvent(MASTER_CHANGED, deviceId,
+                                new RoleInfo(backup, Lists.newLinkedList(backups)));
+                    }
+                case STANDBY:
+                    backups.remove(nodeId);
+                case NONE:
+                default:
+                    log.warn("unknown Mastership Role {}", role);
+            }
+        }
+        return null;
     }
 
 }
diff --git a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowEntryBuilder.java b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowEntryBuilder.java
index e04d87c..2a91b5b 100644
--- a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowEntryBuilder.java
+++ b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowEntryBuilder.java
@@ -33,7 +33,7 @@
 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.instruction.OFInstructionWriteActions;
 import org.projectfloodlight.openflow.protocol.match.Match;
 import org.projectfloodlight.openflow.protocol.match.MatchField;
 import org.projectfloodlight.openflow.protocol.oxm.OFOxmOchSigidBasic;
@@ -104,8 +104,8 @@
             case OF_13:
                 List<OFInstruction> ins = entry.getInstructions();
                 for (OFInstruction in : ins) {
-                    if (in.getType().equals(OFInstructionType.APPLY_ACTIONS)) {
-                        OFInstructionApplyActions apply = (OFInstructionApplyActions) in;
+                    if (in.getType().equals(OFInstructionType.WRITE_ACTIONS)) {
+                        OFInstructionWriteActions apply = (OFInstructionWriteActions) in;
                         return apply.getActions();
                     }
                 }
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 e1fde8a..e0fb7d9 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
@@ -2,14 +2,8 @@
 
 import static org.slf4j.LoggerFactory.getLogger;
 
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.onlab.onos.net.flow.FlowId;
 import org.onlab.onos.net.flow.FlowRule;
 import org.onlab.onos.net.flow.TrafficSelector;
-import org.onlab.onos.net.flow.TrafficTreatment;
 import org.onlab.onos.net.flow.criteria.Criteria.EthCriterion;
 import org.onlab.onos.net.flow.criteria.Criteria.EthTypeCriterion;
 import org.onlab.onos.net.flow.criteria.Criteria.IPCriterion;
@@ -20,22 +14,10 @@
 import org.onlab.onos.net.flow.criteria.Criteria.VlanIdCriterion;
 import org.onlab.onos.net.flow.criteria.Criteria.VlanPcpCriterion;
 import org.onlab.onos.net.flow.criteria.Criterion;
-import org.onlab.onos.net.flow.instructions.Instruction;
-import org.onlab.onos.net.flow.instructions.Instructions.OutputInstruction;
-import org.onlab.onos.net.flow.instructions.L0ModificationInstruction;
-import org.onlab.onos.net.flow.instructions.L0ModificationInstruction.ModLambdaInstruction;
-import org.onlab.onos.net.flow.instructions.L2ModificationInstruction;
-import org.onlab.onos.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction;
-import org.onlab.onos.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction;
-import org.onlab.onos.net.flow.instructions.L2ModificationInstruction.ModVlanPcpInstruction;
-import org.onlab.onos.net.flow.instructions.L3ModificationInstruction;
-import org.onlab.onos.net.flow.instructions.L3ModificationInstruction.ModIPInstruction;
 import org.projectfloodlight.openflow.protocol.OFFactory;
 import org.projectfloodlight.openflow.protocol.OFFlowAdd;
 import org.projectfloodlight.openflow.protocol.OFFlowDelete;
 import org.projectfloodlight.openflow.protocol.OFFlowMod;
-import org.projectfloodlight.openflow.protocol.OFFlowModFlags;
-import org.projectfloodlight.openflow.protocol.action.OFAction;
 import org.projectfloodlight.openflow.protocol.match.Match;
 import org.projectfloodlight.openflow.protocol.match.MatchField;
 import org.projectfloodlight.openflow.types.CircuitSignalID;
@@ -44,181 +26,82 @@
 import org.projectfloodlight.openflow.types.IpProtocol;
 import org.projectfloodlight.openflow.types.MacAddress;
 import org.projectfloodlight.openflow.types.Masked;
-import org.projectfloodlight.openflow.types.OFBufferId;
 import org.projectfloodlight.openflow.types.OFPort;
 import org.projectfloodlight.openflow.types.OFVlanVidMatch;
 import org.projectfloodlight.openflow.types.TransportPort;
-import org.projectfloodlight.openflow.types.U64;
 import org.projectfloodlight.openflow.types.VlanPcp;
 import org.projectfloodlight.openflow.types.VlanVid;
 import org.slf4j.Logger;
 
-
-public class FlowModBuilder {
+/**
+ * Builder for OpenFlow flow mods based on FlowRules.
+ */
+public abstract class FlowModBuilder {
 
     private final Logger log = getLogger(getClass());
 
     private final OFFactory factory;
-    private final TrafficTreatment treatment;
+    private final FlowRule flowRule;
     private final TrafficSelector selector;
 
-    private final int priority;
+    /**
+     * Creates a new flow mod builder.
+     *
+     * @param flowRule the flow rule to transform into a flow mod
+     * @param factory the OpenFlow factory to use to build the flow mod
+     * @return the new flow mod builder
+     */
+    public static FlowModBuilder builder(FlowRule flowRule, OFFactory factory) {
+        switch (factory.getVersion()) {
+        case OF_10:
+            return new FlowModBuilderVer10(flowRule, factory);
+        case OF_13:
+            return new FlowModBuilderVer13(flowRule, factory);
+        default:
+            throw new UnsupportedOperationException(
+                    "No flow mod builder for protocol version " + factory.getVersion());
+        }
+    }
 
-    private final FlowId cookie;
-
-
-
-    public FlowModBuilder(FlowRule flowRule, OFFactory factory) {
+    /**
+     * Constructs a flow mod builder.
+     *
+     * @param flowRule the flow rule to transform into a flow mod
+     * @param factory the OpenFlow factory to use to build the flow mod
+     */
+    protected FlowModBuilder(FlowRule flowRule, OFFactory factory) {
         this.factory = factory;
-        this.treatment = flowRule.treatment();
+        this.flowRule = flowRule;
         this.selector = flowRule.selector();
-        this.priority = flowRule.priority();
-        this.cookie = flowRule.id();
     }
 
-    public OFFlowAdd buildFlowAdd() {
-        Match match = buildMatch();
-        List<OFAction> actions = buildActions();
+    /**
+     * Builds an ADD flow mod.
+     *
+     * @return the flow mod
+     */
+    public abstract OFFlowAdd buildFlowAdd();
 
-        //TODO: what to do without bufferid? do we assume that there will be a pktout as well?
-        OFFlowAdd fm = factory.buildFlowAdd()
-                .setXid(cookie.value())
-                .setCookie(U64.of(cookie.value()))
-                .setBufferId(OFBufferId.NO_BUFFER)
-                .setActions(actions)
-                .setMatch(match)
-                .setFlags(Collections.singleton(OFFlowModFlags.SEND_FLOW_REM))
-                .setPriority(priority)
-                .build();
+    /**
+     * Builds a MODIFY flow mod.
+     *
+     * @return the flow mod
+     */
+    public abstract OFFlowMod buildFlowMod();
 
-        return fm;
+    /**
+     * Builds a DELETE flow mod.
+     *
+     * @return the flow mod
+     */
+    public abstract OFFlowDelete buildFlowDel();
 
-    }
-
-    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()
-                .setXid(cookie.value())
-                .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 OFFlowDelete buildFlowDel() {
-        Match match = buildMatch();
-        List<OFAction> actions = buildActions();
-
-        OFFlowDelete fm = factory.buildFlowDelete()
-                .setXid(cookie.value())
-                .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;
-    }
-
-    private List<OFAction> buildActions() {
-        List<OFAction> acts = new LinkedList<>();
-        if (treatment == null) {
-            return acts;
-        }
-        for (Instruction i : treatment.instructions()) {
-            switch (i.type()) {
-            case DROP:
-                log.warn("Saw drop action; assigning drop action");
-                return new LinkedList<>();
-            case L0MODIFICATION:
-                acts.add(buildL0Modification(i));
-                break;
-            case L2MODIFICATION:
-                acts.add(buildL2Modification(i));
-                break;
-            case L3MODIFICATION:
-                acts.add(buildL3Modification(i));
-                break;
-            case OUTPUT:
-                OutputInstruction out = (OutputInstruction) i;
-                acts.add(factory.actions().buildOutput().setPort(
-                        OFPort.of((int) out.port().toLong())).build());
-                break;
-            case GROUP:
-            default:
-                log.warn("Instruction type {} not yet implemented.", i.type());
-            }
-        }
-
-        return acts;
-    }
-
-    private OFAction buildL0Modification(Instruction i) {
-        L0ModificationInstruction l0m = (L0ModificationInstruction) i;
-        switch (l0m.subtype()) {
-        case LAMBDA:
-            ModLambdaInstruction ml = (ModLambdaInstruction) i;
-            return factory.actions().circuit(factory.oxms().ochSigidBasic(
-                    new CircuitSignalID((byte) 1, (byte) 2, ml.lambda(), (short) 1)));
-        default:
-            log.warn("Unimplemented action type {}.", l0m.subtype());
-            break;
-        }
-        return null;
-    }
-
-    private OFAction buildL3Modification(Instruction i) {
-        L3ModificationInstruction l3m = (L3ModificationInstruction) i;
-        ModIPInstruction ip;
-        switch (l3m.subtype()) {
-        case IP_DST:
-            ip = (ModIPInstruction) i;
-            return factory.actions().setNwDst(IPv4Address.of(ip.ip().toInt()));
-        case IP_SRC:
-            ip = (ModIPInstruction) i;
-            return factory.actions().setNwSrc(IPv4Address.of(ip.ip().toInt()));
-        default:
-            log.warn("Unimplemented action type {}.", l3m.subtype());
-            break;
-        }
-        return null;
-    }
-
-    private OFAction buildL2Modification(Instruction i) {
-        L2ModificationInstruction l2m = (L2ModificationInstruction) i;
-        ModEtherInstruction eth;
-        switch (l2m.subtype()) {
-        case ETH_DST:
-            eth = (ModEtherInstruction) l2m;
-            return factory.actions().setDlDst(MacAddress.of(eth.mac().toLong()));
-        case ETH_SRC:
-            eth = (ModEtherInstruction) l2m;
-            return factory.actions().setDlSrc(MacAddress.of(eth.mac().toLong()));
-        case VLAN_ID:
-            ModVlanIdInstruction vlanId = (ModVlanIdInstruction) l2m;
-            return factory.actions().setVlanVid(VlanVid.ofVlan(vlanId.vlanId.toShort()));
-        case VLAN_PCP:
-            ModVlanPcpInstruction vlanPcp = (ModVlanPcpInstruction) l2m;
-            return factory.actions().setVlanPcp(VlanPcp.of(vlanPcp.vlanPcp()));
-        default:
-            log.warn("Unimplemented action type {}.", l2m.subtype());
-            break;
-        }
-        return null;
-    }
-
-    private Match buildMatch() {
+    /**
+     * Builds the match for the flow mod.
+     *
+     * @return the match
+     */
+    protected Match buildMatch() {
         Match.Builder mBuilder = factory.buildMatch();
         EthCriterion eth;
         IPCriterion ip;
@@ -323,6 +206,22 @@
         return mBuilder.build();
     }
 
+    /**
+     * Returns the flow rule for this builder.
+     *
+     * @return the flow rule
+     */
+    protected FlowRule flowRule() {
+        return flowRule;
+    }
 
+    /**
+     * Returns the factory used for building OpenFlow constructs.
+     *
+     * @return the factory
+     */
+    protected OFFactory factory() {
+        return factory;
+    }
 
 }
diff --git a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowModBuilderVer10.java b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowModBuilderVer10.java
new file mode 100644
index 0000000..fd54131
--- /dev/null
+++ b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowModBuilderVer10.java
@@ -0,0 +1,191 @@
+package org.onlab.onos.provider.of.flow.impl;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.onlab.onos.net.flow.FlowRule;
+import org.onlab.onos.net.flow.TrafficTreatment;
+import org.onlab.onos.net.flow.instructions.Instruction;
+import org.onlab.onos.net.flow.instructions.Instructions.OutputInstruction;
+import org.onlab.onos.net.flow.instructions.L2ModificationInstruction;
+import org.onlab.onos.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction;
+import org.onlab.onos.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction;
+import org.onlab.onos.net.flow.instructions.L2ModificationInstruction.ModVlanPcpInstruction;
+import org.onlab.onos.net.flow.instructions.L3ModificationInstruction;
+import org.onlab.onos.net.flow.instructions.L3ModificationInstruction.ModIPInstruction;
+import org.projectfloodlight.openflow.protocol.OFFactory;
+import org.projectfloodlight.openflow.protocol.OFFlowAdd;
+import org.projectfloodlight.openflow.protocol.OFFlowDelete;
+import org.projectfloodlight.openflow.protocol.OFFlowMod;
+import org.projectfloodlight.openflow.protocol.OFFlowModFlags;
+import org.projectfloodlight.openflow.protocol.action.OFAction;
+import org.projectfloodlight.openflow.protocol.match.Match;
+import org.projectfloodlight.openflow.types.IPv4Address;
+import org.projectfloodlight.openflow.types.MacAddress;
+import org.projectfloodlight.openflow.types.OFBufferId;
+import org.projectfloodlight.openflow.types.OFPort;
+import org.projectfloodlight.openflow.types.U64;
+import org.projectfloodlight.openflow.types.VlanPcp;
+import org.projectfloodlight.openflow.types.VlanVid;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Flow mod builder for OpenFlow 1.0.
+ */
+public class FlowModBuilderVer10 extends FlowModBuilder {
+
+    private static final Logger log = LoggerFactory.getLogger(FlowModBuilderVer10.class);
+
+    private final TrafficTreatment treatment;
+
+    /**
+     * Constructor for a flow mod builder for OpenFlow 1.0.
+     *
+     * @param flowRule the flow rule to transform into a flow mod
+     * @param factory the OpenFlow factory to use to build the flow mod
+     */
+    protected FlowModBuilderVer10(FlowRule flowRule, OFFactory factory) {
+        super(flowRule, factory);
+
+        this.treatment = flowRule.treatment();
+    }
+
+    @Override
+    public OFFlowAdd buildFlowAdd() {
+        Match match = buildMatch();
+        List<OFAction> actions = buildActions();
+
+        long cookie = flowRule().id().value();
+
+        //TODO: what to do without bufferid? do we assume that there will be a pktout as well?
+        OFFlowAdd fm = factory().buildFlowAdd()
+                .setXid(cookie)
+                .setCookie(U64.of(cookie))
+                .setBufferId(OFBufferId.NO_BUFFER)
+                .setActions(actions)
+                .setMatch(match)
+                .setFlags(Collections.singleton(OFFlowModFlags.SEND_FLOW_REM))
+                .setPriority(flowRule().priority())
+                .build();
+
+        return fm;
+    }
+
+    @Override
+    public OFFlowMod buildFlowMod() {
+        Match match = buildMatch();
+        List<OFAction> actions = buildActions();
+
+        long cookie = flowRule().id().value();
+
+        //TODO: what to do without bufferid? do we assume that there will be a pktout as well?
+        OFFlowMod fm = factory().buildFlowModify()
+                .setXid(cookie)
+                .setCookie(U64.of(cookie))
+                .setBufferId(OFBufferId.NO_BUFFER)
+                .setActions(actions)
+                .setMatch(match)
+                .setFlags(Collections.singleton(OFFlowModFlags.SEND_FLOW_REM))
+                .setPriority(flowRule().priority())
+                .build();
+
+        return fm;
+    }
+
+    @Override
+    public OFFlowDelete buildFlowDel() {
+        Match match = buildMatch();
+        List<OFAction> actions = buildActions();
+
+        long cookie = flowRule().id().value();
+
+        OFFlowDelete fm = factory().buildFlowDelete()
+                .setXid(cookie)
+                .setCookie(U64.of(cookie))
+                .setBufferId(OFBufferId.NO_BUFFER)
+                .setActions(actions)
+                .setMatch(match)
+                .setFlags(Collections.singleton(OFFlowModFlags.SEND_FLOW_REM))
+                .setPriority(flowRule().priority())
+                .build();
+
+        return fm;
+    }
+
+    private List<OFAction> buildActions() {
+        List<OFAction> acts = new LinkedList<>();
+        if (treatment == null) {
+            return acts;
+        }
+        for (Instruction i : treatment.instructions()) {
+            switch (i.type()) {
+            case DROP:
+                log.warn("Saw drop action; assigning drop action");
+                return new LinkedList<>();
+            case L2MODIFICATION:
+                acts.add(buildL2Modification(i));
+                break;
+            case L3MODIFICATION:
+                acts.add(buildL3Modification(i));
+                break;
+            case OUTPUT:
+                OutputInstruction out = (OutputInstruction) i;
+                acts.add(factory().actions().buildOutput().setPort(
+                        OFPort.of((int) out.port().toLong())).build());
+                break;
+            case L0MODIFICATION:
+            case GROUP:
+                log.warn("Instruction type {} not supported with protocol version {}",
+                        i.type(), factory().getVersion());
+                break;
+            default:
+                log.warn("Instruction type {} not yet implemented.", i.type());
+            }
+        }
+
+        return acts;
+    }
+
+    private OFAction buildL3Modification(Instruction i) {
+        L3ModificationInstruction l3m = (L3ModificationInstruction) i;
+        ModIPInstruction ip;
+        switch (l3m.subtype()) {
+        case IP_DST:
+            ip = (ModIPInstruction) i;
+            return factory().actions().setNwDst(IPv4Address.of(ip.ip().toInt()));
+        case IP_SRC:
+            ip = (ModIPInstruction) i;
+            return factory().actions().setNwSrc(IPv4Address.of(ip.ip().toInt()));
+        default:
+            log.warn("Unimplemented action type {}.", l3m.subtype());
+            break;
+        }
+        return null;
+    }
+
+    private OFAction buildL2Modification(Instruction i) {
+        L2ModificationInstruction l2m = (L2ModificationInstruction) i;
+        ModEtherInstruction eth;
+        switch (l2m.subtype()) {
+        case ETH_DST:
+            eth = (ModEtherInstruction) l2m;
+            return factory().actions().setDlDst(MacAddress.of(eth.mac().toLong()));
+        case ETH_SRC:
+            eth = (ModEtherInstruction) l2m;
+            return factory().actions().setDlSrc(MacAddress.of(eth.mac().toLong()));
+        case VLAN_ID:
+            ModVlanIdInstruction vlanId = (ModVlanIdInstruction) l2m;
+            return factory().actions().setVlanVid(VlanVid.ofVlan(vlanId.vlanId.toShort()));
+        case VLAN_PCP:
+            ModVlanPcpInstruction vlanPcp = (ModVlanPcpInstruction) l2m;
+            return factory().actions().setVlanPcp(VlanPcp.of(vlanPcp.vlanPcp()));
+        default:
+            log.warn("Unimplemented action type {}.", l2m.subtype());
+            break;
+        }
+        return null;
+    }
+
+}
diff --git a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowModBuilderVer13.java b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowModBuilderVer13.java
new file mode 100644
index 0000000..35e99e1
--- /dev/null
+++ b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/FlowModBuilderVer13.java
@@ -0,0 +1,226 @@
+package org.onlab.onos.provider.of.flow.impl;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.onlab.onos.net.flow.FlowRule;
+import org.onlab.onos.net.flow.TrafficTreatment;
+import org.onlab.onos.net.flow.instructions.Instruction;
+import org.onlab.onos.net.flow.instructions.Instructions.OutputInstruction;
+import org.onlab.onos.net.flow.instructions.L0ModificationInstruction;
+import org.onlab.onos.net.flow.instructions.L0ModificationInstruction.ModLambdaInstruction;
+import org.onlab.onos.net.flow.instructions.L2ModificationInstruction;
+import org.onlab.onos.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction;
+import org.onlab.onos.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction;
+import org.onlab.onos.net.flow.instructions.L2ModificationInstruction.ModVlanPcpInstruction;
+import org.onlab.onos.net.flow.instructions.L3ModificationInstruction;
+import org.onlab.onos.net.flow.instructions.L3ModificationInstruction.ModIPInstruction;
+import org.projectfloodlight.openflow.protocol.OFFactory;
+import org.projectfloodlight.openflow.protocol.OFFlowAdd;
+import org.projectfloodlight.openflow.protocol.OFFlowDelete;
+import org.projectfloodlight.openflow.protocol.OFFlowMod;
+import org.projectfloodlight.openflow.protocol.OFFlowModFlags;
+import org.projectfloodlight.openflow.protocol.action.OFAction;
+import org.projectfloodlight.openflow.protocol.instruction.OFInstruction;
+import org.projectfloodlight.openflow.protocol.match.Match;
+import org.projectfloodlight.openflow.protocol.oxm.OFOxm;
+import org.projectfloodlight.openflow.types.CircuitSignalID;
+import org.projectfloodlight.openflow.types.IPv4Address;
+import org.projectfloodlight.openflow.types.MacAddress;
+import org.projectfloodlight.openflow.types.OFBufferId;
+import org.projectfloodlight.openflow.types.OFPort;
+import org.projectfloodlight.openflow.types.OFVlanVidMatch;
+import org.projectfloodlight.openflow.types.U64;
+import org.projectfloodlight.openflow.types.VlanPcp;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Flow mod builder for OpenFlow 1.3+.
+ */
+public class FlowModBuilderVer13 extends FlowModBuilder {
+
+    private static final Logger log = LoggerFactory.getLogger(FlowModBuilderVer10.class);
+
+    private final TrafficTreatment treatment;
+
+    /**
+     * Constructor for a flow mod builder for OpenFlow 1.3.
+     *
+     * @param flowRule the flow rule to transform into a flow mod
+     * @param factory the OpenFlow factory to use to build the flow mod
+     */
+    protected FlowModBuilderVer13(FlowRule flowRule, OFFactory factory) {
+        super(flowRule, factory);
+
+        this.treatment = flowRule.treatment();
+    }
+
+    @Override
+    public OFFlowAdd buildFlowAdd() {
+        Match match = buildMatch();
+        OFInstruction writeActions =
+                factory().instructions().writeActions(buildActions());
+
+        long cookie = flowRule().id().value();
+
+        //TODO: what to do without bufferid? do we assume that there will be a pktout as well?
+        OFFlowAdd fm = factory().buildFlowAdd()
+                .setXid(cookie)
+                .setCookie(U64.of(cookie))
+                .setBufferId(OFBufferId.NO_BUFFER)
+                .setInstructions(Collections.singletonList(writeActions))
+                .setMatch(match)
+                .setFlags(Collections.singleton(OFFlowModFlags.SEND_FLOW_REM))
+                .setPriority(flowRule().priority())
+                .build();
+
+        return fm;
+    }
+
+    @Override
+    public OFFlowMod buildFlowMod() {
+        Match match = buildMatch();
+        OFInstruction writeActions =
+                factory().instructions().writeActions(buildActions());
+
+        long cookie = flowRule().id().value();
+
+        //TODO: what to do without bufferid? do we assume that there will be a pktout as well?
+        OFFlowMod fm = factory().buildFlowModify()
+                .setXid(cookie)
+                .setCookie(U64.of(cookie))
+                .setBufferId(OFBufferId.NO_BUFFER)
+                .setInstructions(Collections.singletonList(writeActions))
+                .setMatch(match)
+                .setFlags(Collections.singleton(OFFlowModFlags.SEND_FLOW_REM))
+                .setPriority(flowRule().priority())
+                .build();
+
+        return fm;
+    }
+
+    @Override
+    public OFFlowDelete buildFlowDel() {
+        Match match = buildMatch();
+        OFInstruction writeActions =
+                factory().instructions().writeActions(buildActions());
+
+        long cookie = flowRule().id().value();
+
+        OFFlowDelete fm = factory().buildFlowDelete()
+                .setXid(cookie)
+                .setCookie(U64.of(cookie))
+                .setBufferId(OFBufferId.NO_BUFFER)
+                .setInstructions(Collections.singletonList(writeActions))
+                .setMatch(match)
+                .setFlags(Collections.singleton(OFFlowModFlags.SEND_FLOW_REM))
+                .setPriority(flowRule().priority())
+                .build();
+
+        return fm;
+    }
+
+    private List<OFAction> buildActions() {
+        List<OFAction> actions = new LinkedList<>();
+        if (treatment == null) {
+            return actions;
+        }
+        for (Instruction i : treatment.instructions()) {
+            switch (i.type()) {
+            case DROP:
+                log.warn("Saw drop action; assigning drop action");
+                return new LinkedList<>();
+            case L0MODIFICATION:
+                actions.add(buildL0Modification(i));
+                break;
+            case L2MODIFICATION:
+                actions.add(buildL2Modification(i));
+                break;
+            case L3MODIFICATION:
+                actions.add(buildL3Modification(i));
+                break;
+            case OUTPUT:
+                OutputInstruction out = (OutputInstruction) i;
+                actions.add(factory().actions().buildOutput().setPort(
+                        OFPort.of((int) out.port().toLong())).build());
+                break;
+            case GROUP:
+            default:
+                log.warn("Instruction type {} not yet implemented.", i.type());
+            }
+        }
+
+        return actions;
+    }
+
+    private OFAction buildL0Modification(Instruction i) {
+        L0ModificationInstruction l0m = (L0ModificationInstruction) i;
+        switch (l0m.subtype()) {
+        case LAMBDA:
+            ModLambdaInstruction ml = (ModLambdaInstruction) i;
+            return factory().actions().circuit(factory().oxms().ochSigidBasic(
+                    new CircuitSignalID((byte) 1, (byte) 2, ml.lambda(), (short) 1)));
+        default:
+            log.warn("Unimplemented action type {}.", l0m.subtype());
+            break;
+        }
+        return null;
+    }
+
+    private OFAction buildL2Modification(Instruction i) {
+        L2ModificationInstruction l2m = (L2ModificationInstruction) i;
+        ModEtherInstruction eth;
+        OFOxm<?> oxm = null;
+        switch (l2m.subtype()) {
+        case ETH_DST:
+            eth = (ModEtherInstruction) l2m;
+            oxm = factory().oxms().ethDst(MacAddress.of(eth.mac().toLong()));
+            break;
+        case ETH_SRC:
+            eth = (ModEtherInstruction) l2m;
+            oxm = factory().oxms().ethSrc(MacAddress.of(eth.mac().toLong()));
+            break;
+        case VLAN_ID:
+            ModVlanIdInstruction vlanId = (ModVlanIdInstruction) l2m;
+            oxm = factory().oxms().vlanVid(OFVlanVidMatch.ofVlan(vlanId.vlanId.toShort()));
+            break;
+        case VLAN_PCP:
+            ModVlanPcpInstruction vlanPcp = (ModVlanPcpInstruction) l2m;
+            oxm = factory().oxms().vlanPcp(VlanPcp.of(vlanPcp.vlanPcp));
+            break;
+        default:
+            log.warn("Unimplemented action type {}.", l2m.subtype());
+            break;
+        }
+
+        if (oxm != null) {
+            return factory().actions().buildSetField().setField(oxm).build();
+        }
+        return null;
+    }
+
+    private OFAction buildL3Modification(Instruction i) {
+        L3ModificationInstruction l3m = (L3ModificationInstruction) i;
+        ModIPInstruction ip;
+        OFOxm<?> oxm = null;
+        switch (l3m.subtype()) {
+        case IP_DST:
+            ip = (ModIPInstruction) i;
+            oxm = factory().oxms().ipv4Dst(IPv4Address.of(ip.ip().toInt()));
+        case IP_SRC:
+            ip = (ModIPInstruction) i;
+            oxm = factory().oxms().ipv4Src(IPv4Address.of(ip.ip().toInt()));
+        default:
+            log.warn("Unimplemented action type {}.", l3m.subtype());
+            break;
+        }
+
+        if (oxm != null) {
+            return factory().actions().buildSetField().setField(oxm).build();
+        }
+        return null;
+    }
+
+}
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 8d3c018..7fbe09a 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
@@ -22,6 +22,7 @@
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.onlab.onos.ApplicationId;
 import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.flow.BatchOperation;
 import org.onlab.onos.net.flow.CompletedBatchOperation;
 import org.onlab.onos.net.flow.DefaultFlowEntry;
 import org.onlab.onos.net.flow.FlowEntry;
@@ -31,7 +32,6 @@
 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.flow.BatchOperation;
 import org.onlab.onos.net.provider.AbstractProvider;
 import org.onlab.onos.net.provider.ProviderId;
 import org.onlab.onos.net.topology.TopologyService;
@@ -148,7 +148,7 @@
 
     private void applyRule(FlowRule flowRule) {
         OpenFlowSwitch sw = controller.getSwitch(Dpid.dpid(flowRule.deviceId().uri()));
-        sw.sendMsg(new FlowModBuilder(flowRule, sw.factory()).buildFlowAdd());
+        sw.sendMsg(FlowModBuilder.builder(flowRule, sw.factory()).buildFlowAdd());
     }
 
 
@@ -163,7 +163,7 @@
 
     private void removeRule(FlowRule flowRule) {
         OpenFlowSwitch sw = controller.getSwitch(Dpid.dpid(flowRule.deviceId().uri()));
-        sw.sendMsg(new FlowModBuilder(flowRule, sw.factory()).buildFlowDel());
+        sw.sendMsg(FlowModBuilder.builder(flowRule, sw.factory()).buildFlowDel());
     }
 
     @Override
@@ -192,7 +192,7 @@
                 return failed;
             }
             sws.add(new Dpid(sw.getId()));
-            FlowModBuilder builder = new FlowModBuilder(flowRule, sw.factory());
+            FlowModBuilder builder = FlowModBuilder.builder(flowRule, sw.factory());
             switch (fbe.getOperator()) {
                 case ADD:
                     mod = builder.buildFlowAdd();
diff --git a/utils/netty/src/main/java/org/onlab/netty/AsyncResponse.java b/utils/netty/src/main/java/org/onlab/netty/AsyncResponse.java
deleted file mode 100644
index 1772a3c..0000000
--- a/utils/netty/src/main/java/org/onlab/netty/AsyncResponse.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package org.onlab.netty;
-
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- * An asynchronous response.
- * 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.
- */
-public class AsyncResponse implements Response {
-
-    private byte[] value;
-    private boolean done = false;
-    private final long start = System.nanoTime();
-
-    @Override
-    public byte[] get(long timeout, TimeUnit timeUnit) throws TimeoutException {
-        timeout = timeUnit.toNanos(timeout);
-        boolean interrupted = false;
-        try {
-            synchronized (this) {
-                while (!done) {
-                    try {
-                        long timeRemaining = timeout - (System.nanoTime() - start);
-                        if (timeRemaining <= 0) {
-                            throw new TimeoutException("Operation timed out.");
-                        }
-                        TimeUnit.NANOSECONDS.timedWait(this, timeRemaining);
-                    } catch (InterruptedException e) {
-                        interrupted = true;
-                    }
-                }
-            }
-        } finally {
-            if (interrupted) {
-                Thread.currentThread().interrupt();
-            }
-        }
-        return value;
-    }
-
-    @Override
-    public byte[] get() throws InterruptedException {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean isReady() {
-        return done;
-    }
-
-    /**
-     * Sets response value and unblocks any thread blocking on the response to become
-     * available.
-     * @param data response data.
-     */
-    public synchronized void setResponse(byte[] data) {
-        if (!done) {
-            done = true;
-            value = data;
-            this.notifyAll();
-        }
-    }
-}
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 08676ac..bf93331 100644
--- a/utils/netty/src/main/java/org/onlab/netty/MessagingService.java
+++ b/utils/netty/src/main/java/org/onlab/netty/MessagingService.java
@@ -2,6 +2,8 @@
 
 import java.io.IOException;
 
+import com.google.common.util.concurrent.ListenableFuture;
+
 /**
  * Interface for low level messaging primitives.
  */
@@ -24,7 +26,7 @@
      * @return a response future
      * @throws IOException
      */
-    public Response sendAndReceive(Endpoint ep, String type, byte[] payload) throws IOException;
+    public ListenableFuture<byte[]> sendAndReceive(Endpoint ep, String type, byte[] payload) throws IOException;
 
     /**
      * Registers a new message handler for message type.
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 26d835d..1b579f9 100644
--- a/utils/netty/src/main/java/org/onlab/netty/NettyMessagingService.java
+++ b/utils/netty/src/main/java/org/onlab/netty/NettyMessagingService.java
@@ -5,6 +5,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
 
 import io.netty.bootstrap.Bootstrap;
 import io.netty.bootstrap.ServerBootstrap;
@@ -26,7 +27,6 @@
 import io.netty.channel.socket.nio.NioServerSocketChannel;
 import io.netty.channel.socket.nio.NioSocketChannel;
 
-import org.apache.commons.lang.math.RandomUtils;
 import org.apache.commons.pool.KeyedPoolableObjectFactory;
 import org.apache.commons.pool.impl.GenericKeyedObjectPool;
 import org.slf4j.Logger;
@@ -34,6 +34,8 @@
 
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
 
 /**
  * A Netty based implementation of MessagingService.
@@ -44,7 +46,8 @@
 
     private final Endpoint localEp;
     private final ConcurrentMap<String, MessageHandler> handlers = new ConcurrentHashMap<>();
-    private final Cache<Long, AsyncResponse> responseFutures = CacheBuilder.newBuilder()
+    private final AtomicLong messageIdGenerator = new AtomicLong(0);
+    private final Cache<Long, SettableFuture<byte[]>> responseFutures = CacheBuilder.newBuilder()
             .maximumSize(100000)
             .weakValues()
             // TODO: Once the entry expires, notify blocking threads (if any).
@@ -68,7 +71,7 @@
             clientChannelClass = EpollSocketChannel.class;
             return;
         } catch (Throwable t) {
-            log.warn("Failed to initialize native (epoll) transport. Proceeding with nio.", t);
+            log.warn("Failed to initialize native (epoll) transport. Reason: {}. Proceeding with nio.", t.getMessage());
         }
         clientGroup = new NioEventLoopGroup();
         serverGroup = new NioEventLoopGroup();
@@ -119,7 +122,7 @@
     @Override
     public void sendAsync(Endpoint ep, String type, byte[] payload) throws IOException {
         InternalMessage message = new InternalMessage.Builder(this)
-            .withId(RandomUtils.nextLong())
+            .withId(messageIdGenerator.incrementAndGet())
             .withSender(localEp)
             .withType(type)
             .withPayload(payload)
@@ -142,10 +145,10 @@
     }
 
     @Override
-    public Response sendAndReceive(Endpoint ep, String type, byte[] payload)
+    public ListenableFuture<byte[]> sendAndReceive(Endpoint ep, String type, byte[] payload)
             throws IOException {
-        AsyncResponse futureResponse = new AsyncResponse();
-        Long messageId = RandomUtils.nextLong();
+        SettableFuture<byte[]> futureResponse = SettableFuture.create();
+        Long messageId = messageIdGenerator.incrementAndGet();
         responseFutures.put(messageId, futureResponse);
         InternalMessage message = new InternalMessage.Builder(this)
             .withId(messageId)
@@ -267,10 +270,10 @@
             String type = message.type();
             if (type.equals(InternalMessage.REPLY_MESSAGE_TYPE)) {
                 try {
-                    AsyncResponse futureResponse =
+                    SettableFuture<byte[]> futureResponse =
                         NettyMessagingService.this.responseFutures.getIfPresent(message.id());
                     if (futureResponse != null) {
-                        futureResponse.setResponse(message.payload());
+                        futureResponse.set(message.payload());
                     } else {
                         log.warn("Received a reply. But was unable to locate the request handle");
                     }
diff --git a/utils/netty/src/main/java/org/onlab/netty/Response.java b/utils/netty/src/main/java/org/onlab/netty/Response.java
deleted file mode 100644
index 150755e..0000000
--- a/utils/netty/src/main/java/org/onlab/netty/Response.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.onlab.netty;
-
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- * 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.
- */
-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 payload
-     * @throws TimeoutException if the timeout expires before the response arrives.
-     */
-    public byte[] get(long timeout, TimeUnit tu) throws TimeoutException;
-
-    /**
-     * Gets the response waiting for indefinite timeout period.
-     * @return response payload
-     * @throws InterruptedException if the thread is interrupted before the response arrives.
-     */
-    public byte[] get() throws InterruptedException;
-
-    /**
-     * Checks if the response is ready without blocking.
-     * @return true if response is ready, false otherwise.
-     */
-    public boolean isReady();
-}
diff --git a/utils/netty/src/test/java/org/onlab/netty/PingPongTest.java b/utils/netty/src/test/java/org/onlab/netty/PingPongTest.java
index 36d2a1e..ddcdd6f 100644
--- a/utils/netty/src/test/java/org/onlab/netty/PingPongTest.java
+++ b/utils/netty/src/test/java/org/onlab/netty/PingPongTest.java
@@ -1,9 +1,12 @@
 package org.onlab.netty;
 
+import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.commons.lang3.RandomUtils;
+
 import static org.junit.Assert.*;
+
 import org.junit.Test;
 
 /**
@@ -20,8 +23,8 @@
             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));
+            Future<byte[]> responseFuture = pinger.sendAndReceive(new Endpoint("localhost", 9086), "echo", payload);
+            assertArrayEquals(payload, responseFuture.get(10000, TimeUnit.MILLISECONDS));
         } finally {
             pinger.deactivate();
             ponger.deactivate();
diff --git a/web/gui/src/main/java/org/onlab/onos/gui/TopologyResource.java b/web/gui/src/main/java/org/onlab/onos/gui/TopologyResource.java
new file mode 100644
index 0000000..1a97a56
--- /dev/null
+++ b/web/gui/src/main/java/org/onlab/onos/gui/TopologyResource.java
@@ -0,0 +1,215 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.onlab.onos.gui;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.Device;
+import org.onlab.onos.net.Host;
+import org.onlab.onos.net.HostLocation;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.device.DeviceService;
+import org.onlab.onos.net.host.HostService;
+import org.onlab.onos.net.link.LinkService;
+import org.onlab.onos.net.topology.Topology;
+import org.onlab.onos.net.topology.TopologyGraph;
+import org.onlab.onos.net.topology.TopologyService;
+import org.onlab.onos.net.topology.TopologyVertex;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.rest.BaseResource;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Response;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Topology viewer resource.
+ */
+@javax.ws.rs.Path("topology")
+public class TopologyResource extends BaseResource {
+
+    @javax.ws.rs.Path("/graph")
+    @GET
+    @Produces("application/json")
+    public Response graph() {
+        // Fetch the services we'll be using.
+        DeviceService deviceService = get(DeviceService.class);
+        HostService hostService = get(HostService.class);
+        TopologyService topologyService = get(TopologyService.class);
+
+        // Fetch the current topology and its graph that we'll use to render.
+        Topology topo = topologyService.currentTopology();
+        TopologyGraph graph = topologyService.getGraph(topo);
+
+        ObjectMapper mapper = new ObjectMapper();
+        ObjectNode rootNode = mapper.createObjectNode();
+        rootNode.set("devices", getDevices(mapper, deviceService, graph));
+        rootNode.set("links", getLinks(mapper, topo, graph));
+        rootNode.set("hosts", getHosts(mapper, hostService));
+        return Response.ok(rootNode.toString()).build();
+    }
+
+    // Encodes all infrastructure devices.
+    private ArrayNode getDevices(ObjectMapper mapper, DeviceService deviceService,
+                                 TopologyGraph graph) {
+        ArrayNode devices = mapper.createArrayNode();
+        for (TopologyVertex vertex : graph.getVertexes()) {
+            devices.add(json(mapper, deviceService.getDevice(vertex.deviceId()),
+                             deviceService.isAvailable(vertex.deviceId())));
+        }
+        return devices;
+    }
+
+    // Encodes all infrastructure links.
+    private ArrayNode getLinks(ObjectMapper mapper, Topology topo, TopologyGraph graph) {
+        // Now scan all links and count number of them between the same devices
+        // using a normalized link key.
+        Map<String, AggLink> linkRecords = aggregateLinks();
+
+        // Now build all interior edges using the aggregated links.
+        ArrayNode links = mapper.createArrayNode();
+        for (AggLink lr : linkRecords.values()) {
+            links.add(json(mapper, lr));
+        }
+        return links;
+    }
+
+    // Encodes all end-station hosts.
+    private ArrayNode getHosts(ObjectMapper mapper, HostService hostService) {
+        ArrayNode hosts = mapper.createArrayNode();
+        for (Host host : hostService.getHosts()) {
+            Set<IpPrefix> ipAddresses = host.ipAddresses();
+            IpPrefix ipAddress = ipAddresses.isEmpty() ? null : ipAddresses.iterator().next();
+            String label = ipAddress != null ? ipAddress.toString() : host.mac().toString();
+            hosts.add(json(mapper, host));
+        }
+        return hosts;
+    }
+
+    // Scan all links and counts number of them between the same devices
+    // using a normalized link key.
+    private Map<String, AggLink> aggregateLinks() {
+        Map<String, AggLink> aggLinks = new HashMap<>();
+        LinkService linkService = get(LinkService.class);
+        for (Link link : linkService.getLinks()) {
+            String key = key(link);
+            AggLink lr = aggLinks.get(key);
+            if (lr == null) {
+                lr = new AggLink(key);
+                aggLinks.put(key, lr);
+            }
+            lr.addLink(link);
+        }
+        return aggLinks;
+    }
+
+    // Produces JSON for a device.
+    private ObjectNode json(ObjectMapper mapper, Device device, boolean isOnline) {
+        ObjectNode node = mapper.createObjectNode()
+                .put("id", device.id().toString())
+                .put("type", device.type().toString().toLowerCase())
+                .put("online", isOnline);
+        node.set("labels", labels(mapper,
+                                  device.id().uri().getSchemeSpecificPart(),
+                                  MacAddress.valueOf(device.chassisId().value()).toString(),
+                                  device.serialNumber()));
+        return node;
+    }
+
+    // Produces JSON for a link.
+    private ObjectNode json(ObjectMapper mapper, AggLink aggLink) {
+        Link link = aggLink.link;
+        return mapper.createObjectNode()
+                .put("src", link.src().deviceId().toString())
+                .put("dst", link.dst().deviceId().toString())
+                .put("type", link.type().toString().toLowerCase())
+                .put("linkWidth", aggLink.links.size());
+    }
+
+    // Produces JSON for a device.
+    private ObjectNode json(ObjectMapper mapper, Host host) {
+        ObjectNode json = mapper.createObjectNode()
+                .put("id", host.id().toString());
+        json.set("cp", location(mapper, host.location()));
+        json.set("labels", labels(mapper, ip(host.ipAddresses()),
+                                  host.mac().toString()));
+        return json;
+    }
+
+    private String ip(Set<IpPrefix> ipPrefixes) {
+        Iterator<IpPrefix> it = ipPrefixes.iterator();
+        return it.hasNext() ? it.next().toString() : "unknown";
+    }
+
+    private ObjectNode location(ObjectMapper mapper, HostLocation location) {
+        return mapper.createObjectNode()
+                .put("device", location.deviceId().toString())
+                .put("port", location.port().toLong());
+    }
+
+    private ArrayNode labels(ObjectMapper mapper, String... labels) {
+        ArrayNode json = mapper.createArrayNode();
+        for (String label : labels) {
+            json.add(label);
+        }
+        return json;
+    }
+
+    // Aggregate link of all links between the same devices regardless of
+    // their direction.
+    private class AggLink {
+        Link link; // representative links
+
+        final String key;
+        final Set<Link> links = new HashSet<>();
+
+        AggLink(String key) {
+            this.key = key;
+        }
+
+        void addLink(Link link) {
+            links.add(link);
+            if (this.link == null) {
+                this.link = link;
+            }
+        }
+    }
+
+    // Returns a canonical key for the specified link.
+    static String key(Link link) {
+        String s = id(link.src());
+        String d = id(link.dst());
+        return s.compareTo(d) > 0 ? d + s : s + d;
+    }
+
+    // Returns a formatted string for the element associated with the given
+    // connection point.
+    private static String id(ConnectPoint cp) {
+        return cp.elementId().toString();
+    }
+
+}
diff --git a/web/gui/src/main/webapp/network.js b/web/gui/src/main/webapp/network.js
index c5145ad..6cf1899 100644
--- a/web/gui/src/main/webapp/network.js
+++ b/web/gui/src/main/webapp/network.js
@@ -14,6 +14,7 @@
                 layering: true,
                 collisionPrevention: true
             },
+            XjsonUrl: 'rs/topology/graph',
             jsonUrl: 'network.json',
             iconUrl: {
                 device: 'img/device.png',