Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next
diff --git a/apps/optical/src/main/java/org/onlab/onos/optical/cfg/OpticalConfigProvider.java b/apps/optical/src/main/java/org/onlab/onos/optical/cfg/OpticalConfigProvider.java
index 5c9de15..4b9bc9f 100644
--- a/apps/optical/src/main/java/org/onlab/onos/optical/cfg/OpticalConfigProvider.java
+++ b/apps/optical/src/main/java/org/onlab/onos/optical/cfg/OpticalConfigProvider.java
@@ -368,12 +368,12 @@
     }
 
     @Override
-    public void roleChanged(Device device, MastershipRole newRole) {
+    public void roleChanged(DeviceId device, MastershipRole newRole) {
         // TODO Auto-generated method stub.
     }
 
     @Override
-    public boolean isReachable(Device device) {
+    public boolean isReachable(DeviceId device) {
         return false;
     }
 }
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSession.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSession.java
index af67f1a..901ac90 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSession.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSession.java
@@ -246,11 +246,13 @@
         InetAddress inetAddr;
         if (localAddress instanceof InetSocketAddress) {
             inetAddr = ((InetSocketAddress) localAddress).getAddress();
-            localIp4Address = IpAddress.valueOf(inetAddr.getAddress());
+            localIp4Address = IpAddress.valueOf(IpAddress.Version.INET,
+                                                inetAddr.getAddress());
         }
         if (remoteAddress instanceof InetSocketAddress) {
             inetAddr = ((InetSocketAddress) remoteAddress).getAddress();
-            remoteIp4Address = IpAddress.valueOf(inetAddr.getAddress());
+            remoteIp4Address = IpAddress.valueOf(IpAddress.Version.INET,
+                                                 inetAddr.getAddress());
         }
 
         log.debug("BGP Session Connected from {} on {}",
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSessionManager.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSessionManager.java
index 1b5dfb5..247c95f 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSessionManager.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSessionManager.java
@@ -105,7 +105,8 @@
         if (bgpSession.getLocalAddress() instanceof InetSocketAddress) {
             InetAddress inetAddr =
                 ((InetSocketAddress) bgpSession.getLocalAddress()).getAddress();
-            IpAddress ip4Address = IpAddress.valueOf(inetAddr.getAddress());
+            IpAddress ip4Address = IpAddress.valueOf(IpAddress.Version.INET,
+                                                     inetAddr.getAddress());
             updateMyBgpId(ip4Address);
         }
         return true;
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/AddPointToPointIntentCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/AddPointToPointIntentCommand.java
index e1ede9f..ed98c7e 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/AddPointToPointIntentCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/AddPointToPointIntentCommand.java
@@ -74,7 +74,7 @@
      * @param deviceString string representing the device/port
      * @return port number as a string, empty string if the port is not found
      */
-    private String getPortNumber(String deviceString) {
+    public static String getPortNumber(String deviceString) {
         int slash = deviceString.indexOf('/');
         if (slash <= 0) {
             return "";
@@ -88,7 +88,7 @@
      * @param deviceString string representing the device/port
      * @return device ID string
      */
-    private String getDeviceId(String deviceString) {
+    public static String getDeviceId(String deviceString) {
         int slash = deviceString.indexOf('/');
         if (slash <= 0) {
             return "";
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/IntentsListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/IntentsListCommand.java
index 88548a1..f5c65e5 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/IntentsListCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/IntentsListCommand.java
@@ -20,6 +20,7 @@
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
 import org.onlab.onos.cli.AbstractShellCommand;
 import org.onlab.onos.net.ConnectPoint;
 import org.onlab.onos.net.Link;
@@ -44,6 +45,11 @@
          description = "Lists the inventory of intents and their states")
 public class IntentsListCommand extends AbstractShellCommand {
 
+    @Option(name = "-i", aliases = "--installable", description = "Output Installable Intents",
+            required = false, multiValued = false)
+    private boolean showInstallable = false;
+
+
     @Override
     protected void execute() {
         IntentService service = get(IntentService.class);
@@ -93,7 +99,7 @@
         }
 
         List<Intent> installable = service.getInstallableIntents(intent.id());
-        if (installable != null && !installable.isEmpty()) {
+        if (showInstallable && installable != null && !installable.isEmpty()) {
             print("    installable=%s", installable);
         }
     }
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/ResourceAllocationsCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/ResourceAllocationsCommand.java
new file mode 100644
index 0000000..b91ed3e
--- /dev/null
+++ b/cli/src/main/java/org/onlab/onos/cli/net/ResourceAllocationsCommand.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed 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.cli.net;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.onos.cli.AbstractShellCommand;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.link.LinkService;
+import org.onlab.onos.net.resource.LinkResourceAllocations;
+import org.onlab.onos.net.resource.LinkResourceService;
+
+import static org.onlab.onos.cli.net.AddPointToPointIntentCommand.getDeviceId;
+import static org.onlab.onos.cli.net.AddPointToPointIntentCommand.getPortNumber;
+import static org.onlab.onos.net.DeviceId.deviceId;
+import static org.onlab.onos.net.PortNumber.portNumber;
+
+/**
+ * Lists allocations by link.
+ */
+@Command(scope = "onos", name = "resource-allocations",
+         description = "Lists allocations by link")
+public class ResourceAllocationsCommand extends AbstractShellCommand {
+
+    private static final String FMT = "src=%s/%s, dst=%s/%s, type=%s%s";
+    private static final String COMPACT = "%s/%s-%s/%s";
+
+    @Argument(index = 0, name = "srcString", description = "Link source",
+              required = false, multiValued = false)
+    String srcString = null;
+    @Argument(index = 1, name = "dstString", description = "Link destination",
+              required = false, multiValued = false)
+    String dstString = null;
+
+    @Override
+    protected void execute() {
+        LinkResourceService resourceService = get(LinkResourceService.class);
+        LinkService linkService = get(LinkService.class);
+
+        Iterable<LinkResourceAllocations> itr = null;
+        try {
+            DeviceId ingressDeviceId = deviceId(getDeviceId(srcString));
+            PortNumber ingressPortNumber = portNumber(getPortNumber(srcString));
+            ConnectPoint src = new ConnectPoint(ingressDeviceId, ingressPortNumber);
+
+            DeviceId egressDeviceId = deviceId(getDeviceId(dstString));
+            PortNumber egressPortNumber = portNumber(getPortNumber(dstString));
+            ConnectPoint dst = new ConnectPoint(egressDeviceId, egressPortNumber);
+
+            Link link = linkService.getLink(src, dst);
+
+            itr = resourceService.getAllocations(link);
+
+            for (LinkResourceAllocations allocation : itr) {
+                print("%s", allocation.getResourceAllocation(link));
+            }
+
+        } catch (Exception e) {
+            print("----- Displaying all resource allocations -----", e.getMessage());
+            itr = resourceService.getAllocations();
+            for (LinkResourceAllocations allocation : itr) {
+                print("%s", allocation);
+            }
+
+        }
+    }
+}
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/ResourceAvailableCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/ResourceAvailableCommand.java
new file mode 100644
index 0000000..e67990d
--- /dev/null
+++ b/cli/src/main/java/org/onlab/onos/cli/net/ResourceAvailableCommand.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed 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.cli.net;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.onos.cli.AbstractShellCommand;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.link.LinkService;
+import org.onlab.onos.net.resource.LinkResourceService;
+import org.onlab.onos.net.resource.ResourceRequest;
+
+import static org.onlab.onos.cli.net.AddPointToPointIntentCommand.getDeviceId;
+import static org.onlab.onos.cli.net.AddPointToPointIntentCommand.getPortNumber;
+import static org.onlab.onos.net.DeviceId.deviceId;
+import static org.onlab.onos.net.PortNumber.portNumber;
+
+/**
+ * Lists allocations by link.
+ */
+@Command(scope = "onos", name = "resource-available",
+         description = "Lists available resources by link")
+public class ResourceAvailableCommand extends AbstractShellCommand {
+
+    private static final String FMT = "src=%s/%s, dst=%s/%s, type=%s%s";
+    private static final String COMPACT = "%s/%s-%s/%s";
+
+    @Argument(index = 0, name = "srcString", description = "Link source",
+              required = false, multiValued = false)
+    String srcString = null;
+    @Argument(index = 1, name = "dstString", description = "Link destination",
+              required = false, multiValued = false)
+    String dstString = null;
+
+    @Override
+    protected void execute() {
+        LinkResourceService resourceService = get(LinkResourceService.class);
+        LinkService linkService = get(LinkService.class);
+
+        Iterable<ResourceRequest> itr = null;
+        try {
+            DeviceId ingressDeviceId = deviceId(getDeviceId(srcString));
+            PortNumber ingressPortNumber = portNumber(getPortNumber(srcString));
+            ConnectPoint src = new ConnectPoint(ingressDeviceId, ingressPortNumber);
+
+            DeviceId egressDeviceId = deviceId(getDeviceId(dstString));
+            PortNumber egressPortNumber = portNumber(getPortNumber(dstString));
+            ConnectPoint dst = new ConnectPoint(egressDeviceId, egressPortNumber);
+
+            Link link = linkService.getLink(src, dst);
+
+            itr = resourceService.getAvailableResources(link);
+
+            int lambdaCount = 0;
+            for (ResourceRequest req : itr) {
+                switch (req.type()) {
+                    case LAMBDA:
+                        lambdaCount++;
+                        break;
+                    case BANDWIDTH:
+                        print("%s", req);
+                        break;
+                    default:
+                        break;
+                }
+            }
+            if (lambdaCount > 0) {
+                print("Number of available lambdas: %d", lambdaCount);
+            }
+
+        } catch (Exception e) {
+            print("Invalid link", e.getMessage());
+        }
+    }
+}
diff --git a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index bbccb94..78b1dc1 100644
--- a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -158,7 +158,22 @@
                 <null/>
             </completers>
         </command>
-
+        <command>
+            <action class="org.onlab.onos.cli.net.ResourceAllocationsCommand"/>
+            <completers>
+                <ref component-id="connectPointCompleter"/>
+                <ref component-id="connectPointCompleter"/>
+                <null/>
+            </completers>
+        </command>
+        <command>
+            <action class="org.onlab.onos.cli.net.ResourceAvailableCommand"/>
+            <completers>
+                <ref component-id="connectPointCompleter"/>
+                <ref component-id="connectPointCompleter"/>
+                <null/>
+            </completers>
+        </command>
         <command>
             <action class="org.onlab.onos.cli.net.ClustersListCommand"/>
         </command>
diff --git a/core/api/src/main/java/org/onlab/onos/mastership/MastershipEvent.java b/core/api/src/main/java/org/onlab/onos/mastership/MastershipEvent.java
index dafa273..5447545 100644
--- a/core/api/src/main/java/org/onlab/onos/mastership/MastershipEvent.java
+++ b/core/api/src/main/java/org/onlab/onos/mastership/MastershipEvent.java
@@ -40,7 +40,9 @@
         MASTER_CHANGED,
 
         /**
-         * Signifies that the list of backup nodes has changed.
+         * Signifies that the list of backup nodes has changed. If
+         * the change in the backups list is accompanied by a change in
+         * master, the event is subsumed by MASTER_CHANGED.
          */
         BACKUPS_CHANGED
     }
@@ -49,9 +51,9 @@
      * Creates an event of a given type and for the specified device,
      * role information, and the current time.
      *
-     * @param type   device event type
+     * @param type   mastership event type
      * @param device event device subject
-     * @param info   mastership role information subject
+     * @param info   mastership role information
      */
     public MastershipEvent(Type type, DeviceId device, RoleInfo info) {
         super(type, device);
diff --git a/core/api/src/main/java/org/onlab/onos/net/NetworkResource.java b/core/api/src/main/java/org/onlab/onos/net/NetworkResource.java
index 65bb4e8..faf3241 100644
--- a/core/api/src/main/java/org/onlab/onos/net/NetworkResource.java
+++ b/core/api/src/main/java/org/onlab/onos/net/NetworkResource.java
@@ -16,7 +16,7 @@
 package org.onlab.onos.net;
 
 /**
- * Representation of a network resource.
+ * Representation of a network resource, e.g. a link, lambda, MPLS tag.
  */
 public interface NetworkResource {
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/Tunnel.java b/core/api/src/main/java/org/onlab/onos/net/Tunnel.java
new file mode 100644
index 0000000..d5fad6c
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/Tunnel.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed 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.net;
+
+/**
+ * Abstraction of a generalized network tunnel.
+ */
+public interface Tunnel extends Link {
+
+    /**
+     * Tunnel technology type.
+     */
+    enum Type {
+        MPLS, VLAN, VXLAN, GRE, OPTICAL
+    }
+
+    /**
+     * Network resource backing the tunnel, e.g. lambda, VLAN id, MPLS tag.
+     *
+     * @return backing resource
+     */
+    NetworkResource resource();
+
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/device/DeviceClockProviderService.java b/core/api/src/main/java/org/onlab/onos/net/device/DeviceClockProviderService.java
index 72dc03c..011f2a8 100644
--- a/core/api/src/main/java/org/onlab/onos/net/device/DeviceClockProviderService.java
+++ b/core/api/src/main/java/org/onlab/onos/net/device/DeviceClockProviderService.java
@@ -25,6 +25,14 @@
 public interface DeviceClockProviderService {
 
     /**
+     * Checks if this service can issue Timestamp for specified device.
+     *
+     * @param deviceId device identifier.
+     * @return true if timestamp can be issued for specified device
+     */
+    public boolean isTimestampAvailable(DeviceId deviceId);
+
+    /**
      * Updates the mastership term for the specified deviceId.
      *
      * @param deviceId device identifier.
diff --git a/core/api/src/main/java/org/onlab/onos/net/device/DeviceClockService.java b/core/api/src/main/java/org/onlab/onos/net/device/DeviceClockService.java
index 2104d2e..2214cfd 100644
--- a/core/api/src/main/java/org/onlab/onos/net/device/DeviceClockService.java
+++ b/core/api/src/main/java/org/onlab/onos/net/device/DeviceClockService.java
@@ -24,7 +24,16 @@
 public interface DeviceClockService {
 
     /**
+     * Checks if this service can issue Timestamp for specified device.
+     *
+     * @param deviceId device identifier.
+     * @return true if timestamp can be issued for specified device
+     */
+    public boolean isTimestampAvailable(DeviceId deviceId);
+
+    /**
      * Returns a new timestamp for the specified deviceId.
+     *
      * @param deviceId device identifier.
      * @return timestamp.
      */
diff --git a/core/api/src/main/java/org/onlab/onos/net/device/DeviceProvider.java b/core/api/src/main/java/org/onlab/onos/net/device/DeviceProvider.java
index 8411b09..8dd405a 100644
--- a/core/api/src/main/java/org/onlab/onos/net/device/DeviceProvider.java
+++ b/core/api/src/main/java/org/onlab/onos/net/device/DeviceProvider.java
@@ -16,6 +16,7 @@
 package org.onlab.onos.net.device;
 
 import org.onlab.onos.net.Device;
+import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.net.MastershipRole;
 import org.onlab.onos.net.provider.Provider;
 
@@ -42,16 +43,16 @@
      * Notifies the provider of a mastership role change for the specified
      * device as decided by the core.
      *
-     * @param device  affected device
+     * @param deviceId  device identifier
      * @param newRole newly determined mastership role
      */
-    void roleChanged(Device device, MastershipRole newRole);
+    void roleChanged(DeviceId deviceId, MastershipRole newRole);
 
     /**
      * Checks the reachability (connectivity) of a device from this provider.
      *
-     * @param device device to check
+     * @param deviceId  device identifier
      * @return true if reachable, false otherwise
      */
-    boolean isReachable(Device device);
+    boolean isReachable(DeviceId deviceId);
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/device/DeviceProviderService.java b/core/api/src/main/java/org/onlab/onos/net/device/DeviceProviderService.java
index f1667db..6f71495 100644
--- a/core/api/src/main/java/org/onlab/onos/net/device/DeviceProviderService.java
+++ b/core/api/src/main/java/org/onlab/onos/net/device/DeviceProviderService.java
@@ -61,12 +61,12 @@
     void portStatusChanged(DeviceId deviceId, PortDescription portDescription);
 
     /**
-     * Notifies the core about the providers inability to assert the specified
-     * mastership role on the device.
+     * Notifies the core about the result of a RoleRequest sent to a device.
      *
      * @param deviceId identity of the device
-     * @param role mastership role that was asserted but failed
+     * @param requested mastership role that was requested by the node
+     * @param replied mastership role the switch accepted
      */
-    void unableToAssertRole(DeviceId deviceId, MastershipRole role);
+    void receivedRoleReply(DeviceId deviceId, MastershipRole requested, MastershipRole response);
 
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchEvent.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchEvent.java
index d5c762d..1dbf8bd 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchEvent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchEvent.java
@@ -27,11 +27,14 @@
      */
     public enum Type {
 
+        // Request has been forwarded to MASTER Node
         /**
          * Signifies that a batch operation has been initiated.
          */
         BATCH_OPERATION_REQUESTED,
 
+        // MASTER Node has pushed the batch down to the Device
+        // (e.g., Received barrier reply)
         /**
          * Signifies that a batch operation has completed.
          */
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchRequest.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchRequest.java
index 4a2bcf9..f75c663 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchRequest.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchRequest.java
@@ -25,29 +25,29 @@
 public class FlowRuleBatchRequest {
 
     private final int batchId;
-    private final List<FlowEntry> toAdd;
-    private final List<FlowEntry> toRemove;
+    private final List<FlowRule> toAdd;
+    private final List<FlowRule> toRemove;
 
-    public FlowRuleBatchRequest(int batchId, List<? extends FlowEntry> toAdd, List<? extends FlowEntry> toRemove) {
+    public FlowRuleBatchRequest(int batchId, List<? extends FlowRule> toAdd, List<? extends FlowRule> toRemove) {
         this.batchId = batchId;
         this.toAdd = Collections.unmodifiableList(toAdd);
         this.toRemove = Collections.unmodifiableList(toRemove);
     }
 
-    public List<FlowEntry> toAdd() {
+    public List<FlowRule> toAdd() {
         return toAdd;
     }
 
-    public List<FlowEntry> toRemove() {
+    public List<FlowRule> toRemove() {
         return toRemove;
     }
 
     public FlowRuleBatchOperation asBatchOperation() {
         List<FlowRuleBatchEntry> entries = Lists.newArrayList();
-        for (FlowEntry e : toAdd) {
+        for (FlowRule e : toAdd) {
             entries.add(new FlowRuleBatchEntry(FlowRuleOperation.ADD, e));
         }
-        for (FlowEntry e : toRemove) {
+        for (FlowRule e : toRemove) {
             entries.add(new FlowRuleBatchEntry(FlowRuleOperation.REMOVE, e));
         }
         return new FlowRuleBatchOperation(entries);
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/instructions/Instructions.java b/core/api/src/main/java/org/onlab/onos/net/flow/instructions/Instructions.java
index 0bdff77..0e77f4a 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/instructions/Instructions.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/instructions/Instructions.java
@@ -200,7 +200,7 @@
             }
             if (obj instanceof OutputInstruction) {
                 OutputInstruction that = (OutputInstruction) obj;
-                        Objects.equals(port, that.port);
+                return Objects.equals(port, that.port);
 
             }
             return false;
diff --git a/core/api/src/main/java/org/onlab/onos/net/resource/BandwidthResourceAllocation.java b/core/api/src/main/java/org/onlab/onos/net/resource/BandwidthResourceAllocation.java
index e540db2..62051ad 100644
--- a/core/api/src/main/java/org/onlab/onos/net/resource/BandwidthResourceAllocation.java
+++ b/core/api/src/main/java/org/onlab/onos/net/resource/BandwidthResourceAllocation.java
@@ -15,6 +15,8 @@
  */
 package org.onlab.onos.net.resource;
 
+import com.google.common.base.MoreObjects;
+
 /**
  * Representation of allocated bandwidth resource.
  */
@@ -35,4 +37,11 @@
     public BandwidthResourceAllocation(Bandwidth bandwidth) {
         super(bandwidth);
     }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("bandwidth", bandwidth())
+                .toString();
+    }
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/resource/BandwidthResourceRequest.java b/core/api/src/main/java/org/onlab/onos/net/resource/BandwidthResourceRequest.java
index cced791..52b4112 100644
--- a/core/api/src/main/java/org/onlab/onos/net/resource/BandwidthResourceRequest.java
+++ b/core/api/src/main/java/org/onlab/onos/net/resource/BandwidthResourceRequest.java
@@ -15,6 +15,8 @@
  */
 package org.onlab.onos.net.resource;
 
+import com.google.common.base.MoreObjects;
+
 /**
  * Representation of a request for bandwidth resource.
  */
@@ -53,4 +55,11 @@
     public ResourceType type() {
         return ResourceType.BANDWIDTH;
     }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("bandwidth", bandwidth)
+                .toString();
+    }
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/resource/LambdaResourceAllocation.java b/core/api/src/main/java/org/onlab/onos/net/resource/LambdaResourceAllocation.java
index 1c81f1f..9032cc4 100644
--- a/core/api/src/main/java/org/onlab/onos/net/resource/LambdaResourceAllocation.java
+++ b/core/api/src/main/java/org/onlab/onos/net/resource/LambdaResourceAllocation.java
@@ -15,6 +15,8 @@
  */
 package org.onlab.onos.net.resource;
 
+import com.google.common.base.MoreObjects;
+
 import java.util.Objects;
 
 /**
@@ -64,4 +66,11 @@
         final LambdaResourceAllocation other = (LambdaResourceAllocation) obj;
         return Objects.equals(this.lambda, other.lambda);
     }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("lambda", lambda)
+                .toString();
+    }
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/resource/LambdaResourceRequest.java b/core/api/src/main/java/org/onlab/onos/net/resource/LambdaResourceRequest.java
index 382758a..dab8793 100644
--- a/core/api/src/main/java/org/onlab/onos/net/resource/LambdaResourceRequest.java
+++ b/core/api/src/main/java/org/onlab/onos/net/resource/LambdaResourceRequest.java
@@ -15,6 +15,8 @@
  */
 package org.onlab.onos.net.resource;
 
+import com.google.common.base.MoreObjects;
+
 /**
  * Representation of a request for lambda resource.
  */
@@ -25,4 +27,9 @@
         return ResourceType.LAMBDA;
     }
 
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .toString();
+    }
 }
diff --git a/core/net/src/main/java/org/onlab/onos/net/device/impl/DeviceManager.java b/core/net/src/main/java/org/onlab/onos/net/device/impl/DeviceManager.java
index dfff752..68665e4 100644
--- a/core/net/src/main/java/org/onlab/onos/net/device/impl/DeviceManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/device/impl/DeviceManager.java
@@ -17,6 +17,7 @@
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_MASTERSHIP_CHANGED;
+import static org.onlab.onos.net.MastershipRole.*;
 import static org.slf4j.LoggerFactory.getLogger;
 
 import java.util.List;
@@ -29,7 +30,6 @@
 import org.apache.felix.scr.annotations.Service;
 import org.onlab.onos.cluster.ClusterService;
 import org.onlab.onos.cluster.NodeId;
-import org.onlab.onos.cluster.RoleInfo;
 import org.onlab.onos.event.AbstractListenerRegistry;
 import org.onlab.onos.event.EventDeliveryService;
 import org.onlab.onos.mastership.MastershipEvent;
@@ -59,8 +59,6 @@
 import org.onlab.onos.net.provider.AbstractProviderService;
 import org.slf4j.Logger;
 
-import com.google.common.collect.HashMultimap;
-
 /**
  * Provides implementation of the device SB &amp; NB APIs.
  */
@@ -160,39 +158,15 @@
         return store.isAvailable(deviceId);
     }
 
-    // Applies the specified role to the device; ignores NONE
-    private void applyRole(DeviceId deviceId, MastershipRole newRole) {
-        if (newRole.equals(MastershipRole.NONE)) {
-            return;
-        }
-
-        Device device = store.getDevice(deviceId);
-        // FIXME: Device might not be there yet. (eventual consistent)
-        if (device == null) {
-            return;
-        }
-
-        DeviceProvider provider = getProvider(device.providerId());
-        if (provider != null) {
-            provider.roleChanged(device, newRole);
-            // only trigger event when request was sent to provider
-            // TODO: consider removing this from Device event type?
-            post(new DeviceEvent(DEVICE_MASTERSHIP_CHANGED, device));
-
-            if (newRole.equals(MastershipRole.MASTER)) {
-                provider.triggerProbe(device);
-            }
-        }
-    }
-
     // Check a device for control channel connectivity.
-    private boolean isReachable(Device device) {
-        // FIXME: Device might not be there yet. (eventual consistent)
-        if (device == null) {
+    private boolean isReachable(DeviceId deviceId) {
+        DeviceProvider provider = getProvider(deviceId);
+        if (provider != null) {
+            return provider.isReachable(deviceId);
+        } else {
+            log.error("Provider not found for {}", deviceId);
             return false;
         }
-        DeviceProvider provider = getProvider(device.providerId());
-        return provider.isReachable(device);
     }
 
     @Override
@@ -234,6 +208,32 @@
             super(provider);
         }
 
+        /**
+         * Apply role in reaction to provider event.
+         *
+         * @param deviceId  device identifier
+         * @param newRole   new role to apply to the device
+         * @return true if the request was sent to provider
+         */
+        private boolean applyRole(DeviceId deviceId, MastershipRole newRole) {
+
+            if (newRole.equals(MastershipRole.NONE)) {
+                //no-op
+                return true;
+            }
+
+            DeviceProvider provider = provider();
+            if (provider == null) {
+                log.warn("Provider for {} was not found. Cannot apply role {}", deviceId, newRole);
+                return false;
+            }
+            provider.roleChanged(deviceId, newRole);
+            // not triggering probe when triggered by provider service event
+
+            return true;
+        }
+
+
         @Override
         public void deviceConnected(DeviceId deviceId,
                                     DeviceDescription deviceDescription) {
@@ -242,26 +242,19 @@
             checkValidity();
 
             log.info("Device {} connected", deviceId);
-            // check my Role
-            MastershipRole role = mastershipService.requestRoleFor(deviceId);
-            if (role != MastershipRole.MASTER) {
-                // TODO: Do we need to explicitly tell the Provider that
-                // this instance is no longer the MASTER? probably not
-//                Device device = getDevice(deviceId);
-//                if (device != null) {
-//                    // FIXME roleChanged should take DeviceId instead of Device
-//                    provider().roleChanged(device, role);
-//                }
-                return;
-            }
-            MastershipTerm term = termService.getMastershipTerm(deviceId);
-
             final NodeId myNodeId = clusterService.getLocalNode().id();
+
+            // check my Role
+            mastershipService.requestRoleFor(deviceId);
+            final MastershipTerm term = termService.getMastershipTerm(deviceId);
             if (!myNodeId.equals(term.master())) {
-                // lost mastership after requestRole told this instance was MASTER.
-                log.info("lost mastership before getting term info.");
+                log.info("Role of this node is STANDBY for {}", deviceId);
+                // TODO: Do we need to explicitly tell the Provider that
+                // this instance is not the MASTER
+                applyRole(deviceId, MastershipRole.STANDBY);
                 return;
             }
+            log.info("Role of this node is MASTER for {}", deviceId);
 
             // tell clock provider if this instance is the master
             deviceClockProviderService.setMastershipTerm(deviceId, term);
@@ -269,24 +262,15 @@
             DeviceEvent event = store.createOrUpdateDevice(provider().id(),
                                                            deviceId, deviceDescription);
 
+            applyRole(deviceId, MastershipRole.MASTER);
+
             // If there was a change of any kind, tell the provider
             // that this instance is the master.
-            // Note: event can be null, if mastership was lost between
-            // roleRequest and store update calls.
             if (event != null) {
-                // TODO: Check switch reconnected case. Is it assured that
-                //       event will never be null?
-                //       Could there be a situation MastershipService told this
-                //       instance is the new Master, but
-                //       event returned from the store is null?
-
-                // FIXME: 1st argument should be deviceId, to allow setting
-                //        certain roles even if the store returned null.
-                log.info("event: {} {}", event.type(), event);
-                provider().roleChanged(event.subject(), role);
+                log.trace("event: {} {}", event.type(), event);
                 post(event);
             } else {
-                log.info("No event to publish");
+                post(new DeviceEvent(DEVICE_MASTERSHIP_CHANGED, store.getDevice(deviceId)));
             }
         }
 
@@ -295,6 +279,7 @@
             checkNotNull(deviceId, DEVICE_ID_NULL);
             checkValidity();
 
+            log.info("Device {} disconnected from this node", deviceId);
 
             DeviceEvent event = null;
             try {
@@ -318,18 +303,18 @@
                 final NodeId myNodeId = clusterService.getLocalNode().id();
                 // TODO: Move this type of check inside device clock manager, etc.
                 if (myNodeId.equals(term.master())) {
-                    log.info("Marking {} offline", deviceId);
+                    log.info("Retry marking {} offline", deviceId);
                     deviceClockProviderService.setMastershipTerm(deviceId, term);
                     event = store.markOffline(deviceId);
                 } else {
-                    log.error("Failed again marking {} offline. {}", deviceId, role);
+                    log.info("Failed again marking {} offline. {}", deviceId, role);
                 }
             } finally {
                 //relinquish master role and ability to be backup.
                 mastershipService.relinquishMastership(deviceId);
 
                 if (event != null) {
-                    log.info("Device {} disconnected", deviceId);
+                    log.info("Device {} disconnected from cluster", deviceId);
                     post(event);
                 }
             }
@@ -343,6 +328,13 @@
                          "Port descriptions list cannot be null");
             checkValidity();
 
+            if (!deviceClockProviderService.isTimestampAvailable(deviceId)) {
+                // Never been a master for this device
+                // any update will be ignored.
+                log.trace("Ignoring {} port updates on standby node. {}", deviceId, portDescriptions);
+                return;
+            }
+
             List<DeviceEvent> events = store.updatePorts(this.provider().id(),
                                                          deviceId, portDescriptions);
             for (DeviceEvent event : events) {
@@ -357,6 +349,13 @@
             checkNotNull(portDescription, PORT_DESCRIPTION_NULL);
             checkValidity();
 
+            if (!deviceClockProviderService.isTimestampAvailable(deviceId)) {
+                // Never been a master for this device
+                // any update will be ignored.
+                log.trace("Ignoring {} port update on standby node. {}", deviceId, portDescription);
+                return;
+            }
+
             final DeviceEvent event = store.updatePortStatus(this.provider().id(),
                                                              deviceId, portDescription);
             if (event != null) {
@@ -367,16 +366,48 @@
         }
 
         @Override
-        public void unableToAssertRole(DeviceId deviceId, MastershipRole role) {
+        public void receivedRoleReply(
+                DeviceId deviceId, MastershipRole requested, MastershipRole response) {
+            // Several things can happen here:
+            // 1. request and response match
+            // 2. request and response don't match
+            // 3. MastershipRole and requested match (and 1 or 2 are true)
+            // 4. MastershipRole and requested don't match (and 1 or 2 are true)
+            //
+            // 2, 4, and 3 with case 2 are failure modes.
+
             // FIXME: implement response to this notification
-            log.warn("Failed to assert role [{}] onto Device {}", role,
-                     deviceId);
-            if (role == MastershipRole.MASTER) {
+
+            log.info("got reply to a role request for {}: asked for {}, and got {}",
+                    deviceId, requested, response);
+
+            if (requested == null && response == null) {
+                // something was off with DeviceProvider, maybe check channel too?
+                log.warn("Failed to assert role [{}] onto Device {}", requested, deviceId);
                 mastershipService.relinquishMastership(deviceId);
-                // TODO: Shouldn't we be triggering event?
-                //final Device device = getDevice(deviceId);
-                //post(new DeviceEvent(DEVICE_MASTERSHIP_CHANGED, device));
+                return;
             }
+
+            if (requested.equals(response)) {
+                if (requested.equals(mastershipService.getLocalRole(deviceId))) {
+
+                    return;
+                } else {
+                    return;
+                    // FIXME roleManager got the device to comply, but doesn't agree with
+                    // the store; use the store's view, then try to reassert.
+                }
+            } else {
+                // we didn't get back what we asked for. Reelect someone else.
+                log.warn("Failed to assert role [{}] onto Device {}", response, deviceId);
+                if (response == MastershipRole.MASTER) {
+                    mastershipService.relinquishMastership(deviceId);
+                    // TODO: Shouldn't we be triggering event?
+                    //final Device device = getDevice(deviceId);
+                    //post(new DeviceEvent(DEVICE_MASTERSHIP_CHANGED, device));
+                }
+            }
+
         }
     }
 
@@ -390,118 +421,134 @@
     // Intercepts mastership events
     private class InternalMastershipListener implements MastershipListener {
 
-        // random cache size
-        private final int cacheSize = 5;
-        // temporarily stores term number + events to check for duplicates. A hack.
-        private HashMultimap<Integer, RoleInfo> eventCache =
-                HashMultimap.create();
+        // Applies the specified role to the device; ignores NONE
+        /**
+         * Apply role in reaction to mastership event.
+         *
+         * @param deviceId  device identifier
+         * @param newRole   new role to apply to the device
+         * @return true if the request was sent to provider
+         */
+        private boolean applyRole(DeviceId deviceId, MastershipRole newRole) {
+            if (newRole.equals(MastershipRole.NONE)) {
+                //no-op
+                return true;
+            }
+
+            Device device = store.getDevice(deviceId);
+            // FIXME: Device might not be there yet. (eventual consistent)
+            // FIXME relinquish role
+            if (device == null) {
+                log.warn("{} was not there. Cannot apply role {}", deviceId, newRole);
+                return false;
+            }
+
+            DeviceProvider provider = getProvider(device.providerId());
+            if (provider == null) {
+                log.warn("Provider for {} was not found. Cannot apply role {}", deviceId, newRole);
+                return false;
+            }
+            provider.roleChanged(deviceId, newRole);
+
+            if (newRole.equals(MastershipRole.MASTER)) {
+                // only trigger event when request was sent to provider
+                // TODO: consider removing this from Device event type?
+                post(new DeviceEvent(DEVICE_MASTERSHIP_CHANGED, device));
+
+                provider.triggerProbe(device);
+            }
+            return true;
+        }
 
         @Override
         public void event(MastershipEvent event) {
+
+            if (event.type() != MastershipEvent.Type.MASTER_CHANGED) {
+                // Don't care if backup list changed.
+                return;
+            }
+
             final DeviceId did = event.subject();
             final NodeId myNodeId = clusterService.getLocalNode().id();
 
+            // myRole suggested by MastershipService
+            MastershipRole myNextRole;
             if (myNodeId.equals(event.roleInfo().master())) {
+                // confirm latest info
                 MastershipTerm term = termService.getMastershipTerm(did);
-
-                // TODO duplicate suppression should probably occur in the MastershipManager
-                // itself, so listeners that can't deal with duplicates don't have to
-                // so this check themselves.
-//                if (checkDuplicate(event.roleInfo(), term.termNumber())) {
-//                    return;
-//                }
-
-                if (!myNodeId.equals(term.master())) {
-                    // something went wrong in consistency, let go
-                    log.warn("Mastership has changed after this event."
-                                     + "Term Service suggests {} for {}", term, did);
-                    // FIXME: Is it possible to let go of MASTER role
-                    //        but remain on STANDBY list?
-                    mastershipService.relinquishMastership(did);
-                    applyRole(did, MastershipRole.STANDBY);
-                    return;
-                }
-
-                // only set the new term if I am the master
-                deviceClockProviderService.setMastershipTerm(did, term);
-
-                // if the device is null here, we are the first master to claim the
-                // device. No worries, the DeviceManager will create one soon.
-                Device device = getDevice(did);
-                if ((device != null) && !isAvailable(did)) {
-                    if (!isReachable(device)) {
-                        log.warn("Device {} has disconnected after this event", did);
-                        mastershipService.relinquishMastership(did);
-                        return;
-                    }
-                    //flag the device as online. Is there a better way to do this?
-                    DeviceEvent devEvent =
-                            store.createOrUpdateDevice(device.providerId(), did,
-                                                       new DefaultDeviceDescription(
-                                                               did.uri(), device.type(), device.manufacturer(),
-                                                               device.hwVersion(), device.swVersion(),
-                                                               device.serialNumber(), device.chassisId()));
-                    post(devEvent);
-                }
-                applyRole(did, MastershipRole.MASTER);
-            } else if (event.roleInfo().backups().contains(myNodeId)) {
-                if (!isReachable(getDevice(did))) {
-                    log.warn("Device {} has disconnected after this event", did);
-                    mastershipService.relinquishMastership(did);
-                    return;
-                }
-                applyRole(did, MastershipRole.STANDBY);
-            } else {
-                // Event suggests that this Node has no connection to this Device
-                // confirm.
-                final Device device = getDevice(did);
-                if (!isReachable(device)) {
-                    // not connection to device, as expected
-                    return;
-                }
-                // connection seems to exist
-                log.info("Detected mastership info mismatch, requesting Role");
-                mastershipService.requestRoleFor(did);
-                final MastershipTerm term = termService.getMastershipTerm(did);
-                if (myNodeId.equals(term.master())) {
-                    // became MASTER
-                    // TODO: consider slicing out method for applying MASTER role
+                final boolean iHaveControl = myNodeId.equals(term.master());
+                if (iHaveControl) {
                     deviceClockProviderService.setMastershipTerm(did, term);
-
-                    //flag the device as online. Is there a better way to do this?
-                    DeviceEvent devEvent =
-                            store.createOrUpdateDevice(device.providerId(), did,
-                                                       new DefaultDeviceDescription(
-                                                               did.uri(), device.type(), device.manufacturer(),
-                                                               device.hwVersion(), device.swVersion(),
-                                                               device.serialNumber(), device.chassisId()));
-                    applyRole(did, MastershipRole.MASTER);
-                    post(devEvent);
+                    myNextRole = MASTER;
                 } else {
-                    applyRole(did, MastershipRole.STANDBY);
+                    myNextRole = STANDBY;
+                }
+            } else if (event.roleInfo().backups().contains(myNodeId)) {
+                myNextRole = STANDBY;
+            } else {
+                myNextRole = NONE;
+            }
+
+
+            final boolean isReachable = isReachable(did);
+            if (!isReachable) {
+                // device is not connected to this node
+                if (myNextRole != NONE) {
+                    log.warn("Node was instructed to be {} role for {}, "
+                            + "but this node cannot reach the device.  "
+                            + "Relinquishing role.  ",
+                             myNextRole, did);
+                    mastershipService.relinquishMastership(did);
+                    // FIXME disconnect?
+                }
+                return;
+            }
+
+            // device is connected to this node:
+
+            if (myNextRole == NONE) {
+                mastershipService.requestRoleFor(did);
+                MastershipTerm term = termService.getMastershipTerm(did);
+                if (myNodeId.equals(term.master())) {
+                    myNextRole = MASTER;
+                } else {
+                    myNextRole = STANDBY;
                 }
             }
-        }
 
-        // checks for duplicate event, returning true if one is found.
-        private boolean checkDuplicate(RoleInfo roleInfo, int term) {
-            // turning off duplicate check
-            return false;
-//            synchronized (eventCache) {
-//                if (eventCache.get(term).contains(roleInfo)) {
-//                    log.info("duplicate event detected; ignoring");
-//                    return true;
-//                } else {
-//                    eventCache.put(term, roleInfo);
-//                    // purge by-term oldest entries to keep the cache size under limit
-//                    if (eventCache.size() > cacheSize) {
-//                        eventCache.removeAll(term - cacheSize);
-//                    }
-//                    return false;
-//                }
-//            }
+            switch (myNextRole) {
+            case MASTER:
+                final Device device = getDevice(did);
+                if ((device != null) && !isAvailable(did)) {
+                    //flag the device as online. Is there a better way to do this?
+                    DefaultDeviceDescription deviceDescription
+                        = new DefaultDeviceDescription(did.uri(),
+                                                       device.type(),
+                                                       device.manufacturer(),
+                                                       device.hwVersion(),
+                                                       device.swVersion(),
+                                                       device.serialNumber(),
+                                                       device.chassisId());
+                    DeviceEvent devEvent =
+                            store.createOrUpdateDevice(device.providerId(), did,
+                                                       deviceDescription);
+                    post(devEvent);
+                }
+                // TODO: should apply role only if there is mismatch
+                log.info("Applying role {} to {}", myNextRole, did);
+                applyRole(did, MASTER);
+                break;
+            case STANDBY:
+                log.info("Applying role {} to {}", myNextRole, did);
+                applyRole(did, STANDBY);
+                break;
+            case NONE:
+            default:
+                // should never reach here
+                log.error("You didn't see anything. I did not exist.");
+                break;
+            }
         }
-
     }
 
     // Store delegate to re-post events emitted from the store.
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 4fe9022..bd7fb94 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
@@ -371,10 +371,11 @@
             final FlowRuleBatchRequest request = event.subject();
             switch (event.type()) {
             case BATCH_OPERATION_REQUESTED:
-                for (FlowEntry entry : request.toAdd()) {
+                // Request has been forwarded to MASTER Node, and was
+                for (FlowRule entry : request.toAdd()) {
                     eventDispatcher.post(new FlowRuleEvent(FlowRuleEvent.Type.RULE_ADD_REQUESTED, entry));
                 }
-                for (FlowEntry entry : request.toRemove()) {
+                for (FlowRule entry : request.toRemove()) {
                     eventDispatcher.post(new FlowRuleEvent(FlowRuleEvent.Type.RULE_REMOVE_REQUESTED, entry));
                 }
                 // FIXME: what about op.equals(FlowRuleOperation.MODIFY) ?
@@ -392,21 +393,15 @@
                                                                                   Futures.getUnchecked(result)));
                     }
                 }, futureListeners);
+                break;
 
-                break;
             case BATCH_OPERATION_COMPLETED:
-                Set<FlowRule> failedItems = event.result().failedItems();
-                for (FlowEntry entry : request.toAdd()) {
-                    if (!failedItems.contains(entry)) {
-                        eventDispatcher.post(new FlowRuleEvent(FlowRuleEvent.Type.RULE_ADDED, entry));
-                    }
-                }
-                for (FlowEntry entry : request.toRemove()) {
-                    if (!failedItems.contains(entry)) {
-                            eventDispatcher.post(new FlowRuleEvent(FlowRuleEvent.Type.RULE_REMOVED, entry));
-                    }
-                }
+                // MASTER Node has pushed the batch down to the Device
+
+                // Note: RULE_ADDED will be posted
+                // when Flow was actually confirmed by stats reply.
                 break;
+
             default:
                 break;
             }
diff --git a/core/net/src/main/java/org/onlab/onos/net/host/impl/HostMonitor.java b/core/net/src/main/java/org/onlab/onos/net/host/impl/HostMonitor.java
index f28dcdb..a6af018 100644
--- a/core/net/src/main/java/org/onlab/onos/net/host/impl/HostMonitor.java
+++ b/core/net/src/main/java/org/onlab/onos/net/host/impl/HostMonitor.java
@@ -67,6 +67,7 @@
     private final ConcurrentMap<ProviderId, HostProvider> hostProviders;
 
     private static final long DEFAULT_PROBE_RATE = 30000; // milliseconds
+    private static final byte[] ZERO_MAC_ADDRESS = MacAddress.ZERO.toBytes();
     private long probeRate = DEFAULT_PROBE_RATE;
 
     private Timeout timeout;
@@ -215,15 +216,15 @@
            .setProtocolAddressLength((byte) IpAddress.INET_BYTE_LENGTH)
            .setOpCode(ARP.OP_REQUEST);
 
-        arp.setSenderHardwareAddress(sourceMac.getAddress())
+        arp.setSenderHardwareAddress(sourceMac.toBytes())
            .setSenderProtocolAddress(sourceIp.toOctets())
-           .setTargetHardwareAddress(MacAddress.ZERO_MAC_ADDRESS)
+           .setTargetHardwareAddress(ZERO_MAC_ADDRESS)
            .setTargetProtocolAddress(targetIp.toOctets());
 
         Ethernet ethernet = new Ethernet();
         ethernet.setEtherType(Ethernet.TYPE_ARP)
-                .setDestinationMACAddress(MacAddress.BROADCAST_MAC)
-                .setSourceMACAddress(sourceMac.getAddress())
+                .setDestinationMACAddress(MacAddress.BROADCAST)
+                .setSourceMACAddress(sourceMac)
                 .setPayload(arp);
 
         return ethernet;
diff --git a/core/net/src/main/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManager.java b/core/net/src/main/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManager.java
index 953e88a..0aae62d 100644
--- a/core/net/src/main/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManager.java
@@ -132,7 +132,8 @@
         // for one of our external addresses.
         if (isOutsidePort(inPort)) {
             IpAddress target =
-                IpAddress.valueOf(arp.getTargetProtocolAddress());
+                IpAddress.valueOf(IpAddress.Version.INET,
+                                  arp.getTargetProtocolAddress());
             PortAddresses addresses =
                 hostService.getAddressBindingsForPort(inPort);
 
@@ -149,7 +150,8 @@
             // it could be a request from an internal host to an external
             // address. Forward it over to the correct port.
             IpAddress source =
-                IpAddress.valueOf(arp.getSenderProtocolAddress());
+                IpAddress.valueOf(IpAddress.Version.INET,
+                                  arp.getSenderProtocolAddress());
             PortAddresses sourceAddresses = findPortInSubnet(source);
             if (sourceAddresses != null) {
                 for (InterfaceIpAddress ia : sourceAddresses.ipAddresses()) {
@@ -164,8 +166,9 @@
         // Continue with normal proxy ARP case
 
         VlanId vlan = VlanId.vlanId(eth.getVlanID());
-        Set<Host> hosts = hostService.getHostsByIp(IpAddress.valueOf(arp
-                .getTargetProtocolAddress()));
+        Set<Host> hosts =
+            hostService.getHostsByIp(IpAddress.valueOf(IpAddress.Version.INET,
+                                        arp.getTargetProtocolAddress()));
 
         Host dst = null;
         Host src = hostService.getHost(HostId.hostId(eth.getSourceMAC(),
@@ -357,8 +360,8 @@
             Ethernet request) {
 
         Ethernet eth = new Ethernet();
-        eth.setDestinationMACAddress(request.getSourceMACAddress());
-        eth.setSourceMACAddress(srcMac.getAddress());
+        eth.setDestinationMACAddress(request.getSourceMAC());
+        eth.setSourceMACAddress(srcMac);
         eth.setEtherType(Ethernet.TYPE_ARP);
         eth.setVlanID(request.getVlanID());
 
@@ -369,7 +372,7 @@
 
         arp.setProtocolAddressLength((byte) IpAddress.INET_BYTE_LENGTH);
         arp.setHardwareAddressLength((byte) Ethernet.DATALAYER_ADDRESS_LENGTH);
-        arp.setSenderHardwareAddress(srcMac.getAddress());
+        arp.setSenderHardwareAddress(srcMac.toBytes());
         arp.setTargetHardwareAddress(request.getSourceMACAddress());
 
         arp.setTargetProtocolAddress(((ARP) request.getPayload())
diff --git a/core/net/src/main/java/org/onlab/onos/net/resource/impl/DefaultLinkResourceAllocations.java b/core/net/src/main/java/org/onlab/onos/net/resource/impl/DefaultLinkResourceAllocations.java
index 28ace88..13e02e5 100644
--- a/core/net/src/main/java/org/onlab/onos/net/resource/impl/DefaultLinkResourceAllocations.java
+++ b/core/net/src/main/java/org/onlab/onos/net/resource/impl/DefaultLinkResourceAllocations.java
@@ -15,11 +15,7 @@
  */
 package org.onlab.onos.net.resource.impl;
 
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Map;
-import java.util.Set;
-
+import com.google.common.base.MoreObjects;
 import org.onlab.onos.net.Link;
 import org.onlab.onos.net.intent.IntentId;
 import org.onlab.onos.net.resource.LinkResourceAllocations;
@@ -28,6 +24,11 @@
 import org.onlab.onos.net.resource.ResourceRequest;
 import org.onlab.onos.net.resource.ResourceType;
 
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
 /**
  * Implementation of {@link LinkResourceAllocations}.
  */
@@ -38,11 +39,11 @@
     /**
      * Creates a new link resource allocations.
      *
-     * @param request requested resources
+     * @param request     requested resources
      * @param allocations allocated resources
      */
     DefaultLinkResourceAllocations(LinkResourceRequest request,
-            Map<Link, Set<ResourceAllocation>> allocations) {
+                                   Map<Link, Set<ResourceAllocation>> allocations) {
         this.request = request;
         this.allocations = allocations;
     }
@@ -76,4 +77,10 @@
         return result;
     }
 
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("allocations", allocations)
+                .toString();
+    }
 }
diff --git a/core/net/src/test/java/org/onlab/onos/net/device/impl/DeviceManagerTest.java b/core/net/src/test/java/org/onlab/onos/net/device/impl/DeviceManagerTest.java
index 25fa922..d278c24 100644
--- a/core/net/src/test/java/org/onlab/onos/net/device/impl/DeviceManagerTest.java
+++ b/core/net/src/test/java/org/onlab/onos/net/device/impl/DeviceManagerTest.java
@@ -19,7 +19,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.onlab.onos.cluster.ClusterEventListener;
 import org.onlab.onos.cluster.ClusterService;
@@ -181,16 +180,6 @@
         assertEquals("incorrect role", MastershipRole.MASTER, service.getRole(DID1));
     }
 
-    @Ignore("disabled until we settle the device-mastership wiring")
-    @Test
-    public void setRole() throws InterruptedException {
-        connectDevice(DID1, SW1);
-        validateEvents(DEVICE_ADDED, DEVICE_MASTERSHIP_CHANGED);
-        assertEquals("incorrect role", MastershipRole.STANDBY, service.getRole(DID1));
-        assertEquals("incorrect device", DID1, provider.deviceReceived.id());
-        assertEquals("incorrect role", MastershipRole.STANDBY, provider.roleReceived);
-    }
-
     @Test
     public void updatePorts() {
         connectDevice(DID1, SW1);
@@ -262,7 +251,7 @@
 
 
     private class TestProvider extends AbstractProvider implements DeviceProvider {
-        private Device deviceReceived;
+        private DeviceId deviceReceived;
         private MastershipRole roleReceived;
 
         public TestProvider() {
@@ -274,13 +263,13 @@
         }
 
         @Override
-        public void roleChanged(Device device, MastershipRole newRole) {
+        public void roleChanged(DeviceId device, MastershipRole newRole) {
             deviceReceived = device;
             roleReceived = newRole;
         }
 
         @Override
-        public boolean isReachable(Device device) {
+        public boolean isReachable(DeviceId device) {
             return false;
         }
     }
@@ -360,9 +349,16 @@
     private final class TestClockProviderService implements
             DeviceClockProviderService {
 
+        private Set<DeviceId> registerdBefore = Sets.newConcurrentHashSet();
+
         @Override
         public void setMastershipTerm(DeviceId deviceId, MastershipTerm term) {
-            // TODO Auto-generated method stub
+            registerdBefore.add(deviceId);
+        }
+
+        @Override
+        public boolean isTimestampAvailable(DeviceId deviceId) {
+            return registerdBefore.contains(deviceId);
         }
     }
 }
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 b986d6d..f67d992 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
@@ -148,7 +148,7 @@
         int i = 0;
         System.err.println("events :" + listener.events);
         for (FlowRuleEvent e : listener.events) {
-            assertTrue("unexpected event", e.type().equals(events[i]));
+            assertEquals("unexpected event", events[i], e.type());
             i++;
         }
 
@@ -178,15 +178,13 @@
                        RULE_ADDED, RULE_ADDED);
 
         addFlowRule(1);
+        System.err.println("events :" + listener.events);
         assertEquals("should still be 2 rules", 2, flowCount());
 
         providerService.pushFlowMetrics(DID, ImmutableList.of(fe1));
         validateEvents(RULE_UPDATED);
     }
 
-
-    // TODO: If preserving iteration order is a requirement, redo FlowRuleStore.
-    //backing store is sensitive to the order of additions/removals
     private boolean validateState(Map<FlowRule, FlowEntryState> expected) {
         Map<FlowRule, FlowEntryState> expectedToCheck = new HashMap<>(expected);
         Iterable<FlowEntry> rules = service.getFlowEntries(DID);
@@ -539,17 +537,17 @@
 
             @Override
             public boolean cancel(boolean mayInterruptIfRunning) {
-                return true;
+                return false;
             }
 
             @Override
             public boolean isCancelled() {
-                return true;
+                return false;
             }
 
             @Override
             public boolean isDone() {
-                return false;
+                return true;
             }
 
             @Override
@@ -562,12 +560,14 @@
             public CompletedBatchOperation get(long timeout, TimeUnit unit)
                     throws InterruptedException,
                     ExecutionException, TimeoutException {
-                return null;
+                return new CompletedBatchOperation(true, Collections.<FlowRule>emptySet());
             }
 
             @Override
             public void addListener(Runnable task, Executor executor) {
-                // TODO: add stuff.
+                if (isDone()) {
+                    executor.execute(task);
+                }
             }
         }
 
diff --git a/core/net/src/test/java/org/onlab/onos/net/host/impl/HostMonitorTest.java b/core/net/src/test/java/org/onlab/onos/net/host/impl/HostMonitorTest.java
index 7405de2..654cab1 100644
--- a/core/net/src/test/java/org/onlab/onos/net/host/impl/HostMonitorTest.java
+++ b/core/net/src/test/java/org/onlab/onos/net/host/impl/HostMonitorTest.java
@@ -20,11 +20,9 @@
 import static org.easymock.EasyMock.expectLastCall;
 import static org.easymock.EasyMock.replay;
 import static org.easymock.EasyMock.verify;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
@@ -155,17 +153,20 @@
         Instruction instruction = packet.treatment().instructions().get(0);
         assertTrue(instruction instanceof OutputInstruction);
         OutputInstruction oi = (OutputInstruction) instruction;
-        assertTrue(oi.port().equals(portNum));
+        assertEquals(portNum, oi.port());
 
         // Check the output packet is correct (well the important bits anyway)
         Ethernet eth = new Ethernet();
-        eth.deserialize(packet.data().array(), 0, packet.data().array().length);
+        final byte[] pktData = new byte[packet.data().remaining()];
+        packet.data().get(pktData);
+        eth.deserialize(pktData, 0, pktData.length);
         ARP arp = (ARP) eth.getPayload();
-        assertTrue(Arrays.equals(arp.getSenderProtocolAddress(),
-                                 SOURCE_ADDR.toOctets()));
-        assertTrue(Arrays.equals(arp.getSenderHardwareAddress(), sourceMac.toBytes()));
-        assertTrue(Arrays.equals(arp.getTargetProtocolAddress(),
-                                 TARGET_IP_ADDR.toOctets()));
+        assertArrayEquals(SOURCE_ADDR.toOctets(),
+                          arp.getSenderProtocolAddress());
+        assertArrayEquals(sourceMac.toBytes(),
+                          arp.getSenderHardwareAddress());
+        assertArrayEquals(TARGET_IP_ADDR.toOctets(),
+                          arp.getTargetProtocolAddress());
     }
 
     class TestPacketService implements PacketService {
diff --git a/core/net/src/test/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManagerTest.java b/core/net/src/test/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManagerTest.java
index f720535..e82151e 100644
--- a/core/net/src/test/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManagerTest.java
+++ b/core/net/src/test/java/org/onlab/onos/net/proxyarp/impl/ProxyArpManagerTest.java
@@ -19,12 +19,9 @@
 import static org.easymock.EasyMock.createMock;
 import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.replay;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
@@ -91,6 +88,7 @@
     private static final PortNumber P1 = PortNumber.portNumber(1);
     private static final HostLocation LOC1 = new HostLocation(DID1, P1, 123L);
     private static final HostLocation LOC2 = new HostLocation(DID2, P1, 123L);
+    private static final byte[] ZERO_MAC_ADDRESS = MacAddress.ZERO.toBytes();
 
     private ProxyArpManager proxyArp;
 
@@ -483,7 +481,7 @@
      */
     private void verifyPacketOut(Ethernet expected, ConnectPoint outPort,
             OutboundPacket actual) {
-        assertTrue(Arrays.equals(expected.serialize(), actual.data().array()));
+        assertArrayEquals(expected.serialize(), actual.data().array());
         assertEquals(1, actual.treatment().instructions().size());
         assertEquals(outPort.deviceId(), actual.sendThrough());
         Instruction instruction = actual.treatment().instructions().get(0);
@@ -520,12 +518,12 @@
         Ethernet eth = new Ethernet();
 
         if (dstMac == null) {
-            eth.setDestinationMACAddress(MacAddress.BROADCAST_MAC);
+            eth.setDestinationMACAddress(MacAddress.BROADCAST);
         } else {
-            eth.setDestinationMACAddress(dstMac.getAddress());
+            eth.setDestinationMACAddress(dstMac);
         }
 
-        eth.setSourceMACAddress(srcMac.getAddress());
+        eth.setSourceMACAddress(srcMac);
         eth.setEtherType(Ethernet.TYPE_ARP);
         eth.setVlanID(VLAN1.toShort());
 
@@ -536,12 +534,12 @@
 
         arp.setProtocolAddressLength((byte) IpAddress.INET_BYTE_LENGTH);
         arp.setHardwareAddressLength((byte) Ethernet.DATALAYER_ADDRESS_LENGTH);
-        arp.setSenderHardwareAddress(srcMac.getAddress());
+        arp.setSenderHardwareAddress(srcMac.toBytes());
 
         if (dstMac == null) {
-            arp.setTargetHardwareAddress(MacAddress.ZERO_MAC_ADDRESS);
+            arp.setTargetHardwareAddress(ZERO_MAC_ADDRESS);
         } else {
-            arp.setTargetHardwareAddress(dstMac.getAddress());
+            arp.setTargetHardwareAddress(dstMac.toBytes());
         }
 
         arp.setSenderProtocolAddress(srcIp.toOctets());
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/DistributedClusterStore.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/DistributedClusterStore.java
index e3822ad..f372ced 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/DistributedClusterStore.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/impl/DistributedClusterStore.java
@@ -147,8 +147,7 @@
     }
 
     private IpAddress memberAddress(Member member) {
-        byte[] address = member.getSocketAddress().getAddress().getAddress();
-        return IpAddress.valueOf(address);
+        return IpAddress.valueOf(member.getSocketAddress().getAddress());
     }
 
     // Interceptor for membership events.
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 540de77..79612c9 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
@@ -85,7 +85,6 @@
         try {
             netty.activate();
         } catch (Exception e) {
-            // TODO Auto-generated catch block
             log.error("NettyMessagingService#activate", e);
         }
         messagingService = netty;
@@ -95,6 +94,12 @@
     @Deactivate
     public void deactivate() {
         // TODO: cleanup messageingService if needed.
+        // FIXME: workaround until it becomes a service.
+        try {
+            ((NettyMessagingService) messagingService).deactivate();
+        } catch (Exception e) {
+            log.error("NettyMessagingService#deactivate", e);
+        }
         log.info("Stopped");
     }
 
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/DeviceClockManager.java b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/DeviceClockManager.java
index f7f24e9..486d41e 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/DeviceClockManager.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/DeviceClockManager.java
@@ -72,4 +72,9 @@
         log.info("adding term info {} {}", deviceId, term.master());
         deviceMastershipTerms.put(deviceId, term);
     }
+
+    @Override
+    public boolean isTimestampAvailable(DeviceId deviceId) {
+        return deviceMastershipTerms.containsKey(deviceId);
+    }
 }
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/GossipDeviceStore.java b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/GossipDeviceStore.java
index 5449277..66366c1 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/GossipDeviceStore.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/GossipDeviceStore.java
@@ -1216,7 +1216,7 @@
         @Override
         public void handle(ClusterMessage message) {
 
-            log.info("Received device update event from peer: {}", message.sender());
+            log.debug("Received device update event from peer: {}", message.sender());
             InternalDeviceEvent event = (InternalDeviceEvent) SERIALIZER.decode(message.payload());
 
             ProviderId providerId = event.providerId();
@@ -1231,7 +1231,7 @@
         @Override
         public void handle(ClusterMessage message) {
 
-            log.info("Received device offline event from peer: {}", message.sender());
+            log.debug("Received device offline event from peer: {}", message.sender());
             InternalDeviceOfflineEvent event = (InternalDeviceOfflineEvent) SERIALIZER.decode(message.payload());
 
             DeviceId deviceId = event.deviceId();
@@ -1245,7 +1245,7 @@
         @Override
         public void handle(ClusterMessage message) {
 
-            log.info("Received device removed event from peer: {}", message.sender());
+            log.debug("Received device removed event from peer: {}", message.sender());
             InternalDeviceRemovedEvent event = (InternalDeviceRemovedEvent) SERIALIZER.decode(message.payload());
 
             DeviceId deviceId = event.deviceId();
@@ -1259,13 +1259,19 @@
         @Override
         public void handle(ClusterMessage message) {
 
-            log.info("Received port update event from peer: {}", message.sender());
+            log.debug("Received port update event from peer: {}", message.sender());
             InternalPortEvent event = (InternalPortEvent) SERIALIZER.decode(message.payload());
 
             ProviderId providerId = event.providerId();
             DeviceId deviceId = event.deviceId();
             Timestamped<List<PortDescription>> portDescriptions = event.portDescriptions();
 
+            if (getDevice(deviceId) == null) {
+                log.info("{} not found on this node yet, ignoring.", deviceId);
+                // Note: dropped information will be recovered by anti-entropy
+                return;
+            }
+
             notifyDelegate(updatePortsInternal(providerId, deviceId, portDescriptions));
         }
     }
@@ -1274,14 +1280,19 @@
         @Override
         public void handle(ClusterMessage message) {
 
-            log.info("Received port status update event from peer: {}", message.sender());
+            log.debug("Received port status update event from peer: {}", message.sender());
             InternalPortStatusEvent event = (InternalPortStatusEvent) SERIALIZER.decode(message.payload());
-            log.info("{}", event);
 
             ProviderId providerId = event.providerId();
             DeviceId deviceId = event.deviceId();
             Timestamped<PortDescription> portDescription = event.portDescription();
 
+            if (getDevice(deviceId) == null) {
+                log.info("{} not found on this node yet, ignoring.", deviceId);
+                // Note: dropped information will be recovered by anti-entropy
+                return;
+            }
+
             notifyDelegateIfNotNull(updatePortStatusInternal(providerId, deviceId, portDescription));
         }
     }
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/flow/ReplicaInfoEvent.java b/core/store/dist/src/main/java/org/onlab/onos/store/flow/ReplicaInfoEvent.java
index c956c8a..ef4de4d 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/flow/ReplicaInfoEvent.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/flow/ReplicaInfoEvent.java
@@ -36,7 +36,7 @@
          */
         MASTER_CHANGED,
         //
-        // BACKUPS_CHANGED?
+        BACKUPS_CHANGED,
     }
 
 
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 bd2742a..ca3b29c 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
@@ -284,9 +284,10 @@
 
         if (!replicaInfo.master().isPresent()) {
             log.warn("No master for {}", deviceId);
-            // TODO: revisit if this should be returning empty collection.
+            // TODO: revisit if this should be returning empty collection or throwing exception.
             // FIXME: throw a FlowStoreException
-            throw new RuntimeException("No master for " + deviceId);
+            //throw new RuntimeException("No master for " + deviceId);
+            return Collections.emptyList();
         }
 
         if (replicaInfo.master().get().equals(clusterService.getLocalNode().id())) {
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/flow/impl/ReplicaInfoManager.java b/core/store/dist/src/main/java/org/onlab/onos/store/flow/impl/ReplicaInfoManager.java
index 5d766e6..c3fa99a 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/flow/impl/ReplicaInfoManager.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/flow/impl/ReplicaInfoManager.java
@@ -18,10 +18,9 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.slf4j.LoggerFactory.getLogger;
 import static org.onlab.onos.store.flow.ReplicaInfoEvent.Type.MASTER_CHANGED;
+import static org.onlab.onos.store.flow.ReplicaInfoEvent.Type.BACKUPS_CHANGED;
 
 import java.util.Collections;
-import java.util.List;
-
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -96,12 +95,24 @@
 
         @Override
         public void event(MastershipEvent event) {
-            // TODO: distinguish stby list update, when MastershipService,
-            //       start publishing them
-            final List<NodeId> standbyList = Collections.<NodeId>emptyList();
-            eventDispatcher.post(new ReplicaInfoEvent(MASTER_CHANGED,
-                                event.subject(),
-                                new ReplicaInfo(event.roleInfo().master(), standbyList)));
+            final ReplicaInfo replicaInfo
+                = new ReplicaInfo(event.roleInfo().master(),
+                                  event.roleInfo().backups());
+
+            switch (event.type()) {
+            case MASTER_CHANGED:
+                eventDispatcher.post(new ReplicaInfoEvent(MASTER_CHANGED,
+                                                          event.subject(),
+                                                          replicaInfo));
+                break;
+            case BACKUPS_CHANGED:
+                eventDispatcher.post(new ReplicaInfoEvent(BACKUPS_CHANGED,
+                                                          event.subject(),
+                                                          replicaInfo));
+                break;
+            default:
+                break;
+            }
         }
     }
 
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java b/core/store/dist/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java
index fc89e3a..713be4e 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java
@@ -16,6 +16,7 @@
 package org.onlab.onos.store.mastership.impl;
 
 import static org.onlab.onos.mastership.MastershipEvent.Type.MASTER_CHANGED;
+import static org.onlab.onos.mastership.MastershipEvent.Type.BACKUPS_CHANGED;
 import static org.apache.commons.lang3.concurrent.ConcurrentUtils.putIfAbsent;
 
 import java.util.HashSet;
@@ -43,6 +44,7 @@
 import org.onlab.onos.store.serializers.KryoSerializer;
 import org.onlab.util.KryoNamespace;
 
+import com.google.common.base.Objects;
 import com.hazelcast.core.EntryEvent;
 import com.hazelcast.core.EntryListener;
 import com.hazelcast.core.IAtomicLong;
@@ -297,8 +299,7 @@
                 case NONE:
                     rv.reassign(nodeId, NONE, STANDBY);
                     roleMap.put(deviceId, rv);
-                    // TODO: BACKUPS_CHANGED?
-                    return null;
+                    return new MastershipEvent(BACKUPS_CHANGED, deviceId, rv.roleInfo());
                 default:
                     log.warn("unknown Mastership Role {}", currentRole);
             }
@@ -327,7 +328,8 @@
                         roleMap.put(deviceId, rv);
                         return new MastershipEvent(MASTER_CHANGED, deviceId, rv.roleInfo());
                     } else {
-                        // no master candidate
+                        // No master candidate - no more backups, device is likely
+                        // fully disconnected
                         roleMap.put(deviceId, rv);
                         // Should there be new event type?
                         return null;
@@ -338,8 +340,7 @@
                     boolean modified = rv.reassign(nodeId, STANDBY, NONE);
                     if (modified) {
                         roleMap.put(deviceId, rv);
-                        // TODO: BACKUPS_CHANGED?
-                        return null;
+                        return new MastershipEvent(BACKUPS_CHANGED, deviceId, rv.roleInfo());
                     }
                     return null;
                 default:
@@ -441,8 +442,24 @@
 
         @Override
         public void entryUpdated(EntryEvent<DeviceId, RoleValue> event) {
-            notifyDelegate(new MastershipEvent(
-                    MASTER_CHANGED, event.getKey(), event.getValue().roleInfo()));
+            // compare old and current RoleValues. If master is different,
+            // emit MASTER_CHANGED. else, emit BACKUPS_CHANGED.
+            RoleValue oldValue = event.getOldValue();
+            RoleValue newValue = event.getValue();
+
+            NodeId oldMaster = null;
+            if (oldValue != null) {
+                oldMaster = oldValue.get(MASTER);
+            }
+            NodeId newMaster = newValue.get(MASTER);
+
+            if (Objects.equal(oldMaster, newMaster)) {
+                notifyDelegate(new MastershipEvent(
+                        MASTER_CHANGED, event.getKey(), event.getValue().roleInfo()));
+            } else {
+                notifyDelegate(new MastershipEvent(
+                        BACKUPS_CHANGED, event.getKey(), event.getValue().roleInfo()));
+            }
         }
 
         @Override
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/mastership/impl/RoleValue.java b/core/store/dist/src/main/java/org/onlab/onos/store/mastership/impl/RoleValue.java
index 8cc05e8..aa5d911 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/mastership/impl/RoleValue.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/mastership/impl/RoleValue.java
@@ -55,6 +55,13 @@
         return value.get(type);
     }
 
+    /**
+     * Returns the first node to match the MastershipRole, or if there
+     * are none, null.
+     *
+     * @param type the role
+     * @return a node ID or null
+     */
     public NodeId get(MastershipRole type) {
         return value.get(type).isEmpty() ? null : value.get(type).get(0);
     }
diff --git a/core/store/dist/src/test/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStoreTest.java b/core/store/dist/src/test/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStoreTest.java
index f598374..d3b7ad1 100644
--- a/core/store/dist/src/test/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStoreTest.java
+++ b/core/store/dist/src/test/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStoreTest.java
@@ -214,11 +214,11 @@
                 dms.roleMap.get(DID1).nodesOfRole(STANDBY).size());
 
         //If STANDBY, should drop to NONE
-        assertNull("wrong event:", dms.relinquishRole(N1, DID1));
+        assertEquals("wrong event:", Type.BACKUPS_CHANGED, dms.relinquishRole(N1, DID1).type());
         assertEquals("wrong role for node:", NONE, dms.getRole(N1, DID1));
 
         //NONE - nothing happens
-        assertNull("wrong event:", dms.relinquishRole(N1, DID2));
+        assertEquals("wrong event:", Type.BACKUPS_CHANGED, dms.relinquishRole(N1, DID2).type());
         assertEquals("wrong role for node:", NONE, dms.getRole(N1, DID2));
 
     }
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/IpAddressSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/IpAddressSerializer.java
index 686adb2..858d455 100644
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/IpAddressSerializer.java
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/IpAddressSerializer.java
@@ -46,7 +46,13 @@
         final int octLen = input.readInt();
         byte[] octs = new byte[octLen];
         input.readBytes(octs);
-        return IpAddress.valueOf(octs);
+        // Use the address size to decide whether it is IPv4 or IPv6 address
+        if (octLen == IpAddress.INET_BYTE_LENGTH) {
+            return IpAddress.valueOf(IpAddress.Version.INET, octs);
+        }
+        if (octLen == IpAddress.INET6_BYTE_LENGTH) {
+            return IpAddress.valueOf(IpAddress.Version.INET6, octs);
+        }
+        return null;    // Shouldn't be reached
     }
-
 }
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/IpPrefixSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/IpPrefixSerializer.java
index 004f75f..16bd0f4 100644
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/IpPrefixSerializer.java
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/IpPrefixSerializer.java
@@ -15,6 +15,7 @@
  */
 package org.onlab.onos.store.serializers;
 
+import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
 
 import com.esotericsoftware.kryo.Kryo;
@@ -51,6 +52,13 @@
         byte[] octs = new byte[octLen];
         input.readBytes(octs);
         int prefLen = input.readInt();
-        return IpPrefix.valueOf(octs, prefLen);
+        // Use the address size to decide whether it is IPv4 or IPv6 address
+        if (octLen == IpAddress.INET_BYTE_LENGTH) {
+            return IpPrefix.valueOf(IpAddress.Version.INET, octs, prefLen);
+        }
+        if (octLen == IpAddress.INET6_BYTE_LENGTH) {
+            return IpPrefix.valueOf(IpAddress.Version.INET6, octs, prefLen);
+        }
+        return null;    // Shouldn't be reached
     }
 }
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/MacAddressSerializer.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/MacAddressSerializer.java
index 4837e84..8e4467e 100644
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/MacAddressSerializer.java
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/MacAddressSerializer.java
@@ -36,7 +36,7 @@
 
     @Override
     public void write(Kryo kryo, Output output, MacAddress object) {
-        output.writeBytes(object.getAddress());
+        output.writeBytes(object.toBytes());
     }
 
     @Override
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/NoOpClockProviderService.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/NoOpClockProviderService.java
index 64e5e16..d32ac53 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/NoOpClockProviderService.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/NoOpClockProviderService.java
@@ -15,13 +15,16 @@
  */
 package org.onlab.onos.store.trivial.impl;
 
+import java.util.Set;
+
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Service;
 import org.onlab.onos.mastership.MastershipTerm;
 import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.net.device.DeviceClockProviderService;
 
-//FIXME: Code clone in onos-core-trivial, onos-core-hz-net
+import com.google.common.collect.Sets;
+
 /**
  * Dummy implementation of {@link DeviceClockProviderService}.
  */
@@ -29,7 +32,15 @@
 @Service
 public class NoOpClockProviderService implements DeviceClockProviderService {
 
+    private Set<DeviceId> registerdBefore = Sets.newConcurrentHashSet();
+
     @Override
     public void setMastershipTerm(DeviceId deviceId, MastershipTerm term) {
+        registerdBefore.add(deviceId);
+    }
+
+    @Override
+    public boolean isTimestampAvailable(DeviceId deviceId) {
+        return registerdBefore.contains(deviceId);
     }
 }
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 49b0c71..b7d26fb 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
@@ -16,8 +16,12 @@
 package org.onlab.onos.store.trivial.impl;
 
 import com.google.common.base.Function;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
 import com.google.common.collect.FluentIterable;
 import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.SettableFuture;
+
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -43,13 +47,15 @@
 import org.onlab.util.NewConcurrentHashMap;
 import org.slf4j.Logger;
 
-import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import static org.apache.commons.lang3.concurrent.ConcurrentUtils.createIfAbsentUnchecked;
 import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
@@ -72,6 +78,18 @@
     private final ConcurrentMap<DeviceId, ConcurrentMap<FlowId, List<StoredFlowEntry>>>
             flowEntries = new ConcurrentHashMap<>();
 
+    private final AtomicInteger localBatchIdGen = new AtomicInteger();
+
+    // TODO: make this configurable
+    private int pendingFutureTimeoutMinutes = 5;
+
+    private Cache<Integer, SettableFuture<CompletedBatchOperation>> pendingFutures =
+            CacheBuilder.newBuilder()
+                .expireAfterWrite(pendingFutureTimeoutMinutes, TimeUnit.MINUTES)
+                // TODO Explicitly fail the future if expired?
+                //.removalListener(listener)
+                .build();
+
     @Activate
     public void activate() {
         log.info("Started");
@@ -173,10 +191,6 @@
             }
             // new flow rule added
             existing.add(f);
-            notifyDelegate(FlowRuleBatchEvent.requested(
-                    new FlowRuleBatchRequest(1, /* FIXME generate something */
-                                             Arrays.<FlowEntry>asList(f),
-                                             Collections.<FlowEntry>emptyList())));
         }
     }
 
@@ -190,11 +204,6 @@
                 if (entry.equals(rule)) {
                     synchronized (entry) {
                         entry.setState(FlowEntryState.PENDING_REMOVE);
-                        // TODO: Should we notify only if it's "remote" event?
-                        notifyDelegate(FlowRuleBatchEvent.requested(
-                                new FlowRuleBatchRequest(1, /* FIXME generate something */
-                                                         Collections.<FlowEntry>emptyList(),
-                                                         Arrays.<FlowEntry>asList(entry))));
                     }
                 }
             }
@@ -251,20 +260,47 @@
     @Override
     public Future<CompletedBatchOperation> storeBatch(
             FlowRuleBatchOperation batchOperation) {
+        List<FlowRule> toAdd = new ArrayList<>();
+        List<FlowRule> toRemove = new ArrayList<>();
         for (FlowRuleBatchEntry entry : batchOperation.getOperations()) {
+            final FlowRule flowRule = entry.getTarget();
             if (entry.getOperator().equals(FlowRuleOperation.ADD)) {
-                storeFlowRule(entry.getTarget());
+                if (!getFlowEntries(flowRule.deviceId(), flowRule.id()).contains(flowRule)) {
+                    storeFlowRule(flowRule);
+                    toAdd.add(flowRule);
+                }
             } else if (entry.getOperator().equals(FlowRuleOperation.REMOVE)) {
-                deleteFlowRule(entry.getTarget());
+                if (getFlowEntries(flowRule.deviceId(), flowRule.id()).contains(flowRule)) {
+                    deleteFlowRule(flowRule);
+                    toRemove.add(flowRule);
+                }
             } else {
                 throw new UnsupportedOperationException("Unsupported operation type");
             }
         }
-        return Futures.immediateFuture(new CompletedBatchOperation(true, Collections.<FlowEntry>emptySet()));
+
+        if (toAdd.isEmpty() && toRemove.isEmpty()) {
+            return Futures.immediateFuture(new CompletedBatchOperation(true, Collections.<FlowRule>emptySet()));
+        }
+
+        SettableFuture<CompletedBatchOperation> r = SettableFuture.create();
+        final int batchId = localBatchIdGen.incrementAndGet();
+
+        pendingFutures.put(batchId, r);
+        notifyDelegate(FlowRuleBatchEvent.requested(new FlowRuleBatchRequest(batchId, toAdd, toRemove)));
+
+        return r;
     }
 
     @Override
     public void batchOperationComplete(FlowRuleBatchEvent event) {
+        final Integer batchId = event.subject().batchId();
+        SettableFuture<CompletedBatchOperation> future
+            = pendingFutures.getIfPresent(batchId);
+        if (future != null) {
+            future.set(event.result());
+            pendingFutures.invalidate(batchId);
+        }
         notifyDelegate(event);
     }
 }
diff --git a/openflow/api/src/main/java/org/onlab/onos/openflow/controller/OpenFlowSwitch.java b/openflow/api/src/main/java/org/onlab/onos/openflow/controller/OpenFlowSwitch.java
index 17f168f..2d3f890 100644
--- a/openflow/api/src/main/java/org/onlab/onos/openflow/controller/OpenFlowSwitch.java
+++ b/openflow/api/src/main/java/org/onlab/onos/openflow/controller/OpenFlowSwitch.java
@@ -115,17 +115,25 @@
     public String serialNumber();
 
     /**
+     * Checks if the switch is still connected.
+     *
+     * @return whether the switch is still connected
+     */
+    public boolean isConnected();
+
+    /**
      * Disconnects the switch by closing the TCP connection. Results in a call
      * to the channel handler's channelDisconnected method for cleanup
      */
     public void disconnectSwitch();
 
     /**
-     * Notifies the controller that role assertion has failed.
+     * Notifies the controller that the device has responded to a set-role request.
      *
-     * @param role the failed role
+     * @param requested the role requested by the controller
+     * @param response the role set at the device
      */
-    public void returnRoleAssertFailure(RoleState role);
+    public void returnRoleReply(RoleState requested, RoleState reponse);
 
     /**
      * Indicates if this switch is optical.
diff --git a/openflow/api/src/main/java/org/onlab/onos/openflow/controller/OpenFlowSwitchListener.java b/openflow/api/src/main/java/org/onlab/onos/openflow/controller/OpenFlowSwitchListener.java
index a96c56f..192f045 100644
--- a/openflow/api/src/main/java/org/onlab/onos/openflow/controller/OpenFlowSwitchListener.java
+++ b/openflow/api/src/main/java/org/onlab/onos/openflow/controller/OpenFlowSwitchListener.java
@@ -53,5 +53,5 @@
      * @param dpid the switch that failed role assertion
      * @param role the role imposed by the controller
      */
-    public void roleAssertFailed(Dpid dpid, RoleState role);
+    public void receivedRoleReply(Dpid dpid, RoleState requested, RoleState response);
 }
diff --git a/openflow/api/src/main/java/org/onlab/onos/openflow/controller/driver/AbstractOpenFlowSwitch.java b/openflow/api/src/main/java/org/onlab/onos/openflow/controller/driver/AbstractOpenFlowSwitch.java
index 9950515..61359cd 100644
--- a/openflow/api/src/main/java/org/onlab/onos/openflow/controller/driver/AbstractOpenFlowSwitch.java
+++ b/openflow/api/src/main/java/org/onlab/onos/openflow/controller/driver/AbstractOpenFlowSwitch.java
@@ -217,8 +217,8 @@
     }
 
     @Override
-    public void returnRoleAssertFailure(RoleState role) {
-        this.agent.returnRoleAssertFailed(dpid, role);
+    public void returnRoleReply(RoleState requested, RoleState response) {
+        this.agent.returnRoleReply(dpid, requested, response);
     }
 
     @Override
diff --git a/openflow/api/src/main/java/org/onlab/onos/openflow/controller/driver/OpenFlowAgent.java b/openflow/api/src/main/java/org/onlab/onos/openflow/controller/driver/OpenFlowAgent.java
index fa2823f..6b73efc 100644
--- a/openflow/api/src/main/java/org/onlab/onos/openflow/controller/driver/OpenFlowAgent.java
+++ b/openflow/api/src/main/java/org/onlab/onos/openflow/controller/driver/OpenFlowAgent.java
@@ -97,5 +97,5 @@
      * @param dpid the switch that failed role assertion
      * @param role the failed role
      */
-    public void returnRoleAssertFailed(Dpid dpid, RoleState role);
+    public void returnRoleReply(Dpid dpid, RoleState requested, RoleState response);
 }
diff --git a/openflow/api/src/main/java/org/onlab/onos/openflow/controller/driver/OpenFlowSwitchDriver.java b/openflow/api/src/main/java/org/onlab/onos/openflow/controller/driver/OpenFlowSwitchDriver.java
index 2647fed..b6a4770 100644
--- a/openflow/api/src/main/java/org/onlab/onos/openflow/controller/driver/OpenFlowSwitchDriver.java
+++ b/openflow/api/src/main/java/org/onlab/onos/openflow/controller/driver/OpenFlowSwitchDriver.java
@@ -187,13 +187,6 @@
     public void setConnected(boolean connected);
 
     /**
-     * Checks if the switch is still connected.
-     *
-     * @return whether the switch is still connected
-     */
-    public boolean isConnected();
-
-    /**
      * Writes the message to the output stream
      * in a driver specific manner.
      *
diff --git a/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OpenFlowControllerImpl.java b/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OpenFlowControllerImpl.java
index 70c9b1b..098771d 100644
--- a/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OpenFlowControllerImpl.java
+++ b/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OpenFlowControllerImpl.java
@@ -374,9 +374,9 @@
         }
 
         @Override
-        public void returnRoleAssertFailed(Dpid dpid, RoleState role) {
+        public void returnRoleReply(Dpid dpid, RoleState requested, RoleState response) {
             for (OpenFlowSwitchListener l : ofSwitchListener) {
-                l.roleAssertFailed(dpid, role);
+                l.receivedRoleReply(dpid, requested, response);
             }
         }
     }
diff --git a/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/RoleManager.java b/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/RoleManager.java
index c9f71b7..6e5b236 100644
--- a/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/RoleManager.java
+++ b/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/RoleManager.java
@@ -17,6 +17,7 @@
 
 import java.io.IOException;
 import java.util.Collections;
+import java.util.concurrent.TimeUnit;
 
 import org.onlab.onos.openflow.controller.RoleState;
 import org.onlab.onos.openflow.controller.driver.OpenFlowSwitchDriver;
@@ -41,31 +42,29 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+
 
 /**
  * A utility class to handle role requests and replies for this channel.
  * After a role request is submitted the role changer keeps track of the
  * pending request, collects the reply (if any) and times out the request
  * if necessary.
- *
- * To simplify role handling we only keep track of the /last/ pending
- * role reply send to the switch. If multiple requests are pending and
- * we receive replies for earlier requests we ignore them. However, this
- * way of handling pending requests implies that we could wait forever if
- * a new request is submitted before the timeout triggers. If necessary
- * we could work around that though.
  */
 class RoleManager implements RoleHandler {
     protected static final long NICIRA_EXPERIMENTER = 0x2320;
 
     private static Logger log = LoggerFactory.getLogger(RoleManager.class);
-    // indicates that a request is currently pending
-    // needs to be volatile to allow correct double-check idiom
-    private volatile boolean requestPending;
-    // the transaction Id of the pending request
-    private int pendingXid;
-    // the role that's pending
-    private RoleState pendingRole;
+
+    // The time until cached XID is evicted. Arbitray for now.
+    private final int pendingXidTimeoutSeconds = 60;
+
+    // The cache for pending expected RoleReplies keyed on expected XID
+    private Cache<Integer, RoleState> pendingReplies =
+            CacheBuilder.newBuilder()
+                .expireAfterWrite(pendingXidTimeoutSeconds, TimeUnit.SECONDS)
+                .build();
 
     // the expectation set by the caller for the returned role
     private RoleRecvStatus expectation;
@@ -73,9 +72,6 @@
 
 
     public RoleManager(OpenFlowSwitchDriver sw) {
-        this.requestPending = false;
-        this.pendingXid = -1;
-        this.pendingRole = null;
         this.expectation = RoleRecvStatus.MATCHED_CURRENT_ROLE;
         this.sw = sw;
     }
@@ -157,15 +153,11 @@
             }
             // OF1.0 switch with support for NX_ROLE_REQUEST vendor extn.
             // make Role.EQUAL become Role.SLAVE
-            pendingRole = role;
-            role = (role == RoleState.EQUAL) ? RoleState.SLAVE : role;
-            pendingXid = sendNxRoleRequest(role);
-            requestPending = true;
+            RoleState roleToSend = (role == RoleState.EQUAL) ? RoleState.SLAVE : role;
+            pendingReplies.put(sendNxRoleRequest(roleToSend), role);
         } else {
             // OF1.3 switch, use OFPT_ROLE_REQUEST message
-            pendingXid = sendOF13RoleRequest(role);
-            pendingRole = role;
-            requestPending = true;
+            pendingReplies.put(sendOF13RoleRequest(role), role);
         }
         return true;
     }
@@ -192,12 +184,17 @@
     @Override
     public synchronized RoleRecvStatus deliverRoleReply(RoleReplyInfo rri)
             throws SwitchStateException {
-        if (!requestPending) {
+        int xid = (int) rri.getXid();
+        RoleState receivedRole = rri.getRole();
+        RoleState expectedRole = pendingReplies.getIfPresent(xid);
+
+        if (expectedRole == null) {
             RoleState currentRole = (sw != null) ? sw.getRole() : null;
             if (currentRole != null) {
                 if (currentRole == rri.getRole()) {
                     // Don't disconnect if the role reply we received is
                     // for the same role we are already in.
+                    // FIXME: but we do from the caller anyways.
                     log.debug("Received unexpected RoleReply from "
                             + "Switch: {}. "
                             + "Role in reply is same as current role of this "
@@ -223,34 +220,33 @@
             return RoleRecvStatus.OTHER_EXPECTATION;
         }
 
-        int xid = (int) rri.getXid();
-        RoleState role = rri.getRole();
-        // XXX S should check generation id meaningfully and other cases of expectations
+        // XXX Should check generation id meaningfully and other cases of expectations
+        //if (pendingXid != xid) {
+        //    log.info("Received older role reply from " +
+        //            "switch {} ({}). Ignoring. " +
+        //            "Waiting for {}, xid={}",
+        //            new Object[] {sw.getStringId(), rri,
+        //            pendingRole, pendingXid });
+        //    return RoleRecvStatus.OLD_REPLY;
+        //}
+        sw.returnRoleReply(expectedRole, receivedRole);
 
-        if (pendingXid != xid) {
-            log.debug("Received older role reply from " +
-                    "switch {} ({}). Ignoring. " +
-                    "Waiting for {}, xid={}",
-                    new Object[] {sw.getStringId(), rri,
-                    pendingRole, pendingXid });
-            return RoleRecvStatus.OLD_REPLY;
-        }
-
-        if (pendingRole == role) {
+        if (expectedRole == receivedRole) {
             log.debug("Received role reply message from {} that matched "
                     + "expected role-reply {} with expectations {}",
-                    new Object[] {sw.getStringId(), role, expectation});
+                    new Object[] {sw.getStringId(), receivedRole, expectation});
 
+            // Done with this RoleReply; Invalidate
+            pendingReplies.invalidate(xid);
             if (expectation == RoleRecvStatus.MATCHED_CURRENT_ROLE ||
                     expectation == RoleRecvStatus.MATCHED_SET_ROLE) {
                 return expectation;
             } else {
                 return RoleRecvStatus.OTHER_EXPECTATION;
             }
-        } else {
-            sw.returnRoleAssertFailure(pendingRole);
         }
 
+        pendingReplies.invalidate(xid);
         // if xids match but role's don't, perhaps its a query (OF1.3)
         if (expectation == RoleRecvStatus.REPLY_QUERY) {
             return expectation;
@@ -270,18 +266,17 @@
     @Override
     public synchronized RoleRecvStatus deliverError(OFErrorMsg error)
             throws SwitchStateException {
-        if (!requestPending) {
-            log.debug("Received an error msg from sw {}, but no pending "
-                    + "requests in role-changer; not handling ...",
-                    sw.getStringId());
-            return RoleRecvStatus.OTHER_EXPECTATION;
-        }
-        if (pendingXid != error.getXid()) {
+        RoleState errorRole = pendingReplies.getIfPresent(error.getXid());
+        if (errorRole == null) {
             if (error.getErrType() == OFErrorType.ROLE_REQUEST_FAILED) {
                 log.debug("Received an error msg from sw {} for a role request,"
                         + " but not for pending request in role-changer; "
                         + " ignoring error {} ...",
                         sw.getStringId(), error);
+            } else {
+                log.debug("Received an error msg from sw {}, but no pending "
+                        + "requests in role-changer; not handling ...",
+                        sw.getStringId());
             }
             return RoleRecvStatus.OTHER_EXPECTATION;
         }
@@ -292,7 +287,7 @@
                     + "role-messaging is supported. Possible issues in "
                     + "switch driver configuration?", new Object[] {
                             ((OFBadRequestErrorMsg) error).toString(),
-                            sw.getStringId(), pendingRole
+                            sw.getStringId(), errorRole
                     });
             return RoleRecvStatus.UNSUPPORTED;
         }
@@ -316,7 +311,7 @@
                         + "received Error to for pending role request [%s]. "
                         + "Error:[%s]. Disconnecting switch ... ",
                         sw.getStringId(),
-                        pendingRole, rrerr);
+                        errorRole, rrerr);
                 throw new SwitchStateException(msgx);
             default:
                 break;
diff --git a/openflow/ctl/src/test/java/org/onlab/onos/openflow/controller/impl/RoleManagerTest.java b/openflow/ctl/src/test/java/org/onlab/onos/openflow/controller/impl/RoleManagerTest.java
index aa23995..7260ddd 100644
--- a/openflow/ctl/src/test/java/org/onlab/onos/openflow/controller/impl/RoleManagerTest.java
+++ b/openflow/ctl/src/test/java/org/onlab/onos/openflow/controller/impl/RoleManagerTest.java
@@ -175,11 +175,6 @@
         }
 
         @Override
-        public void returnRoleAssertFailure(RoleState role) {
-            failed = role;
-        }
-
-        @Override
         public boolean isOptical() {
             return false;
         }
@@ -300,5 +295,10 @@
         public void write(List<OFMessage> msgs) {
         }
 
+        @Override
+        public void returnRoleReply(RoleState requested, RoleState response) {
+            failed = requested;
+        }
+
     }
 }
diff --git a/pom.xml b/pom.xml
index 83ed0f4..deefb48 100644
--- a/pom.xml
+++ b/pom.xml
@@ -318,6 +318,7 @@
                 <groupId>io.netty</groupId>
                 <artifactId>netty-transport-native-epoll</artifactId>
                 <version>${netty4.version}</version>
+                <classifier>${os.detected.classifier}</classifier>
             </dependency>
             <dependency>
                 <groupId>joda-time</groupId>
@@ -351,6 +352,13 @@
     </dependencies>
 
     <build>
+        <extensions>
+            <extension>
+                <groupId>kr.motd.maven</groupId>
+                <artifactId>os-maven-plugin</artifactId>
+                <version>1.2.3.Final</version>
+            </extension>
+        </extensions>
         <pluginManagement>
             <plugins>
                 <plugin>
diff --git a/providers/host/src/main/java/org/onlab/onos/provider/host/impl/HostLocationProvider.java b/providers/host/src/main/java/org/onlab/onos/provider/host/impl/HostLocationProvider.java
index 36d67b2..48762bb 100644
--- a/providers/host/src/main/java/org/onlab/onos/provider/host/impl/HostLocationProvider.java
+++ b/providers/host/src/main/java/org/onlab/onos/provider/host/impl/HostLocationProvider.java
@@ -120,7 +120,8 @@
             if (eth.getEtherType() == Ethernet.TYPE_ARP) {
                 ARP arp = (ARP) eth.getPayload();
                 IpAddress ip =
-                    IpAddress.valueOf(arp.getSenderProtocolAddress());
+                    IpAddress.valueOf(IpAddress.Version.INET,
+                                      arp.getSenderProtocolAddress());
                 HostDescription hdescr =
                         new DefaultHostDescription(eth.getSourceMAC(), vlan, hloc, ip);
                 providerService.hostDetected(hid, hdescr);
diff --git a/providers/host/src/test/java/org/onlab/onos/provider/host/impl/HostLocationProviderTest.java b/providers/host/src/test/java/org/onlab/onos/provider/host/impl/HostLocationProviderTest.java
index b226fff..e42d657 100644
--- a/providers/host/src/test/java/org/onlab/onos/provider/host/impl/HostLocationProviderTest.java
+++ b/providers/host/src/test/java/org/onlab/onos/provider/host/impl/HostLocationProviderTest.java
@@ -216,7 +216,7 @@
             eth.setEtherType(Ethernet.TYPE_ARP)
                     .setVlanID(VLAN.toShort())
                     .setSourceMACAddress(MAC.toBytes())
-                    .setDestinationMACAddress(BCMAC.getAddress())
+                    .setDestinationMACAddress(BCMAC)
                     .setPayload(arp);
             ConnectPoint receivedFrom = new ConnectPoint(DeviceId.deviceId(deviceId),
                                                          PortNumber.portNumber(INPORT));
diff --git a/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/LLDPLinkProvider.java b/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/LLDPLinkProvider.java
index 24336aa..6dc464e 100644
--- a/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/LLDPLinkProvider.java
+++ b/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/LLDPLinkProvider.java
@@ -127,18 +127,19 @@
                 return;
             }
             log.trace("{} {} {}", event.type(), event.subject(), event);
+            final DeviceId deviceId = device.id();
             switch (event.type()) {
                 case DEVICE_ADDED:
                 case DEVICE_UPDATED:
-                    ld = discoverers.get(device.id());
+                    ld = discoverers.get(deviceId);
                     if (ld == null) {
-                        log.debug("Device added ({}) {}", event.type(), device.id());
-                        discoverers.put(device.id(),
+                        log.debug("Device added ({}) {}", event.type(), deviceId);
+                        discoverers.put(deviceId,
                                new LinkDiscovery(device, packetSevice, masterService,
                                       providerService, useBDDP));
                     } else {
                         if (ld.isStopped()) {
-                            log.debug("Device restarted ({}) {}", event.type(), device.id());
+                            log.debug("Device restarted ({}) {}", event.type(), deviceId);
                             ld.start();
                         }
                     }
@@ -146,7 +147,7 @@
                 case PORT_ADDED:
                 case PORT_UPDATED:
                     if (port.isEnabled()) {
-                        ld = discoverers.get(device.id());
+                        ld = discoverers.get(deviceId);
                         if (ld == null) {
                             return;
                         }
@@ -156,47 +157,47 @@
                         }
                     } else {
                         log.debug("Port down {}", port);
-                        ConnectPoint point = new ConnectPoint(device.id(),
+                        ConnectPoint point = new ConnectPoint(deviceId,
                                                               port.number());
                         providerService.linksVanished(point);
                     }
                     break;
                 case PORT_REMOVED:
                     log.debug("Port removed {}", port);
-                    ConnectPoint point = new ConnectPoint(device.id(),
+                    ConnectPoint point = new ConnectPoint(deviceId,
                                                           port.number());
                     providerService.linksVanished(point);
                     // TODO: Don't we need to removePort from ld?
                     break;
                 case DEVICE_REMOVED:
                 case DEVICE_SUSPENDED:
-                    log.debug("Device removed {}", device.id());
-                    ld = discoverers.get(device.id());
+                    log.debug("Device removed {}", deviceId);
+                    ld = discoverers.get(deviceId);
                     if (ld == null) {
                         return;
                     }
                     ld.stop();
-                    providerService.linksVanished(device.id());
+                    providerService.linksVanished(deviceId);
                     break;
                 case DEVICE_AVAILABILITY_CHANGED:
-                    ld = discoverers.get(device.id());
+                    ld = discoverers.get(deviceId);
                     if (ld == null) {
                         return;
                     }
-                    if (deviceService.isAvailable(device.id())) {
-                        log.debug("Device up {}", device.id());
+                    if (deviceService.isAvailable(deviceId)) {
+                        log.debug("Device up {}", deviceId);
                         ld.start();
                     } else {
-                        providerService.linksVanished(device.id());
-                        log.debug("Device down {}", device.id());
+                        providerService.linksVanished(deviceId);
+                        log.debug("Device down {}", deviceId);
                         ld.stop();
                     }
                     break;
                 case DEVICE_MASTERSHIP_CHANGED:
-                    if (!discoverers.containsKey(device.id())) {
+                    if (!discoverers.containsKey(deviceId)) {
                         // TODO: ideally, should never reach here
-                        log.debug("Device mastership changed ({}) {}", event.type(), device.id());
-                        discoverers.put(device.id(),
+                        log.debug("Device mastership changed ({}) {}", event.type(), deviceId);
+                        discoverers.put(deviceId,
                                new LinkDiscovery(device, packetSevice, masterService,
                                       providerService, useBDDP));
                     }
diff --git a/providers/openflow/device/src/main/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProvider.java b/providers/openflow/device/src/main/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProvider.java
index 8aa3d77..c0cc416 100644
--- a/providers/openflow/device/src/main/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProvider.java
+++ b/providers/openflow/device/src/main/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProvider.java
@@ -39,7 +39,6 @@
 import org.onlab.onos.openflow.controller.OpenFlowSwitch;
 import org.onlab.onos.openflow.controller.OpenFlowSwitchListener;
 import org.onlab.onos.openflow.controller.RoleState;
-import org.onlab.onos.openflow.controller.driver.OpenFlowSwitchDriver;
 import org.onlab.packet.ChassisId;
 import org.projectfloodlight.openflow.protocol.OFFactory;
 import org.projectfloodlight.openflow.protocol.OFPortConfig;
@@ -112,27 +111,39 @@
 
 
     @Override
-    public boolean isReachable(Device device) {
-        // FIXME if possible, we might want this to be part of
-        // OpenFlowSwitch interface so the driver interface isn't misused.
-        OpenFlowSwitch sw = controller.getSwitch(dpid(device.id().uri()));
-        if (sw == null || !((OpenFlowSwitchDriver) sw).isConnected()) {
+    public boolean isReachable(DeviceId deviceId) {
+        OpenFlowSwitch sw = controller.getSwitch(dpid(deviceId.uri()));
+        if (sw == null || !sw.isConnected()) {
             return false;
         }
         return true;
-        //return checkChannel(device, sw);
     }
 
     @Override
     public void triggerProbe(Device device) {
-        LOG.info("Triggering probe on device {}", device.id());
+        final DeviceId deviceId = device.id();
+        LOG.info("Triggering probe on device {}", deviceId);
 
-        OpenFlowSwitch sw = controller.getSwitch(dpid(device.id().uri()));
-        //if (!checkChannel(device, sw)) {
-        //  LOG.error("Failed to probe device {} on sw={}", device, sw);
-        //  providerService.deviceDisconnected(device.id());
-        //return;
-        //}
+        final Dpid dpid = dpid(deviceId.uri());
+        OpenFlowSwitch sw = controller.getSwitch(dpid);
+        if (sw == null || !sw.isConnected()) {
+            LOG.error("Failed to probe device {} on sw={}", device, sw);
+            providerService.deviceDisconnected(deviceId);
+        } else {
+            LOG.trace("Confirmed device {} connection", device);
+            // FIXME require something like below to match javadoc description
+            // but this starts infinite loop with current DeviceManager
+//            final ChassisId cId = new ChassisId(dpid.value());
+//            final Type deviceType = device.type();
+//            DeviceDescription description =
+//                    new DefaultDeviceDescription(deviceId.uri(), deviceType,
+//                                                 sw.manfacturerDescription(),
+//                                                 sw.hardwareDescription(),
+//                                                 sw.softwareDescription(),
+//                                                 sw.serialNumber(),
+//                                                 cId);
+//            providerService.deviceConnected(deviceId, description);
+        }
 
         // Prompt an update of port information. We can use any XID for this.
         OFFactory fact = sw.factory();
@@ -159,22 +170,22 @@
     // }
 
     @Override
-    public void roleChanged(Device device, MastershipRole newRole) {
+    public void roleChanged(DeviceId deviceId, MastershipRole newRole) {
         switch (newRole) {
             case MASTER:
-                controller.setRole(dpid(device.id().uri()), RoleState.MASTER);
+                controller.setRole(dpid(deviceId.uri()), RoleState.MASTER);
                 break;
             case STANDBY:
-                controller.setRole(dpid(device.id().uri()), RoleState.EQUAL);
+                controller.setRole(dpid(deviceId.uri()), RoleState.EQUAL);
                 break;
             case NONE:
-                controller.setRole(dpid(device.id().uri()), RoleState.SLAVE);
+                controller.setRole(dpid(deviceId.uri()), RoleState.SLAVE);
                 break;
             default:
                 LOG.error("Unknown Mastership state : {}", newRole);
 
         }
-        LOG.info("Accepting mastership role change for device {}", device.id());
+        LOG.info("Accepting mastership role change for device {}", deviceId);
     }
 
     private class InternalDeviceProvider implements OpenFlowSwitchListener {
@@ -226,23 +237,31 @@
         }
 
         @Override
-        public void roleAssertFailed(Dpid dpid, RoleState role) {
-            MastershipRole failed;
-            switch (role) {
+        public void receivedRoleReply(Dpid dpid, RoleState requested, RoleState response) {
+            MastershipRole request = roleOf(requested);
+            MastershipRole reply = roleOf(response);
+
+            providerService.receivedRoleReply(deviceId(uri(dpid)), request, reply);
+        }
+
+        /**
+         * Translates a RoleState to the corresponding MastershipRole.
+         *
+         * @param response
+         * @return a MastershipRole
+         */
+        private MastershipRole roleOf(RoleState response) {
+            switch (response) {
                 case MASTER:
-                    failed = MastershipRole.MASTER;
-                    break;
+                    return MastershipRole.MASTER;
                 case EQUAL:
-                    failed = MastershipRole.STANDBY;
-                    break;
+                    return MastershipRole.STANDBY;
                 case SLAVE:
-                    failed = MastershipRole.NONE;
-                    break;
+                    return MastershipRole.NONE;
                 default:
-                    LOG.warn("unknown role {}", role);
-                    return;
+                    LOG.warn("unknown role {}", response);
+                    return null;
             }
-            providerService.unableToAssertRole(deviceId(uri(dpid)), failed);
         }
 
         /**
diff --git a/providers/openflow/device/src/test/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProviderTest.java b/providers/openflow/device/src/test/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProviderTest.java
index e7e57b2..88c68a9 100644
--- a/providers/openflow/device/src/test/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProviderTest.java
+++ b/providers/openflow/device/src/test/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProviderTest.java
@@ -105,11 +105,11 @@
 
     @Test
     public void roleChanged() {
-        provider.roleChanged(DEV1, MASTER);
+        provider.roleChanged(DID1, MASTER);
         assertEquals("Should be MASTER", RoleState.MASTER, controller.roleMap.get(DPID1));
-        provider.roleChanged(DEV1, STANDBY);
+        provider.roleChanged(DID1, STANDBY);
         assertEquals("Should be EQUAL", RoleState.EQUAL, controller.roleMap.get(DPID1));
-        provider.roleChanged(DEV1, NONE);
+        provider.roleChanged(DID1, NONE);
         assertEquals("Should be SLAVE", RoleState.SLAVE, controller.roleMap.get(DPID1));
     }
 
@@ -136,12 +136,13 @@
     }
 
     @Test
-    public void roleAssertFailed() {
-        controller.listener.roleAssertFailed(DPID1, RoleState.MASTER);
+    public void receivedRoleReply() {
+        // check translation capabilities
+        controller.listener.receivedRoleReply(DPID1, RoleState.MASTER, RoleState.MASTER);
         assertEquals("wrong role reported", DPID1, registry.roles.get(MASTER));
-        controller.listener.roleAssertFailed(DPID1, RoleState.EQUAL);
+        controller.listener.receivedRoleReply(DPID1, RoleState.EQUAL, RoleState.MASTER);
         assertEquals("wrong role reported", DPID1, registry.roles.get(STANDBY));
-        controller.listener.roleAssertFailed(DPID1, RoleState.SLAVE);
+        controller.listener.receivedRoleReply(DPID1, RoleState.SLAVE, RoleState.MASTER);
         assertEquals("wrong role reported", DPID1, registry.roles.get(NONE));
     }
 
@@ -210,8 +211,9 @@
             }
 
             @Override
-            public void unableToAssertRole(DeviceId deviceId, MastershipRole role) {
-                roles.put(role, Dpid.dpid(deviceId.uri()));
+            public void receivedRoleReply(DeviceId deviceId,
+                    MastershipRole requested, MastershipRole response) {
+                roles.put(requested, Dpid.dpid(deviceId.uri()));
             }
 
         }
@@ -368,11 +370,12 @@
         }
 
         @Override
-        public void disconnectSwitch() {
+        public boolean isConnected() {
+            return true;
         }
 
         @Override
-        public void returnRoleAssertFailure(RoleState role) {
+        public void disconnectSwitch() {
         }
 
         @Override
@@ -380,6 +383,10 @@
             return false;
         }
 
+        @Override
+        public void returnRoleReply(RoleState requested, RoleState reponse) {
+        }
+
     }
 
 }
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 0a0c0c7..0caf06b 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
@@ -147,7 +147,8 @@
                 ip = (IPCriterion) c;
                 if (ip.ip().prefixLength() != IpPrefix.MAX_INET_MASK_LENGTH) {
                     IpAddress maskAddr =
-                        IpAddress.makeMaskPrefix(ip.ip().prefixLength());
+                        IpAddress.makeMaskPrefix(ip.ip().address().version(),
+                                                 ip.ip().prefixLength());
                     Masked<IPv4Address> maskedIp =
                         Masked.of(IPv4Address.of(ip.ip().address().toInt()),
                                   IPv4Address.of(maskAddr.toInt()));
@@ -161,7 +162,8 @@
                 ip = (IPCriterion) c;
                 if (ip.ip().prefixLength() != IpPrefix.MAX_INET_MASK_LENGTH) {
                     IpAddress maskAddr =
-                        IpAddress.makeMaskPrefix(ip.ip().prefixLength());
+                        IpAddress.makeMaskPrefix(ip.ip().address().version(),
+                                                 ip.ip().prefixLength());
                     Masked<IPv4Address> maskedIp =
                         Masked.of(IPv4Address.of(ip.ip().address().toInt()),
                                   IPv4Address.of(maskAddr.toInt()));
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 53a49b6..018d6f3 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
@@ -302,7 +302,10 @@
         }
 
         @Override
-        public void roleAssertFailed(Dpid dpid, RoleState role) {}
+        public void receivedRoleReply(Dpid dpid, RoleState requested,
+                RoleState response) {
+            // Do nothing here for now.
+        }
 
         private synchronized void pushFlowMetrics(Dpid dpid, OFStatsReply stats) {
             if (stats.getStatsType() != OFStatsType.FLOW) {
diff --git a/providers/openflow/host/src/main/java/org/onlab/onos/provider/of/host/impl/OpenFlowHostProvider.java b/providers/openflow/host/src/main/java/org/onlab/onos/provider/of/host/impl/OpenFlowHostProvider.java
index e563c34..3c08a2f 100644
--- a/providers/openflow/host/src/main/java/org/onlab/onos/provider/of/host/impl/OpenFlowHostProvider.java
+++ b/providers/openflow/host/src/main/java/org/onlab/onos/provider/of/host/impl/OpenFlowHostProvider.java
@@ -126,7 +126,8 @@
             if (eth.getEtherType() == Ethernet.TYPE_ARP) {
                 ARP arp = (ARP) eth.getPayload();
                 IpAddress ip =
-                    IpAddress.valueOf(arp.getSenderProtocolAddress());
+                    IpAddress.valueOf(IpAddress.Version.INET,
+                                      arp.getSenderProtocolAddress());
                 HostDescription hdescr =
                         new DefaultHostDescription(eth.getSourceMAC(), vlan, hloc, ip);
                 providerService.hostDetected(hid, hdescr);
diff --git a/providers/openflow/host/src/test/java/org/onlab/onos/provider/of/host/impl/OpenFlowHostProviderTest.java b/providers/openflow/host/src/test/java/org/onlab/onos/provider/of/host/impl/OpenFlowHostProviderTest.java
index 9205517..d227116 100644
--- a/providers/openflow/host/src/test/java/org/onlab/onos/provider/of/host/impl/OpenFlowHostProviderTest.java
+++ b/providers/openflow/host/src/test/java/org/onlab/onos/provider/of/host/impl/OpenFlowHostProviderTest.java
@@ -213,8 +213,8 @@
             Ethernet eth = new Ethernet();
             eth.setEtherType(Ethernet.TYPE_ARP)
             .setVlanID(VLAN.toShort())
-            .setSourceMACAddress(MAC.toBytes())
-            .setDestinationMACAddress(BCMAC.getAddress())
+            .setSourceMACAddress(MAC)
+            .setDestinationMACAddress(BCMAC)
             .setPayload(arp);
 
             return eth;
diff --git a/providers/openflow/link/src/main/java/org/onlab/onos/provider/of/link/impl/OpenFlowLinkProvider.java b/providers/openflow/link/src/main/java/org/onlab/onos/provider/of/link/impl/OpenFlowLinkProvider.java
index 02ba907..f32ca55 100644
--- a/providers/openflow/link/src/main/java/org/onlab/onos/provider/of/link/impl/OpenFlowLinkProvider.java
+++ b/providers/openflow/link/src/main/java/org/onlab/onos/provider/of/link/impl/OpenFlowLinkProvider.java
@@ -160,7 +160,8 @@
         }
 
         @Override
-        public void roleAssertFailed(Dpid dpid, RoleState role) {
+        public void receivedRoleReply(Dpid dpid, RoleState requested,
+                RoleState response) {
             // do nothing for this.
         }
 
diff --git a/providers/openflow/link/src/test/java/org/onlab/onos/provider/of/link/impl/OpenFlowLinkProviderTest.java b/providers/openflow/link/src/test/java/org/onlab/onos/provider/of/link/impl/OpenFlowLinkProviderTest.java
index 32339f2..a6b0ac3 100644
--- a/providers/openflow/link/src/test/java/org/onlab/onos/provider/of/link/impl/OpenFlowLinkProviderTest.java
+++ b/providers/openflow/link/src/test/java/org/onlab/onos/provider/of/link/impl/OpenFlowLinkProviderTest.java
@@ -475,11 +475,12 @@
         }
 
         @Override
-        public void disconnectSwitch() {
+        public boolean isConnected() {
+            return true;
         }
 
         @Override
-        public void returnRoleAssertFailure(RoleState role) {
+        public void disconnectSwitch() {
         }
 
         @Override
@@ -487,5 +488,9 @@
             return false;
         }
 
+        @Override
+        public void returnRoleReply(RoleState requested, RoleState reponse) {
+        }
+
     }
 }
diff --git a/providers/openflow/packet/src/test/java/org/onlab/onos/provider/of/packet/impl/OpenFlowPacketProviderTest.java b/providers/openflow/packet/src/test/java/org/onlab/onos/provider/of/packet/impl/OpenFlowPacketProviderTest.java
index ed682d6..c27e084 100644
--- a/providers/openflow/packet/src/test/java/org/onlab/onos/provider/of/packet/impl/OpenFlowPacketProviderTest.java
+++ b/providers/openflow/packet/src/test/java/org/onlab/onos/provider/of/packet/impl/OpenFlowPacketProviderTest.java
@@ -406,11 +406,12 @@
         }
 
         @Override
-        public void disconnectSwitch() {
+        public boolean isConnected() {
+            return true;
         }
 
         @Override
-        public void returnRoleAssertFailure(RoleState role) {
+        public void disconnectSwitch() {
         }
 
         @Override
@@ -418,6 +419,10 @@
             return false;
         }
 
+        @Override
+        public void returnRoleReply(RoleState requested, RoleState reponse) {
+        }
+
     }
 
 }
diff --git a/tools/test/bin/find-node.sh b/tools/test/bin/find-node.sh
new file mode 100644
index 0000000..e76a84a
--- /dev/null
+++ b/tools/test/bin/find-node.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+validate_number () {
+    local re="^[0-9]+$"
+    if [[ ! $1 =~ $re ]] ; then
+	return 1
+    fi
+
+    return 0
+}
+
+find_node () {
+    if validate_number $1 ; then
+	# input is a number, try to find if an OC node is defined
+
+	oc_try="OC$1"
+	node=${!oc_try}
+
+	if [ -n "$node" ]; then
+    	    # node lookup succeeded, return node
+	    echo $node
+	else
+    	    # node lookup failed, return original input
+	    echo $1
+	fi
+
+    else
+	echo $1
+    fi
+
+    return 0
+}
diff --git a/tools/test/bin/onos b/tools/test/bin/onos
index 653b7f3c..a0c126c 100755
--- a/tools/test/bin/onos
+++ b/tools/test/bin/onos
@@ -5,8 +5,9 @@
 
 [ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1
 . $ONOS_ROOT/tools/build/envDefaults
+. $ONOS_ROOT/tools/test/bin/find-node.sh
 
 [ "$1" = "-w" ] && shift && onos-wait-for-start $1
 
-[ -n "$1" ] && OCI=$1 && shift
+[ -n "$1" ] && OCI=$(find_node $1) && shift
 client -h $OCI -u karaf "$@" 2>/dev/null
diff --git a/tools/test/bin/onos-log b/tools/test/bin/onos-log
index a336462..ff52eee 100755
--- a/tools/test/bin/onos-log
+++ b/tools/test/bin/onos-log
@@ -5,12 +5,14 @@
 
 [ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1
 . $ONOS_ROOT/tools/build/envDefaults
+. $ONOS_ROOT/tools/test/bin/find-node.sh
 
 less=0
-
 [ "$1" = "-l" ] && shift && less=1
 
-remote=$ONOS_USER@${1:-$OCI}
+remote=$(find_node $1)
+
+remote=$ONOS_USER@${remote:-$OCI}
 instance=$2
 
 [ -n "$instance" ] && \
diff --git a/utils/misc/src/main/java/org/onlab/packet/Ethernet.java b/utils/misc/src/main/java/org/onlab/packet/Ethernet.java
index 70a52e2..96ec021 100644
--- a/utils/misc/src/main/java/org/onlab/packet/Ethernet.java
+++ b/utils/misc/src/main/java/org/onlab/packet/Ethernet.java
@@ -18,6 +18,8 @@
 
 package org.onlab.packet;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
 import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -86,6 +88,17 @@
      * @param destMac the destination MAC to set
      * @return the Ethernet frame
      */
+    public Ethernet setDestinationMACAddress(final MacAddress destMac) {
+        this.destinationMACAddress = checkNotNull(destMac);
+        return this;
+    }
+
+    /**
+     * Sets the destination MAC address.
+     *
+     * @param destMac the destination MAC to set
+     * @return the Ethernet frame
+     */
     public Ethernet setDestinationMACAddress(final byte[] destMac) {
         this.destinationMACAddress = MacAddress.valueOf(destMac);
         return this;
@@ -126,6 +139,17 @@
      * @param sourceMac the source MAC to set
      * @return the Ethernet frame
      */
+    public Ethernet setSourceMACAddress(final MacAddress sourceMac) {
+        this.sourceMACAddress = checkNotNull(sourceMac);
+        return this;
+    }
+
+    /**
+     * Sets the source MAC address.
+     *
+     * @param sourceMac the source MAC to set
+     * @return the Ethernet frame
+     */
     public Ethernet setSourceMACAddress(final byte[] sourceMac) {
         this.sourceMACAddress = MacAddress.valueOf(sourceMac);
         return this;
diff --git a/utils/misc/src/main/java/org/onlab/packet/IpAddress.java b/utils/misc/src/main/java/org/onlab/packet/IpAddress.java
index 33be226..77ba9b0 100644
--- a/utils/misc/src/main/java/org/onlab/packet/IpAddress.java
+++ b/utils/misc/src/main/java/org/onlab/packet/IpAddress.java
@@ -15,10 +15,18 @@
  */
 package org.onlab.packet;
 
+import java.net.InetAddress;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.UnknownHostException;
 import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.Objects;
-import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.net.InetAddresses;
+import com.google.common.primitives.UnsignedBytes;
+
+import static com.google.common.base.Preconditions.checkState;
 
 /**
  * A class representing an IP address.
@@ -40,87 +48,26 @@
     /**
      * Constructor for given IP address version and address octets.
      *
+     * @param version the IP address version
      * @param value the IP address value stored in network byte order
      * (i.e., the most significant byte first)
-     * @param value the IP address value
+     * @throws IllegalArgumentException if the arguments are invalid
      */
     private IpAddress(Version version, byte[] value) {
+        checkArguments(version, value, 0);
         this.version = version;
-        this.octets = Arrays.copyOf(value, INET_BYTE_LENGTH);
-    }
-
-    /**
-     * Converts an integer into an IPv4 address.
-     *
-     * @param value an integer representing an IPv4 address value
-     * @return an IP address
-     */
-    public static IpAddress valueOf(int value) {
-        byte[] bytes =
-            ByteBuffer.allocate(INET_BYTE_LENGTH).putInt(value).array();
-        return new IpAddress(Version.INET, bytes);
-    }
-
-    /**
-     * Converts a byte array into an IP address.
-     *
-     * @param value the IP address value stored in network byte order
-     * (i.e., the most significant byte first)
-     * @return an IP address
-     */
-    public static IpAddress valueOf(byte[] value) {
-        checkNotNull(value);
-        return new IpAddress(Version.INET, value);
-    }
-
-    /**
-     * Converts a byte array and a given offset from the beginning of the
-     * array into an IP address.
-     * <p>
-     * The IP address is stored in network byte order (i.e., the most
-     * significant byte first).
-     * </p>
-     * @param value the value to use
-     * @param offset the offset in bytes from the beginning of the byte array
-     * @return an IP address
-     */
-    public static IpAddress valueOf(byte[] value, int offset) {
-        // Verify the arguments
-        if ((offset < 0) || (offset + INET_BYTE_LENGTH > value.length)) {
-            String msg;
-            if (value.length < INET_BYTE_LENGTH) {
-                msg = "Invalid IPv4 address array: array length: " +
-                    value.length + ". Must be at least " + INET_BYTE_LENGTH;
-            } else {
-                msg = "Invalid IPv4 address array: array offset: " +
-                    offset + ". Must be in the interval [0, " +
-                    (value.length - INET_BYTE_LENGTH) + "]";
-            }
-            throw new IllegalArgumentException(msg);
+        switch (version) {
+        case INET:
+            this.octets = Arrays.copyOf(value, INET_BYTE_LENGTH);
+            break;
+        case INET6:
+            this.octets = Arrays.copyOf(value, INET6_BYTE_LENGTH);
+            break;
+        default:
+            // Should not be reached
+            this.octets = null;
+            break;
         }
-
-        byte[] bc = Arrays.copyOfRange(value, offset, value.length);
-        return IpAddress.valueOf(bc);
-    }
-
-    /**
-     * Converts a dotted-decimal string (x.x.x.x) into an IPv4 address.
-     *
-     * @param address an IP address in string form, e.g. "10.0.0.1"
-     * @return an IP address
-     */
-    public static IpAddress valueOf(String address) {
-        final String[] net = address.split("\\.");
-        if (net.length != INET_BYTE_LENGTH) {
-            String msg = "Malformed IPv4 address string: " + address + "." +
-                "Address must have four decimal values separated by dots (.)";
-            throw new IllegalArgumentException(msg);
-        }
-        final byte[] bytes = new byte[INET_BYTE_LENGTH];
-        for (int i = 0; i < INET_BYTE_LENGTH; i++) {
-            bytes[i] = (byte) Short.parseShort(net[i], 10);
-        }
-        return new IpAddress(Version.INET, bytes);
     }
 
     /**
@@ -138,11 +85,12 @@
      * @return a byte array
      */
     public byte[] toOctets() {
-        return Arrays.copyOf(this.octets, INET_BYTE_LENGTH);
+        return Arrays.copyOf(octets, octets.length);
     }
 
     /**
      * Returns the integral value of this IP address.
+     * TODO: This method should be moved to Ip4Address.
      *
      * @return the IP address's value as an integer
      */
@@ -152,24 +100,152 @@
     }
 
     /**
+     * Computes the IP address byte length for a given IP version.
+     *
+     * @param version the IP version
+     * @return the IP address byte length for the IP version
+     * @throws IllegalArgumentException if the IP version is invalid
+     */
+    public static int byteLength(Version version) {
+        switch (version) {
+        case INET:
+            return INET_BYTE_LENGTH;
+        case INET6:
+            return INET6_BYTE_LENGTH;
+        default:
+            String msg = "Invalid IP version " + version;
+            throw new IllegalArgumentException(msg);
+        }
+    }
+
+    /**
+     * Converts an integer into an IPv4 address.
+     *
+     * @param value an integer representing an IPv4 address value
+     * @return an IP address
+     */
+    public static IpAddress valueOf(int value) {
+        byte[] bytes =
+            ByteBuffer.allocate(INET_BYTE_LENGTH).putInt(value).array();
+        return new IpAddress(Version.INET, bytes);
+    }
+
+    /**
+     * Converts a byte array into an IP address.
+     *
+     * @param version the IP address version
+     * @param value the IP address value stored in network byte order
+     * (i.e., the most significant byte first)
+     * @return an IP address
+     * @throws IllegalArgumentException if the arguments are invalid
+     */
+    public static IpAddress valueOf(Version version, byte[] value) {
+        return new IpAddress(version, value);
+    }
+
+    /**
+     * Converts a byte array and a given offset from the beginning of the
+     * array into an IP address.
+     * <p>
+     * The IP address is stored in network byte order (i.e., the most
+     * significant byte first).
+     * </p>
+     * @param version the IP address version
+     * @param value the value to use
+     * @param offset the offset in bytes from the beginning of the byte array
+     * @return an IP address
+     * @throws IllegalArgumentException if the arguments are invalid
+     */
+    public static IpAddress valueOf(Version version, byte[] value,
+                                    int offset) {
+        checkArguments(version, value, offset);
+        byte[] bc = Arrays.copyOfRange(value, offset, value.length);
+        return IpAddress.valueOf(version, bc);
+    }
+
+    /**
+     * Converts an InetAddress into an IP address.
+     *
+     * @param inetAddress the InetAddress value to use
+     * @return an IP address
+     * @throws IllegalArgumentException if the argument is invalid
+     */
+    public static IpAddress valueOf(InetAddress inetAddress) {
+        byte[] bytes = inetAddress.getAddress();
+        if (inetAddress instanceof Inet4Address) {
+            return new IpAddress(Version.INET, bytes);
+        }
+        if (inetAddress instanceof Inet6Address) {
+            return new IpAddress(Version.INET6, bytes);
+        }
+        // Use the number of bytes as a hint
+        if (bytes.length == INET_BYTE_LENGTH) {
+            return new IpAddress(Version.INET, bytes);
+        }
+        if (bytes.length == INET6_BYTE_LENGTH) {
+            return new IpAddress(Version.INET6, bytes);
+        }
+        final String msg = "Unrecognized IP version address string: " +
+            inetAddress.toString();
+        throw new IllegalArgumentException(msg);
+    }
+
+    /**
+     * Converts an IPv4 or IPv6 string literal (e.g., "10.2.3.4" or
+     * "1111:2222::8888") into an IP address.
+     *
+     * @param value an IP address value in string form
+     * @return an IP address
+     * @throws IllegalArgumentException if the argument is invalid
+     */
+    public static IpAddress valueOf(String value) {
+        InetAddress inetAddress = null;
+        try {
+            inetAddress = InetAddresses.forString(value);
+        } catch (IllegalArgumentException e) {
+            final String msg = "Invalid IP address string: " + value;
+            throw new IllegalArgumentException(msg);
+        }
+        return valueOf(inetAddress);
+    }
+
+    /**
      * Creates an IP network mask prefix.
      *
+     * @param version the IP address version
      * @param prefixLength the length of the mask prefix. Must be in the
-     * interval [0, 32] for IPv4
+     * interval [0, 32] for IPv4, or [0, 128] for IPv6
      * @return a new IP address that contains a mask prefix of the
      * specified length
+     * @throws IllegalArgumentException if the arguments are invalid
      */
-    public static IpAddress makeMaskPrefix(int prefixLength) {
+    public static IpAddress makeMaskPrefix(Version version, int prefixLength) {
+        int addrByteLength = byteLength(version);
+        int addrBitLength = addrByteLength * Byte.SIZE;
+
         // Verify the prefix length
-        if ((prefixLength < 0) || (prefixLength > INET_BIT_LENGTH)) {
-            final String msg = "Invalid IPv4 prefix length: " + prefixLength +
-                ". Must be in the interval [0, 32].";
+        if ((prefixLength < 0) || (prefixLength > addrBitLength)) {
+            final String msg = "Invalid IP prefix length: " + prefixLength +
+                ". Must be in the interval [0, " + addrBitLength + "].";
             throw new IllegalArgumentException(msg);
         }
 
-        long v =
-            (0xffffffffL << (INET_BIT_LENGTH - prefixLength)) & 0xffffffffL;
-        return IpAddress.valueOf((int) v);
+        // Number of bytes and extra bits that should be all 1s
+        int maskBytes = prefixLength / Byte.SIZE;
+        int maskBits = prefixLength % Byte.SIZE;
+        byte[] mask = new byte[addrByteLength];
+
+        // Set the bytes and extra bits to 1s
+        for (int i = 0; i < maskBytes; i++) {
+            mask[i] = (byte) 0xff;              // Set mask bytes to 1s
+        }
+        for (int i = maskBytes; i < addrByteLength; i++) {
+            mask[i] = 0;                        // Set remaining bytes to 0s
+        }
+        if (maskBits > 0) {
+            mask[maskBytes] = (byte) (0xff << (Byte.SIZE - maskBits));
+        }
+        return new IpAddress(version, mask);
     }
 
     /**
@@ -178,27 +254,38 @@
      *
      * @param addr the address to mask
      * @param prefixLength the length of the mask prefix. Must be in the
-     * interval [0, 32] for IPv4
+     * interval [0, 32] for IPv4, or [0, 128] for IPv6
      * @return a new IP address that is masked with a mask prefix of the
      * specified length
+     * @throws IllegalArgumentException if the prefix length is invalid
      */
     public static IpAddress makeMaskedAddress(final IpAddress addr,
                                               int prefixLength) {
-        IpAddress mask = IpAddress.makeMaskPrefix(prefixLength);
-        byte[] net = new byte[INET_BYTE_LENGTH];
+        IpAddress mask = IpAddress.makeMaskPrefix(addr.version(),
+                                                  prefixLength);
+        byte[] net = new byte[mask.octets.length];
 
         // Mask each byte
-        for (int i = 0; i < INET_BYTE_LENGTH; i++) {
+        for (int i = 0; i < net.length; i++) {
             net[i] = (byte) (addr.octets[i] & mask.octets[i]);
         }
-        return IpAddress.valueOf(net);
+        return IpAddress.valueOf(addr.version(), net);
     }
 
     @Override
     public int compareTo(IpAddress o) {
-        Long lv = ((long) this.toInt()) & 0xffffffffL;
-        Long rv = ((long) o.toInt()) & 0xffffffffL;
-        return lv.compareTo(rv);
+        // Compare first the version
+        if (this.version != o.version) {
+            return this.version.compareTo(o.version);
+        }
+
+        // Compare the bytes, one-by-one
+        for (int i = 0; i < this.octets.length; i++) {
+            if (this.octets[i] != o.octets[i]) {
+                return UnsignedBytes.compare(this.octets[i], o.octets[i]);
+            }
+        }
+        return 0;       // Equal
     }
 
     @Override
@@ -222,18 +309,67 @@
     @Override
     /*
      * (non-Javadoc)
-     * The format is "x.x.x.x" for IPv4 addresses.
+     * The string representation of the IP address: "x.x.x.x" for IPv4
+     * addresses, or ':' separated string for IPv6 addresses.
      *
      * @see java.lang.Object#toString()
      */
     public String toString() {
-        final StringBuilder builder = new StringBuilder();
-        for (final byte b : this.octets) {
-            if (builder.length() > 0) {
-                builder.append(".");
-            }
-            builder.append(String.format("%d", b & 0xff));
+        InetAddress inetAddr = null;
+        try {
+            inetAddr = InetAddress.getByAddress(octets);
+        } catch (UnknownHostException e) {
+            // Should never happen
+            checkState(false, "Internal error: Ip6Address.toString()");
+            return "[Invalid IP Address]";
         }
-        return builder.toString();
+        return InetAddresses.toAddrString(inetAddr);
+    }
+
+    /**
+     * Gets the IP address name for the IP address version.
+     *
+     * @param version the IP address version
+     * @return the IP address name for the IP address version
+     */
+    private static String addressName(Version version) {
+        switch (version) {
+        case INET:
+            return "IPv4";
+        case INET6:
+            return "IPv6";
+        default:
+            break;
+        }
+        return "UnknownIP(" + version + ")";
+    }
+
+    /**
+     * Checks whether the arguments are valid.
+     *
+     * @param version the IP address version
+     * @param value the IP address value stored in a byte array
+     * @param offset the offset in bytes from the beginning of the byte
+     * array with the address
+     * @throws IllegalArgumentException if any of the arguments is invalid
+     */
+    private static void checkArguments(Version version, byte[] value,
+                                       int offset) {
+        // Check the offset and byte array length
+        int addrByteLength = byteLength(version);
+        if ((offset < 0) || (offset + addrByteLength > value.length)) {
+            String msg;
+            if (value.length < addrByteLength) {
+                msg = "Invalid " + addressName(version) +
+                    " address array: array length: " + value.length +
+                    ". Must be at least " + addrByteLength;
+            } else {
+                msg = "Invalid " + addressName(version) +
+                    " address array: array offset: " + offset +
+                    ". Must be in the interval [0, " +
+                    (value.length - addrByteLength) + "]";
+            }
+            throw new IllegalArgumentException(msg);
+        }
     }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/IpPrefix.java b/utils/misc/src/main/java/org/onlab/packet/IpPrefix.java
index 180b771..3cde79d 100644
--- a/utils/misc/src/main/java/org/onlab/packet/IpPrefix.java
+++ b/utils/misc/src/main/java/org/onlab/packet/IpPrefix.java
@@ -76,12 +76,15 @@
     /**
      * Converts a byte array and a prefix length into an IP prefix.
      *
+     * @param version the IP address version
      * @param address the IP address value stored in network byte order
      * @param prefixLength the prefix length
      * @return an IP prefix
      */
-    public static IpPrefix valueOf(byte[] address, int prefixLength) {
-        return new IpPrefix(IpAddress.valueOf(address), prefixLength);
+    public static IpPrefix valueOf(IpAddress.Version version, byte[] address,
+                                   int prefixLength) {
+        return new IpPrefix(IpAddress.valueOf(version, address),
+                            prefixLength);
     }
 
     /**
diff --git a/utils/misc/src/main/java/org/onlab/packet/MacAddress.java b/utils/misc/src/main/java/org/onlab/packet/MacAddress.java
index 5675a31..5c59d22 100644
--- a/utils/misc/src/main/java/org/onlab/packet/MacAddress.java
+++ b/utils/misc/src/main/java/org/onlab/packet/MacAddress.java
@@ -25,9 +25,6 @@
     public static final MacAddress ZERO = valueOf("00:00:00:00:00:00");
     public static final MacAddress BROADCAST = valueOf("ff:ff:ff:ff:ff:ff");
 
-    public static final byte[] ZERO_MAC_ADDRESS = ZERO.getAddress();
-    public static final byte[] BROADCAST_MAC = BROADCAST.getAddress();
-
     private static final byte[] LL = new byte[]{
             0x01, (byte) 0x80, (byte) 0xc2, 0x00, 0x00,
             0x00, 0x0e, 0x03
@@ -217,8 +214,4 @@
         }
         return builder.toString();
     }
-
-    public byte[] getAddress() {
-        return this.address;
-    }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/ONOSLLDP.java b/utils/misc/src/main/java/org/onlab/packet/ONOSLLDP.java
index df3880f..0b33c45 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ONOSLLDP.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ONOSLLDP.java
@@ -64,7 +64,7 @@
         setName(DEFAULT_NAME);
         setDevice(DEFAULT_DEVICE);
         setOptionalTLVList(Lists.<LLDPTLV>newArrayList(nameTLV, deviceTLV));
-        setTtl(new LLDPTLV().setType((byte) TTL_TLV_TYPE)
+        setTtl(new LLDPTLV().setType(TTL_TLV_TYPE)
                        .setLength((short) ttlValue.length)
                        .setValue(ttlValue));
 
@@ -94,7 +94,7 @@
     public void setChassisId(final ChassisId chassisId) {
         MacAddress chassisMac = MacAddress.valueOf(chassisId.value());
         byte[] chassis = ArrayUtils.addAll(new byte[] {CHASSIS_TLV_SUBTYPE},
-                                           chassisMac.getAddress());
+                                           chassisMac.toBytes());
 
         LLDPTLV chassisTLV = new LLDPTLV();
         chassisTLV.setLength(CHASSIS_TLV_SIZE);
diff --git a/utils/misc/src/test/java/org/onlab/packet/IpAddressTest.java b/utils/misc/src/test/java/org/onlab/packet/IpAddressTest.java
new file mode 100644
index 0000000..4237829
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/packet/IpAddressTest.java
@@ -0,0 +1,810 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed 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.packet;
+
+import com.google.common.net.InetAddresses;
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+
+import java.net.InetAddress;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+/**
+ * Tests for class {@link IpAddress}.
+ */
+public class IpAddressTest {
+    /**
+     * Tests the immutability of {@link IpAddress}.
+     */
+    @Test
+    public void testImmutable() {
+        assertThatClassIsImmutable(IpAddress.class);
+    }
+
+    /**
+     * Tests the length of the address in bytes (octets).
+     */
+    @Test
+    public void testAddrByteLength() {
+        assertThat(IpAddress.INET_BYTE_LENGTH, is(4));
+        assertThat(IpAddress.INET6_BYTE_LENGTH, is(16));
+        assertThat(IpAddress.byteLength(IpAddress.Version.INET), is(4));
+        assertThat(IpAddress.byteLength(IpAddress.Version.INET6), is(16));
+    }
+
+    /**
+     * Tests the length of the address in bits.
+     */
+    @Test
+    public void testAddrBitLength() {
+        assertThat(IpAddress.INET_BIT_LENGTH, is(32));
+        assertThat(IpAddress.INET6_BIT_LENGTH, is(128));
+    }
+
+    /**
+     * Tests returning the IP address version.
+     */
+    @Test
+    public void testVersion() {
+        IpAddress ipAddress;
+
+        // IPv4
+        ipAddress = IpAddress.valueOf("0.0.0.0");
+        assertThat(ipAddress.version(), is(IpAddress.Version.INET));
+
+        // IPv6
+        ipAddress = IpAddress.valueOf("::");
+        assertThat(ipAddress.version(), is(IpAddress.Version.INET6));
+    }
+
+    /**
+     * Tests returning an IPv4 address as a byte array.
+     */
+    @Test
+    public void testAddressToOctetsIPv4() {
+        IpAddress ipAddress;
+
+        final byte[] value1 = new byte[] {1, 2, 3, 4};
+        ipAddress = IpAddress.valueOf("1.2.3.4");
+        assertThat(ipAddress.toOctets(), is(value1));
+
+        final byte[] value2 = new byte[] {0, 0, 0, 0};
+        ipAddress = IpAddress.valueOf("0.0.0.0");
+        assertThat(ipAddress.toOctets(), is(value2));
+
+        final byte[] value3 = new byte[] {(byte) 0xff, (byte) 0xff,
+                                          (byte) 0xff, (byte) 0xff};
+        ipAddress = IpAddress.valueOf("255.255.255.255");
+        assertThat(ipAddress.toOctets(), is(value3));
+    }
+
+    /**
+     * Tests returning an IPv6 address as a byte array.
+     */
+    @Test
+    public void testAddressToOctetsIPv6() {
+        IpAddress ipAddress;
+
+        final byte[] value1 = new byte[] {0x11, 0x11, 0x22, 0x22,
+                                          0x33, 0x33, 0x44, 0x44,
+                                          0x55, 0x55, 0x66, 0x66,
+                                          0x77, 0x77,
+                                          (byte) 0x88, (byte) 0x88};
+        ipAddress =
+            IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8888");
+        assertThat(ipAddress.toOctets(), is(value1));
+
+        final byte[] value2 = new byte[] {0x00, 0x00, 0x00, 0x00,
+                                          0x00, 0x00, 0x00, 0x00,
+                                          0x00, 0x00, 0x00, 0x00,
+                                          0x00, 0x00, 0x00, 0x00};
+        ipAddress = IpAddress.valueOf("::");
+        assertThat(ipAddress.toOctets(), is(value2));
+
+        final byte[] value3 = new byte[] {(byte) 0xff, (byte) 0xff,
+                                          (byte) 0xff, (byte) 0xff,
+                                          (byte) 0xff, (byte) 0xff,
+                                          (byte) 0xff, (byte) 0xff,
+                                          (byte) 0xff, (byte) 0xff,
+                                          (byte) 0xff, (byte) 0xff,
+                                          (byte) 0xff, (byte) 0xff,
+                                          (byte) 0xff, (byte) 0xff};
+        ipAddress =
+            IpAddress.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
+        assertThat(ipAddress.toOctets(), is(value3));
+    }
+
+    /**
+     * Tests returning an IPv4 address asn an integer.
+     */
+    @Test
+    public void testToint() {
+        IpAddress ipAddress;
+
+        ipAddress = IpAddress.valueOf("1.2.3.4");
+        assertThat(ipAddress.toInt(), is(0x01020304));
+
+        ipAddress = IpAddress.valueOf("0.0.0.0");
+        assertThat(ipAddress.toInt(), is(0));
+
+        ipAddress = IpAddress.valueOf("255.255.255.255");
+        assertThat(ipAddress.toInt(), is(-1));
+    }
+
+    /**
+     * Tests valueOf() converter for an integer value.
+     */
+    @Test
+    public void testValueOfForInteger() {
+        IpAddress ipAddress;
+
+        ipAddress = IpAddress.valueOf(0x01020304);
+        assertThat(ipAddress.toString(), is("1.2.3.4"));
+
+        ipAddress = IpAddress.valueOf(0);
+        assertThat(ipAddress.toString(), is("0.0.0.0"));
+
+        ipAddress = IpAddress.valueOf(0xffffffff);
+        assertThat(ipAddress.toString(), is("255.255.255.255"));
+    }
+
+    /**
+     * Tests valueOf() converter for IPv4 byte array.
+     */
+    @Test
+    public void testValueOfByteArrayIPv4() {
+        IpAddress ipAddress;
+
+        final byte[] value1 = new byte[] {1, 2, 3, 4};
+        ipAddress = IpAddress.valueOf(IpAddress.Version.INET, value1);
+        assertThat(ipAddress.toString(), is("1.2.3.4"));
+
+        final byte[] value2 = new byte[] {0, 0, 0, 0};
+        ipAddress = IpAddress.valueOf(IpAddress.Version.INET, value2);
+        assertThat(ipAddress.toString(), is("0.0.0.0"));
+
+        final byte[] value3 = new byte[] {(byte) 0xff, (byte) 0xff,
+                                          (byte) 0xff, (byte) 0xff};
+        ipAddress = IpAddress.valueOf(IpAddress.Version.INET, value3);
+        assertThat(ipAddress.toString(), is("255.255.255.255"));
+    }
+
+    /**
+     * Tests valueOf() converter for IPv6 byte array.
+     */
+    @Test
+    public void testValueOfByteArrayIPv6() {
+        IpAddress ipAddress;
+
+        final byte[] value1 = new byte[] {0x11, 0x11, 0x22, 0x22,
+                                          0x33, 0x33, 0x44, 0x44,
+                                          0x55, 0x55, 0x66, 0x66,
+                                          0x77, 0x77,
+                                          (byte) 0x88, (byte) 0x88};
+        ipAddress = IpAddress.valueOf(IpAddress.Version.INET6, value1);
+        assertThat(ipAddress.toString(),
+                   is("1111:2222:3333:4444:5555:6666:7777:8888"));
+
+        final byte[] value2 = new byte[] {0x00, 0x00, 0x00, 0x00,
+                                          0x00, 0x00, 0x00, 0x00,
+                                          0x00, 0x00, 0x00, 0x00,
+                                          0x00, 0x00, 0x00, 0x00};
+        ipAddress = IpAddress.valueOf(IpAddress.Version.INET6, value2);
+        assertThat(ipAddress.toString(), is("::"));
+
+        final byte[] value3 = new byte[] {(byte) 0xff, (byte) 0xff,
+                                          (byte) 0xff, (byte) 0xff,
+                                          (byte) 0xff, (byte) 0xff,
+                                          (byte) 0xff, (byte) 0xff,
+                                          (byte) 0xff, (byte) 0xff,
+                                          (byte) 0xff, (byte) 0xff,
+                                          (byte) 0xff, (byte) 0xff,
+                                          (byte) 0xff, (byte) 0xff};
+        ipAddress = IpAddress.valueOf(IpAddress.Version.INET6, value3);
+        assertThat(ipAddress.toString(),
+                   is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"));
+    }
+
+    /**
+     * Tests invalid valueOf() converter for a null array for IPv4.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testInvalidValueOfNullArrayIPv4() {
+        IpAddress ipAddress;
+
+        final byte[] fromArray = null;
+        ipAddress = IpAddress.valueOf(IpAddress.Version.INET, fromArray);
+    }
+
+    /**
+     * Tests invalid valueOf() converter for a null array for IPv6.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testInvalidValueOfNullArrayIPv6() {
+        IpAddress ipAddress;
+
+        final byte[] fromArray = null;
+        ipAddress = IpAddress.valueOf(IpAddress.Version.INET6, fromArray);
+    }
+
+    /**
+     * Tests invalid valueOf() converger for an array that is too short for
+     * IPv4.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidValueOfShortArrayIPv4() {
+        IpAddress ipAddress;
+
+        final byte[] fromArray = new byte[] {1, 2, 3};
+        ipAddress = IpAddress.valueOf(IpAddress.Version.INET, fromArray);
+    }
+
+    /**
+     * Tests invalid valueOf() converger for an array that is too short for
+     * IPv6.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidValueOfShortArrayIPv6() {
+        IpAddress ipAddress;
+
+        final byte[] fromArray = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9};
+        ipAddress = IpAddress.valueOf(IpAddress.Version.INET6, fromArray);
+    }
+
+    /**
+     * Tests valueOf() converter for IPv4 byte array and an offset.
+     */
+    @Test
+    public void testValueOfByteArrayOffsetIPv4() {
+        IpAddress ipAddress;
+
+        final byte[] value1 = new byte[] {11, 22, 33,   // Preamble
+                                          1, 2, 3, 4,
+                                          44, 55};      // Extra bytes
+        ipAddress = IpAddress.valueOf(IpAddress.Version.INET, value1, 3);
+        assertThat(ipAddress.toString(), is("1.2.3.4"));
+
+        final byte[] value2 = new byte[] {11, 22,       // Preamble
+                                          0, 0, 0, 0,
+                                          33};          // Extra bytes
+        ipAddress = IpAddress.valueOf(IpAddress.Version.INET, value2, 2);
+        assertThat(ipAddress.toString(), is("0.0.0.0"));
+
+        final byte[] value3 = new byte[] {11, 22,       // Preamble
+                                          (byte) 0xff, (byte) 0xff,
+                                          (byte) 0xff, (byte) 0xff,
+                                          33};          // Extra bytes
+        ipAddress = IpAddress.valueOf(IpAddress.Version.INET, value3, 2);
+        assertThat(ipAddress.toString(), is("255.255.255.255"));
+    }
+
+    /**
+     * Tests valueOf() converter for IPv6 byte array and an offset.
+     */
+    @Test
+    public void testValueOfByteArrayOffsetIPv6() {
+        IpAddress ipAddress;
+
+        final byte[] value1 = new byte[] {11, 22, 33,           // Preamble
+                                          0x11, 0x11, 0x22, 0x22,
+                                          0x33, 0x33, 0x44, 0x44,
+                                          0x55, 0x55, 0x66, 0x66,
+                                          0x77, 0x77,
+                                          (byte) 0x88, (byte) 0x88,
+                                          44, 55};              // Extra bytes
+        ipAddress = IpAddress.valueOf(IpAddress.Version.INET6, value1, 3);
+        assertThat(ipAddress.toString(),
+                   is("1111:2222:3333:4444:5555:6666:7777:8888"));
+
+        final byte[] value2 = new byte[] {11, 22,               // Preamble
+                                          0x00, 0x00, 0x00, 0x00,
+                                          0x00, 0x00, 0x00, 0x00,
+                                          0x00, 0x00, 0x00, 0x00,
+                                          0x00, 0x00, 0x00, 0x00,
+                                          33};                  // Extra bytes
+        ipAddress = IpAddress.valueOf(IpAddress.Version.INET6, value2, 2);
+        assertThat(ipAddress.toString(), is("::"));
+
+        final byte[] value3 = new byte[] {11, 22,               // Preamble
+                                          (byte) 0xff, (byte) 0xff,
+                                          (byte) 0xff, (byte) 0xff,
+                                          (byte) 0xff, (byte) 0xff,
+                                          (byte) 0xff, (byte) 0xff,
+                                          (byte) 0xff, (byte) 0xff,
+                                          (byte) 0xff, (byte) 0xff,
+                                          (byte) 0xff, (byte) 0xff,
+                                          (byte) 0xff, (byte) 0xff,
+                                          33};                  // Extra bytes
+        ipAddress = IpAddress.valueOf(IpAddress.Version.INET6, value3, 2);
+        assertThat(ipAddress.toString(),
+                   is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"));
+    }
+
+    /**
+     * Tests invalid valueOf() converger for an array and an invalid offset
+     * for IPv4.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidValueOfArrayInvalidOffsetIPv4() {
+        IpAddress ipAddress;
+
+        final byte[] value1 = new byte[] {11, 22, 33,   // Preamble
+                                          1, 2, 3, 4,
+                                          44, 55};      // Extra bytes
+        ipAddress = IpAddress.valueOf(IpAddress.Version.INET, value1, 6);
+    }
+
+    /**
+     * Tests invalid valueOf() converger for an array and an invalid offset
+     * for IPv6.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidValueOfArrayInvalidOffsetIPv6() {
+        IpAddress ipAddress;
+
+        final byte[] value1 = new byte[] {11, 22, 33,           // Preamble
+                                          0x11, 0x11, 0x22, 0x22,
+                                          0x33, 0x33, 0x44, 0x44,
+                                          0x55, 0x55, 0x66, 0x66,
+                                          0x77, 0x77,
+                                          (byte) 0x88, (byte) 0x88,
+                                          44, 55};              // Extra bytes
+        ipAddress = IpAddress.valueOf(IpAddress.Version.INET6, value1, 6);
+    }
+
+    /**
+     * Tests valueOf() converter for IPv4 InetAddress.
+     */
+    @Test
+    public void testValueOfInetAddressIPv4() {
+        IpAddress ipAddress;
+        InetAddress inetAddress;
+
+        inetAddress = InetAddresses.forString("1.2.3.4");
+        ipAddress = IpAddress.valueOf(inetAddress);
+        assertThat(ipAddress.toString(), is("1.2.3.4"));
+
+        inetAddress = InetAddresses.forString("0.0.0.0");
+        ipAddress = IpAddress.valueOf(inetAddress);
+        assertThat(ipAddress.toString(), is("0.0.0.0"));
+
+        inetAddress = InetAddresses.forString("255.255.255.255");
+        ipAddress = IpAddress.valueOf(inetAddress);
+        assertThat(ipAddress.toString(), is("255.255.255.255"));
+    }
+
+    /**
+     * Tests valueOf() converter for IPv6 InetAddress.
+     */
+    @Test
+    public void testValueOfInetAddressIPv6() {
+        IpAddress ipAddress;
+        InetAddress inetAddress;
+
+        inetAddress =
+            InetAddresses.forString("1111:2222:3333:4444:5555:6666:7777:8888");
+        ipAddress = IpAddress.valueOf(inetAddress);
+        assertThat(ipAddress.toString(),
+                   is("1111:2222:3333:4444:5555:6666:7777:8888"));
+
+        inetAddress = InetAddresses.forString("::");
+        ipAddress = IpAddress.valueOf(inetAddress);
+        assertThat(ipAddress.toString(), is("::"));
+
+        inetAddress =
+            InetAddresses.forString("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
+        ipAddress = IpAddress.valueOf(inetAddress);
+        assertThat(ipAddress.toString(),
+                   is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"));
+    }
+
+    /**
+     * Tests valueOf() converter for IPv4 string.
+     */
+    @Test
+    public void testValueOfStringIPv4() {
+        IpAddress ipAddress;
+
+        ipAddress = IpAddress.valueOf("1.2.3.4");
+        assertThat(ipAddress.toString(), is("1.2.3.4"));
+
+        ipAddress = IpAddress.valueOf("0.0.0.0");
+        assertThat(ipAddress.toString(), is("0.0.0.0"));
+
+        ipAddress = IpAddress.valueOf("255.255.255.255");
+        assertThat(ipAddress.toString(), is("255.255.255.255"));
+    }
+
+    /**
+     * Tests valueOf() converter for IPv6 string.
+     */
+    @Test
+    public void testValueOfStringIPv6() {
+        IpAddress ipAddress;
+
+        ipAddress =
+            IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8888");
+        assertThat(ipAddress.toString(),
+                   is("1111:2222:3333:4444:5555:6666:7777:8888"));
+
+        ipAddress = IpAddress.valueOf("::");
+        assertThat(ipAddress.toString(), is("::"));
+
+        ipAddress =
+            IpAddress.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
+        assertThat(ipAddress.toString(),
+                   is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"));
+    }
+
+    /**
+     * Tests invalid valueOf() converter for a null string.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testInvalidValueOfNullString() {
+        IpAddress ipAddress;
+
+        String fromString = null;
+        ipAddress = IpAddress.valueOf(fromString);
+    }
+
+    /**
+     * Tests invalid valueOf() converter for an empty string.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidValueOfEmptyString() {
+        IpAddress ipAddress;
+
+        String fromString = "";
+        ipAddress = IpAddress.valueOf(fromString);
+    }
+
+    /**
+     * Tests invalid valueOf() converter for an incorrect string.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidValueOfIncorrectString() {
+        IpAddress ipAddress;
+
+        String fromString = "NoSuchIpAddress";
+        ipAddress = IpAddress.valueOf(fromString);
+    }
+
+    /**
+     * Tests making a mask prefix for a given prefix length for IPv4.
+     */
+    @Test
+    public void testMakeMaskPrefixIPv4() {
+        IpAddress ipAddress;
+
+        ipAddress = IpAddress.makeMaskPrefix(IpAddress.Version.INET, 25);
+        assertThat(ipAddress.toString(), is("255.255.255.128"));
+
+        ipAddress = IpAddress.makeMaskPrefix(IpAddress.Version.INET, 0);
+        assertThat(ipAddress.toString(), is("0.0.0.0"));
+
+        ipAddress = IpAddress.makeMaskPrefix(IpAddress.Version.INET, 32);
+        assertThat(ipAddress.toString(), is("255.255.255.255"));
+    }
+
+    /**
+     * Tests making a mask prefix for a given prefix length for IPv6.
+     */
+    @Test
+    public void testMakeMaskPrefixIPv6() {
+        IpAddress ipAddress;
+
+        ipAddress = IpAddress.makeMaskPrefix(IpAddress.Version.INET6, 8);
+        assertThat(ipAddress.toString(), is("ff00::"));
+
+        ipAddress = IpAddress.makeMaskPrefix(IpAddress.Version.INET6, 120);
+        assertThat(ipAddress.toString(),
+                   is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00"));
+
+        ipAddress = IpAddress.makeMaskPrefix(IpAddress.Version.INET6, 0);
+        assertThat(ipAddress.toString(), is("::"));
+
+        ipAddress = IpAddress.makeMaskPrefix(IpAddress.Version.INET6, 128);
+        assertThat(ipAddress.toString(),
+                   is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"));
+
+        ipAddress = IpAddress.makeMaskPrefix(IpAddress.Version.INET6, 64);
+        assertThat(ipAddress.toString(), is("ffff:ffff:ffff:ffff::"));
+    }
+
+    /**
+     * Tests making a mask prefix for an invalid prefix length for IPv4:
+     * negative prefix length.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidMakeNegativeMaskPrefixIPv4() {
+        IpAddress ipAddress;
+
+        ipAddress = IpAddress.makeMaskPrefix(IpAddress.Version.INET, -1);
+    }
+
+    /**
+     * Tests making a mask prefix for an invalid prefix length for IPv6:
+     * negative prefix length.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidMakeNegativeMaskPrefixIPv6() {
+        IpAddress ipAddress;
+
+        ipAddress = IpAddress.makeMaskPrefix(IpAddress.Version.INET6, -1);
+    }
+
+    /**
+     * Tests making a mask prefix for an invalid prefix length for IPv4:
+     * too long prefix length.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidMakeTooLongMaskPrefixIPv4() {
+        IpAddress ipAddress;
+
+        ipAddress = IpAddress.makeMaskPrefix(IpAddress.Version.INET, 33);
+    }
+
+    /**
+     * Tests making a mask prefix for an invalid prefix length for IPv6:
+     * too long prefix length.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidMakeTooLongMaskPrefixIPv6() {
+        IpAddress ipAddress;
+
+        ipAddress = IpAddress.makeMaskPrefix(IpAddress.Version.INET6, 129);
+    }
+
+    /**
+     * Tests making of a masked address for IPv4.
+     */
+    @Test
+    public void testMakeMaskedAddressIPv4() {
+        IpAddress ipAddress = IpAddress.valueOf("1.2.3.5");
+        IpAddress ipAddressMasked;
+
+        ipAddressMasked = IpAddress.makeMaskedAddress(ipAddress, 24);
+        assertThat(ipAddressMasked.toString(), is("1.2.3.0"));
+
+        ipAddressMasked = IpAddress.makeMaskedAddress(ipAddress, 0);
+        assertThat(ipAddressMasked.toString(), is("0.0.0.0"));
+
+        ipAddressMasked = IpAddress.makeMaskedAddress(ipAddress, 32);
+        assertThat(ipAddressMasked.toString(), is("1.2.3.5"));
+    }
+
+    /**
+     * Tests making of a masked address for IPv6.
+     */
+    @Test
+    public void testMakeMaskedAddressIPv6() {
+        IpAddress ipAddress =
+            IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8885");
+        IpAddress ipAddressMasked;
+
+        ipAddressMasked = IpAddress.makeMaskedAddress(ipAddress, 8);
+        assertThat(ipAddressMasked.toString(), is("1100::"));
+
+        ipAddressMasked = IpAddress.makeMaskedAddress(ipAddress, 120);
+        assertThat(ipAddressMasked.toString(),
+                   is("1111:2222:3333:4444:5555:6666:7777:8800"));
+
+        ipAddressMasked = IpAddress.makeMaskedAddress(ipAddress, 0);
+        assertThat(ipAddressMasked.toString(), is("::"));
+
+        ipAddressMasked = IpAddress.makeMaskedAddress(ipAddress, 128);
+        assertThat(ipAddressMasked.toString(),
+                   is("1111:2222:3333:4444:5555:6666:7777:8885"));
+
+        ipAddressMasked = IpAddress.makeMaskedAddress(ipAddress, 64);
+        assertThat(ipAddressMasked.toString(), is("1111:2222:3333:4444::"));
+    }
+
+    /**
+     * Tests making of a masked address for invalid prefix length for IPv4:
+     * negative prefix length.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidMakeNegativeMaskedAddressIPv4() {
+        IpAddress ipAddress = IpAddress.valueOf("1.2.3.5");
+        IpAddress ipAddressMasked;
+
+        ipAddressMasked = IpAddress.makeMaskedAddress(ipAddress, -1);
+    }
+
+    /**
+     * Tests making of a masked address for invalid prefix length for IPv6:
+     * negative prefix length.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidMakeNegativeMaskedAddressIPv6() {
+        IpAddress ipAddress =
+            IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8885");
+        IpAddress ipAddressMasked;
+
+        ipAddressMasked = IpAddress.makeMaskedAddress(ipAddress, -1);
+    }
+
+    /**
+     * Tests making of a masked address for an invalid prefix length for IPv4:
+     * too long prefix length.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidMakeTooLongMaskedAddressIPv4() {
+        IpAddress ipAddress = IpAddress.valueOf("1.2.3.5");
+        IpAddress ipAddressMasked;
+
+        ipAddressMasked = IpAddress.makeMaskedAddress(ipAddress, 33);
+    }
+
+    /**
+     * Tests making of a masked address for an invalid prefix length for IPv6:
+     * too long prefix length.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidMakeTooLongMaskedAddressIPv6() {
+        IpAddress ipAddress =
+            IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8885");
+        IpAddress ipAddressMasked;
+
+        ipAddressMasked = IpAddress.makeMaskedAddress(ipAddress, 129);
+    }
+
+    /**
+     * Tests comparison of {@link IpAddress} for IPv4.
+     */
+    @Test
+    public void testComparisonIPv4() {
+        IpAddress addr1, addr2, addr3, addr4;
+
+        addr1 = IpAddress.valueOf("1.2.3.4");
+        addr2 = IpAddress.valueOf("1.2.3.4");
+        addr3 = IpAddress.valueOf("1.2.3.3");
+        addr4 = IpAddress.valueOf("1.2.3.5");
+        assertTrue(addr1.compareTo(addr2) == 0);
+        assertTrue(addr1.compareTo(addr3) > 0);
+        assertTrue(addr1.compareTo(addr4) < 0);
+
+        addr1 = IpAddress.valueOf("255.2.3.4");
+        addr2 = IpAddress.valueOf("255.2.3.4");
+        addr3 = IpAddress.valueOf("255.2.3.3");
+        addr4 = IpAddress.valueOf("255.2.3.5");
+        assertTrue(addr1.compareTo(addr2) == 0);
+        assertTrue(addr1.compareTo(addr3) > 0);
+        assertTrue(addr1.compareTo(addr4) < 0);
+    }
+
+    /**
+     * Tests comparison of {@link IpAddress} for IPv6.
+     */
+    @Test
+    public void testComparisonIPv6() {
+        IpAddress addr1, addr2, addr3, addr4;
+
+        addr1 = IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8888");
+        addr2 = IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8888");
+        addr3 = IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8887");
+        addr4 = IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8889");
+        assertTrue(addr1.compareTo(addr2) == 0);
+        assertTrue(addr1.compareTo(addr3) > 0);
+        assertTrue(addr1.compareTo(addr4) < 0);
+
+        addr1 = IpAddress.valueOf("ffff:2222:3333:4444:5555:6666:7777:8888");
+        addr2 = IpAddress.valueOf("ffff:2222:3333:4444:5555:6666:7777:8888");
+        addr3 = IpAddress.valueOf("ffff:2222:3333:4444:5555:6666:7777:8887");
+        addr4 = IpAddress.valueOf("ffff:2222:3333:4444:5555:6666:7777:8889");
+        assertTrue(addr1.compareTo(addr2) == 0);
+        assertTrue(addr1.compareTo(addr3) > 0);
+        assertTrue(addr1.compareTo(addr4) < 0);
+
+        addr1 = IpAddress.valueOf("ffff:2222:3333:4444:5555:6666:7777:8888");
+        addr2 = IpAddress.valueOf("ffff:2222:3333:4444:5555:6666:7777:8888");
+        addr3 = IpAddress.valueOf("ffff:2222:3333:4443:5555:6666:7777:8888");
+        addr4 = IpAddress.valueOf("ffff:2222:3333:4445:5555:6666:7777:8888");
+        assertTrue(addr1.compareTo(addr2) == 0);
+        assertTrue(addr1.compareTo(addr3) > 0);
+        assertTrue(addr1.compareTo(addr4) < 0);
+    }
+
+    /**
+     * Tests equality of {@link IpAddress} for IPv4.
+     */
+    @Test
+    public void testEqualityIPv4() {
+        new EqualsTester()
+            .addEqualityGroup(IpAddress.valueOf("1.2.3.4"),
+                              IpAddress.valueOf("1.2.3.4"))
+            .addEqualityGroup(IpAddress.valueOf("1.2.3.5"),
+                              IpAddress.valueOf("1.2.3.5"))
+            .addEqualityGroup(IpAddress.valueOf("0.0.0.0"),
+                              IpAddress.valueOf("0.0.0.0"))
+            .addEqualityGroup(IpAddress.valueOf("255.255.255.255"),
+                              IpAddress.valueOf("255.255.255.255"))
+            .testEquals();
+    }
+
+    /**
+     * Tests equality of {@link IpAddress} for IPv6.
+     */
+    @Test
+    public void testEqualityIPv6() {
+        new EqualsTester()
+            .addEqualityGroup(
+                IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8888"),
+                IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8888"))
+            .addEqualityGroup(
+                IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:888a"),
+                IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:888a"))
+            .addEqualityGroup(
+                IpAddress.valueOf("::"),
+                IpAddress.valueOf("::"))
+            .addEqualityGroup(
+                IpAddress.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"),
+                IpAddress.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))
+            .testEquals();
+    }
+
+    /**
+     * Tests object string representation for IPv4.
+     */
+    @Test
+    public void testToStringIPv4() {
+        IpAddress ipAddress;
+
+        ipAddress = IpAddress.valueOf("1.2.3.4");
+        assertThat(ipAddress.toString(), is("1.2.3.4"));
+
+        ipAddress = IpAddress.valueOf("0.0.0.0");
+        assertThat(ipAddress.toString(), is("0.0.0.0"));
+
+        ipAddress = IpAddress.valueOf("255.255.255.255");
+        assertThat(ipAddress.toString(), is("255.255.255.255"));
+    }
+
+    /**
+     * Tests object string representation for IPv6.
+     */
+    @Test
+    public void testToStringIPv6() {
+        IpAddress ipAddress;
+
+        ipAddress =
+            IpAddress.valueOf("1111:2222:3333:4444:5555:6666:7777:8888");
+        assertThat(ipAddress.toString(),
+                   is("1111:2222:3333:4444:5555:6666:7777:8888"));
+
+        ipAddress = IpAddress.valueOf("1111::8888");
+        assertThat(ipAddress.toString(), is("1111::8888"));
+
+        ipAddress = IpAddress.valueOf("1111::");
+        assertThat(ipAddress.toString(), is("1111::"));
+
+        ipAddress = IpAddress.valueOf("::8888");
+        assertThat(ipAddress.toString(), is("::8888"));
+
+        ipAddress = IpAddress.valueOf("::");
+        assertThat(ipAddress.toString(), is("::"));
+
+        ipAddress =
+            IpAddress.valueOf("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
+        assertThat(ipAddress.toString(),
+                   is("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"));
+    }
+}
diff --git a/utils/misc/src/test/java/org/onlab/packet/IpPrefixTest.java b/utils/misc/src/test/java/org/onlab/packet/IpPrefixTest.java
index ce6a1e3..f5ad88b 100644
--- a/utils/misc/src/test/java/org/onlab/packet/IpPrefixTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/IpPrefixTest.java
@@ -38,9 +38,11 @@
 
     @Test
     public void testEquality() {
-        IpPrefix ip1 = IpPrefix.valueOf(BYTES1, IpPrefix.MAX_INET_MASK_LENGTH);
+        IpPrefix ip1 = IpPrefix.valueOf(IpAddress.Version.INET,
+                                        BYTES1, IpPrefix.MAX_INET_MASK_LENGTH);
         IpPrefix ip2 = IpPrefix.valueOf(INTVAL1, IpPrefix.MAX_INET_MASK_LENGTH);
-        IpPrefix ip3 = IpPrefix.valueOf(BYTES2, IpPrefix.MAX_INET_MASK_LENGTH);
+        IpPrefix ip3 = IpPrefix.valueOf(IpAddress.Version.INET,
+                                        BYTES2, IpPrefix.MAX_INET_MASK_LENGTH);
         IpPrefix ip4 = IpPrefix.valueOf(INTVAL2, IpPrefix.MAX_INET_MASK_LENGTH);
         IpPrefix ip5 = IpPrefix.valueOf(STRVAL);
 
@@ -50,16 +52,19 @@
         .testEquals();
 
         // string conversions
-        IpPrefix ip6 = IpPrefix.valueOf(BYTES1, MASK_LENGTH);
+        IpPrefix ip6 = IpPrefix.valueOf(IpAddress.Version.INET,
+                                        BYTES1, MASK_LENGTH);
         IpPrefix ip7 = IpPrefix.valueOf("10.0.0.10/16");
-        IpPrefix ip8 = IpPrefix.valueOf(new byte [] {0xa, 0x0, 0x0, 0xc}, 16);
+        IpPrefix ip8 = IpPrefix.valueOf(IpAddress.Version.INET,
+                                        new byte [] {0xa, 0x0, 0x0, 0xc}, 16);
         assertEquals("incorrect address conversion", ip6, ip7);
         assertEquals("incorrect address conversion", ip5, ip8);
     }
 
     @Test
     public void basics() {
-        IpPrefix ip1 = IpPrefix.valueOf(BYTES1, MASK_LENGTH);
+        IpPrefix ip1 = IpPrefix.valueOf(IpAddress.Version.INET,
+                                        BYTES1, MASK_LENGTH);
         final byte [] bytes = new byte [] {0xa, 0x0, 0x0, 0x0};
 
         // check fields
@@ -74,7 +79,8 @@
     @Test
     public void netmasks() {
         // masked
-        IpPrefix ip1 = IpPrefix.valueOf(BYTES1, MASK_LENGTH);
+        IpPrefix ip1 = IpPrefix.valueOf(IpAddress.Version.INET,
+                                        BYTES1, MASK_LENGTH);
         IpPrefix ip2 = IpPrefix.valueOf("10.0.0.10/16");
         IpPrefix ip3 = IpPrefix.valueOf("10.0.0.0/16");
         assertEquals("incorrect binary masked address",
@@ -87,9 +93,12 @@
 
     @Test
     public void testContainsIpPrefix() {
-        IpPrefix slash31 = IpPrefix.valueOf(BYTES1, 31);
-        IpPrefix slash32 = IpPrefix.valueOf(BYTES1, 32);
-        IpPrefix differentSlash32 = IpPrefix.valueOf(BYTES2, 32);
+        IpPrefix slash31 = IpPrefix.valueOf(IpAddress.Version.INET,
+                                            BYTES1, 31);
+        IpPrefix slash32 = IpPrefix.valueOf(IpAddress.Version.INET,
+                                            BYTES1, 32);
+        IpPrefix differentSlash32 = IpPrefix.valueOf(IpAddress.Version.INET,
+                                                     BYTES2, 32);
 
         assertTrue(slash31.contains(differentSlash32));
         assertFalse(differentSlash32.contains(slash31));
@@ -109,8 +118,9 @@
 
     @Test
     public void testContainsIpAddress() {
-        IpPrefix slash31 = IpPrefix.valueOf(BYTES1, 31);
-        IpAddress addr32 = IpAddress.valueOf(BYTES1);
+        IpPrefix slash31 = IpPrefix.valueOf(IpAddress.Version.INET,
+                                            BYTES1, 31);
+        IpAddress addr32 = IpAddress.valueOf(IpAddress.Version.INET, BYTES1);
 
         assertTrue(slash31.contains(addr32));
 
diff --git a/utils/netty/pom.xml b/utils/netty/pom.xml
index fd0e8e0..436214f 100644
--- a/utils/netty/pom.xml
+++ b/utils/netty/pom.xml
@@ -73,6 +73,7 @@
         <dependency>
           <groupId>io.netty</groupId>
           <artifactId>netty-transport-native-epoll</artifactId>
+          <version>${netty4.version}</version>
         </dependency>
     </dependencies>
 
diff --git a/web/api/src/main/java/org/onlab/onos/rest/ConfigProvider.java b/web/api/src/main/java/org/onlab/onos/rest/ConfigProvider.java
index 57e247f..e4a0763 100644
--- a/web/api/src/main/java/org/onlab/onos/rest/ConfigProvider.java
+++ b/web/api/src/main/java/org/onlab/onos/rest/ConfigProvider.java
@@ -244,7 +244,7 @@
     }
 
     @Override
-    public void roleChanged(Device device, MastershipRole newRole) {
+    public void roleChanged(DeviceId device, MastershipRole newRole) {
     }
 
     @Override
@@ -257,7 +257,7 @@
     }
 
     @Override
-    public boolean isReachable(Device device) {
+    public boolean isReachable(DeviceId device) {
         return false;
     }
 }
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
index 14795b2..b63e3e0 100644
--- a/web/gui/src/main/java/org/onlab/onos/gui/TopologyResource.java
+++ b/web/gui/src/main/java/org/onlab/onos/gui/TopologyResource.java
@@ -101,7 +101,7 @@
                             new Prop("Vendor", device.manufacturer()),
                             new Prop("H/W Version", device.hwVersion()),
                             new Prop("S/W Version", device.swVersion()),
-                            new Prop("S/W Version", device.serialNumber()),
+                            new Prop("Serial Number", device.serialNumber()),
                             new Separator(),
                             new Prop("Latitude", annot.value("latitude")),
                             new Prop("Longitude", annot.value("longitude")),