Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next
diff --git a/apps/calendar/src/main/java/org/onlab/onos/calendar/BandwidthCalendarResource.java b/apps/calendar/src/main/java/org/onlab/onos/calendar/BandwidthCalendarResource.java
index c534371..cd8cc5c 100644
--- a/apps/calendar/src/main/java/org/onlab/onos/calendar/BandwidthCalendarResource.java
+++ b/apps/calendar/src/main/java/org/onlab/onos/calendar/BandwidthCalendarResource.java
@@ -15,18 +15,29 @@
  */
 package org.onlab.onos.calendar;
 
-import org.onlab.onos.net.ConnectPoint;
-import org.onlab.onos.net.DeviceId;
-import org.onlab.onos.net.intent.IntentService;
-import org.onlab.rest.BaseResource;
+import java.net.URI;
 
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.core.Response;
-import java.net.URI;
+
+import org.onlab.onos.core.ApplicationId;
+import org.onlab.onos.core.CoreService;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.flow.DefaultTrafficSelector;
+import org.onlab.onos.net.flow.TrafficSelector;
+import org.onlab.onos.net.flow.TrafficTreatment;
+import org.onlab.onos.net.intent.Intent;
+import org.onlab.onos.net.intent.IntentService;
+import org.onlab.onos.net.intent.PointToPointIntentWithBandwidthConstraint;
+import org.onlab.onos.net.resource.BandwidthResourceRequest;
+import org.onlab.packet.Ethernet;
+import org.onlab.rest.BaseResource;
 
 import static org.onlab.onos.net.PortNumber.portNumber;
+import static org.onlab.onos.net.flow.DefaultTrafficTreatment.builder;
 
 /**
  * Web resource for triggering calendared intents.
@@ -47,12 +58,33 @@
         ConnectPoint srcPoint = new ConnectPoint(deviceId(src), portNumber(srcPort));
         ConnectPoint dstPoint = new ConnectPoint(deviceId(dst), portNumber(dstPort));
 
+        TrafficSelector selector = buildTrafficSelector();
+        TrafficTreatment treatment = builder().build();
+
+        Intent intent = new PointToPointIntentWithBandwidthConstraint(
+                appId(), selector, treatment,
+                srcPoint, dstPoint, new BandwidthResourceRequest(Double.parseDouble(bandwidth)));
+        service.submit(intent);
+
         return Response.ok("Yo! We got src=" + srcPoint + "; dst=" + dstPoint +
                                    "; bw=" + bandwidth + "; intent service " + service).build();
     }
 
+    private TrafficSelector buildTrafficSelector() {
+        TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder();
+        Short ethType = Ethernet.TYPE_IPV4;
+
+        selectorBuilder.matchEthType(ethType);
+
+        return selectorBuilder.build();
+    }
+
     private DeviceId deviceId(String dpid) {
         return DeviceId.deviceId(URI.create("of:" + dpid));
     }
 
+    protected ApplicationId appId() {
+        return get(CoreService.class).registerApplication("org.onlab.onos.calendar");
+    }
+
 }
diff --git a/apps/optical/src/main/resources/demo-3-roadm-2-ps.json b/apps/optical/src/main/resources/demo-3-roadm-2-ps.json
index 05c4124..125a307 100644
--- a/apps/optical/src/main/resources/demo-3-roadm-2-ps.json
+++ b/apps/optical/src/main/resources/demo-3-roadm-2-ps.json
@@ -76,7 +76,7 @@
                 "nodeName1": "ROUTER1",
                 "nodeName2": "ROADM1",
                 "bandWidth": 100000,
-                "port1": 1,
+                "port1": 2,
                 "port2": 10
             },
             "type": "pktOptLink"
@@ -90,7 +90,7 @@
                 "nodeName1": "ROUTER2",
                 "nodeName2": "ROADM2",
                 "bandWidth": 100000,
-                "port1": 1,
+                "port1": 2,
                 "port2": 11
             },
             "type": "pktOptLink"
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/AddPointToPointIntentWithBandwidthConstraintCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/AddPointToPointIntentWithBandwidthConstraintCommand.java
new file mode 100644
index 0000000..1207f1a
--- /dev/null
+++ b/cli/src/main/java/org/onlab/onos/cli/net/AddPointToPointIntentWithBandwidthConstraintCommand.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.onlab.onos.cli.net;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.flow.TrafficSelector;
+import org.onlab.onos.net.flow.TrafficTreatment;
+import org.onlab.onos.net.intent.Intent;
+import org.onlab.onos.net.intent.IntentService;
+import org.onlab.onos.net.intent.PointToPointIntentWithBandwidthConstraint;
+import org.onlab.onos.net.resource.BandwidthResourceRequest;
+
+import static org.onlab.onos.net.DeviceId.deviceId;
+import static org.onlab.onos.net.PortNumber.portNumber;
+import static org.onlab.onos.net.flow.DefaultTrafficTreatment.builder;
+
+/**
+ * Installs point-to-point connectivity intents.
+ */
+@Command(scope = "onos", name = "add-point-intent-bw",
+         description = "Installs point-to-point connectivity intent with bandwidth constraint")
+public class AddPointToPointIntentWithBandwidthConstraintCommand extends ConnectivityIntentCommand {
+
+    @Argument(index = 0, name = "ingressDevice",
+              description = "Ingress Device/Port Description",
+              required = true, multiValued = false)
+    String ingressDeviceString = null;
+
+    @Argument(index = 1, name = "egressDevice",
+              description = "Egress Device/Port Description",
+              required = true, multiValued = false)
+    String egressDeviceString = null;
+
+    @Argument(index = 2, name = "bandwidth",
+            description = "Bandwidth",
+            required = true, multiValued = false)
+    String bandwidthString = null;
+
+    @Override
+    protected void execute() {
+        IntentService service = get(IntentService.class);
+
+        DeviceId ingressDeviceId = deviceId(getDeviceId(ingressDeviceString));
+        PortNumber ingressPortNumber = portNumber(getPortNumber(ingressDeviceString));
+        ConnectPoint ingress = new ConnectPoint(ingressDeviceId, ingressPortNumber);
+
+        DeviceId egressDeviceId = deviceId(getDeviceId(egressDeviceString));
+        PortNumber egressPortNumber = portNumber(getPortNumber(egressDeviceString));
+        ConnectPoint egress = new ConnectPoint(egressDeviceId, egressPortNumber);
+
+        long bandwidth = Long.parseLong(bandwidthString);
+
+        TrafficSelector selector = buildTrafficSelector();
+        TrafficTreatment treatment = builder().build();
+
+        Intent intent = new PointToPointIntentWithBandwidthConstraint(
+                appId(), selector, treatment,
+                ingress, egress, new BandwidthResourceRequest(bandwidth));
+        service.submit(intent);
+    }
+
+    /**
+     * Extracts the port number portion of the ConnectPoint.
+     *
+     * @param deviceString string representing the device/port
+     * @return port number as a string, empty string if the port is not found
+     */
+    private String getPortNumber(String deviceString) {
+        int slash = deviceString.indexOf('/');
+        if (slash <= 0) {
+            return "";
+        }
+        return deviceString.substring(slash + 1, deviceString.length());
+    }
+
+    /**
+     * Extracts the device ID portion of the ConnectPoint.
+     *
+     * @param deviceString string representing the device/port
+     * @return device ID string
+     */
+    private String getDeviceId(String deviceString) {
+        int slash = deviceString.indexOf('/');
+        if (slash <= 0) {
+            return "";
+        }
+        return deviceString.substring(0, slash);
+    }
+}
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/ConnectivityIntentCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/ConnectivityIntentCommand.java
index b9b071f..b61a646 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/ConnectivityIntentCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/ConnectivityIntentCommand.java
@@ -47,8 +47,8 @@
      */
     protected TrafficSelector buildTrafficSelector() {
         TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder();
-
         Short ethType = Ethernet.TYPE_IPV4;
+
         if (!Strings.isNullOrEmpty(ethTypeString)) {
             EthType ethTypeParameter = EthType.valueOf(ethTypeString);
             ethType = ethTypeParameter.value();
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java
index b585285..1d32aa5 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java
@@ -40,7 +40,7 @@
          description = "Lists all ports or all ports of a device")
 public class DevicePortsListCommand extends DevicesListCommand {
 
-    private static final String FMT = "  port=%s, state=%s%s";
+    private static final String FMT = "  port=%s, state=%s, type=%s, speed=%s%s";
 
     @Option(name = "-e", aliases = "--enabled", description = "Show only enabled ports",
             required = false, multiValued = false)
@@ -110,10 +110,14 @@
                 ports.add(mapper.createObjectNode()
                                   .put("port", port.number().toString())
                                   .put("isEnabled", port.isEnabled())
+                                  .put("type", port.type().toString().toLowerCase())
+                                  .put("portSpeed", port.portSpeed())
                                   .set("annotations", annotations(mapper, port.annotations())));
             }
         }
-        return result.put("device", device.id().toString()).set("ports", ports);
+        result.set("device", json(service, mapper, device));
+        result.set("ports", ports);
+        return result;
     }
 
     // Determines if a port should be included in output.
@@ -130,6 +134,7 @@
         for (Port port : ports) {
             if (isIncluded(port)) {
                 print(FMT, port.number(), port.isEnabled() ? "enabled" : "disabled",
+                      port.type().toString().toLowerCase(), port.portSpeed(),
                       annotations(port.annotations()));
             }
         }
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 6fa500e..bbccb94 100644
--- a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -116,6 +116,17 @@
             </optional-completers>
         </command>
         <command>
+            <action class="org.onlab.onos.cli.net.AddPointToPointIntentWithBandwidthConstraintCommand"/>
+            <completers>
+                <ref component-id="connectPointCompleter"/>
+                <ref component-id="connectPointCompleter"/>
+                <null/>
+            </completers>
+            <optional-completers>
+                <entry key="-t" value-ref="ethTypeCompleter"/>
+            </optional-completers>
+        </command>
+        <command>
             <action class="org.onlab.onos.cli.net.AddOpticalIntentCommand"/>
             <completers>
                 <ref component-id="connectPointCompleter"/>
diff --git a/core/api/src/main/java/org/onlab/onos/cluster/RoleInfo.java b/core/api/src/main/java/org/onlab/onos/cluster/RoleInfo.java
index d3753f5..3e2905e 100644
--- a/core/api/src/main/java/org/onlab/onos/cluster/RoleInfo.java
+++ b/core/api/src/main/java/org/onlab/onos/cluster/RoleInfo.java
@@ -35,6 +35,11 @@
         this.backups = ImmutableList.copyOf(backups);
     }
 
+    public RoleInfo() {
+        this.master = null;
+        this.backups = ImmutableList.of();
+    }
+
     public NodeId master() {
         return master;
     }
@@ -63,7 +68,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(master, backups.hashCode());
+        return Objects.hash(master, backups);
     }
 
     @Override
diff --git a/core/api/src/main/java/org/onlab/onos/net/DefaultPort.java b/core/api/src/main/java/org/onlab/onos/net/DefaultPort.java
index 9d4f8a8..d9cd764 100644
--- a/core/api/src/main/java/org/onlab/onos/net/DefaultPort.java
+++ b/core/api/src/main/java/org/onlab/onos/net/DefaultPort.java
@@ -24,9 +24,14 @@
  */
 public class DefaultPort extends AbstractAnnotated implements Port {
 
+    /** Default port speed in Mbps. */
+    public static final long DEFAULT_SPEED = 1_000;
+
     private final Element element;
     private final PortNumber number;
     private final boolean isEnabled;
+    private final Type type;
+    private final long portSpeed;
 
     /**
      * Creates a network element attributed to the specified provider.
@@ -36,40 +41,35 @@
      * @param isEnabled   indicator whether the port is up and active
      * @param annotations optional key/value annotations
      */
-    public DefaultPort(Element element, PortNumber number,
-                       boolean isEnabled, Annotations... annotations) {
+    public DefaultPort(Element element, PortNumber number, boolean isEnabled,
+                       Annotations... annotations) {
+        this(element, number, isEnabled, Type.COPPER, DEFAULT_SPEED, annotations);
+    }
+
+    /**
+     * Creates a network element attributed to the specified provider.
+     *
+     * @param element     parent network element
+     * @param number      port number
+     * @param isEnabled   indicator whether the port is up and active
+     * @param type        port type
+     * @param portSpeed   port speed in Mbs
+     * @param annotations optional key/value annotations
+     */
+    public DefaultPort(Element element, PortNumber number, boolean isEnabled,
+                       Type type, long portSpeed, Annotations... annotations) {
         super(annotations);
         this.element = element;
         this.number = number;
         this.isEnabled = isEnabled;
+        this.type = type;
+        this.portSpeed = portSpeed;
+
     }
 
     @Override
-    public int hashCode() {
-        return Objects.hash(number, isEnabled);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj instanceof DefaultPort) {
-            final DefaultPort other = (DefaultPort) obj;
-            return Objects.equals(this.element.id(), other.element.id()) &&
-                    Objects.equals(this.number, other.number) &&
-                    Objects.equals(this.isEnabled, other.isEnabled);
-        }
-        return false;
-    }
-
-    @Override
-    public String toString() {
-        return toStringHelper(this)
-                .add("element", element.id())
-                .add("number", number)
-                .add("isEnabled", isEnabled)
-                .toString();
+    public Element element() {
+        return element;
     }
 
     @Override
@@ -83,8 +83,45 @@
     }
 
     @Override
-    public Element element() {
-        return element;
+    public Type type() {
+        return type;
+    }
+
+    @Override
+    public long portSpeed() {
+        return portSpeed;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(number, isEnabled, type, portSpeed);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof DefaultPort) {
+            final DefaultPort other = (DefaultPort) obj;
+            return Objects.equals(this.element.id(), other.element.id()) &&
+                    Objects.equals(this.number, other.number) &&
+                    Objects.equals(this.isEnabled, other.isEnabled) &&
+                    Objects.equals(this.type, other.type) &&
+                    Objects.equals(this.portSpeed, other.portSpeed);
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("element", element.id())
+                .add("number", number)
+                .add("isEnabled", isEnabled)
+                .add("type", type)
+                .add("portSpeed", portSpeed)
+                .toString();
     }
 
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/Port.java b/core/api/src/main/java/org/onlab/onos/net/Port.java
index 7bf6af3..2153593 100644
--- a/core/api/src/main/java/org/onlab/onos/net/Port.java
+++ b/core/api/src/main/java/org/onlab/onos/net/Port.java
@@ -21,6 +21,26 @@
  */
 public interface Port extends Annotated {
 
+    /** Represents coarse port type classification. */
+    public enum Type {
+        /**
+         * Signifies copper-based connectivity.
+         */
+        COPPER,
+
+        /**
+         * Signifies optical fiber-based connectivity.
+         */
+        FIBER
+    }
+
+    /**
+     * Returns the parent network element to which this port belongs.
+     *
+     * @return parent network element
+     */
+    Element element();
+
     /**
      * Returns the port number.
      *
@@ -36,12 +56,18 @@
     boolean isEnabled();
 
     /**
-     * Returns the parent network element to which this port belongs.
+     * Returns the port type.
      *
-     * @return parent network element
+     * @return port type
      */
-    Element element();
+    Type type();
 
-    // set of port attributes
+    /**
+     * Returns the current port speed in Mbps.
+     *
+     * @return current port speed
+     */
+    long portSpeed();
 
+    // TODO: more attributes?
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/device/DefaultPortDescription.java b/core/api/src/main/java/org/onlab/onos/net/device/DefaultPortDescription.java
index 47e3280..9688827 100644
--- a/core/api/src/main/java/org/onlab/onos/net/device/DefaultPortDescription.java
+++ b/core/api/src/main/java/org/onlab/onos/net/device/DefaultPortDescription.java
@@ -15,11 +15,12 @@
  */
 package org.onlab.onos.net.device;
 
+import com.google.common.base.MoreObjects;
 import org.onlab.onos.net.AbstractDescription;
 import org.onlab.onos.net.PortNumber;
 import org.onlab.onos.net.SparseAnnotations;
 
-import com.google.common.base.MoreObjects;
+import static org.onlab.onos.net.Port.Type;
 
 /**
  * Default implementation of immutable port description.
@@ -27,32 +28,62 @@
 public class DefaultPortDescription extends AbstractDescription
         implements PortDescription {
 
+    private static final long DEFAULT_SPEED = 1_000;
+
     private final PortNumber number;
     private final boolean isEnabled;
+    private final Type type;
+    private final long portSpeed;
 
     /**
      * Creates a port description using the supplied information.
      *
-     * @param number       port number
-     * @param isEnabled    port enabled state
-     * @param annotations  optional key/value annotations map
+     * @param number      port number
+     * @param isEnabled   port enabled state
+     * @param annotations optional key/value annotations map
      */
     public DefaultPortDescription(PortNumber number, boolean isEnabled,
-                SparseAnnotations... annotations) {
-        super(annotations);
-        this.number = number;
-        this.isEnabled = isEnabled;
+                                  SparseAnnotations... annotations) {
+        this(number, isEnabled, Type.COPPER, DEFAULT_SPEED, annotations);
     }
 
     /**
      * Creates a port description using the supplied information.
      *
-     * @param base         PortDescription to get basic information from
-     * @param annotations  optional key/value annotations map
+     * @param number      port number
+     * @param isEnabled   port enabled state
+     * @param type        port type
+     * @param portSpeed   port speed in Mbps
+     * @param annotations optional key/value annotations map
+     */
+    public DefaultPortDescription(PortNumber number, boolean isEnabled,
+                                  Type type, long portSpeed,
+                                  SparseAnnotations...annotations) {
+        super(annotations);
+        this.number = number;
+        this.isEnabled = isEnabled;
+        this.type = type;
+        this.portSpeed = portSpeed;
+    }
+
+    // Default constructor for serialization
+    private DefaultPortDescription() {
+        this.number = null;
+        this.isEnabled = false;
+        this.portSpeed = DEFAULT_SPEED;
+        this.type = Type.COPPER;
+    }
+
+    /**
+     * Creates a port description using the supplied information.
+     *
+     * @param base        PortDescription to get basic information from
+     * @param annotations optional key/value annotations map
      */
     public DefaultPortDescription(PortDescription base,
-            SparseAnnotations annotations) {
-        this(base.portNumber(), base.isEnabled(), annotations);
+                                  SparseAnnotations annotations) {
+        this(base.portNumber(), base.isEnabled(), base.type(), base.portSpeed(),
+             annotations);
     }
 
     @Override
@@ -66,17 +97,24 @@
     }
 
     @Override
+    public Type type() {
+        return type;
+    }
+
+    @Override
+    public long portSpeed() {
+        return portSpeed;
+    }
+
+    @Override
     public String toString() {
         return MoreObjects.toStringHelper(getClass())
                 .add("number", number)
                 .add("isEnabled", isEnabled)
+                .add("type", type)
+                .add("portSpeed", portSpeed)
                 .add("annotations", annotations())
                 .toString();
     }
 
-    // default constructor for serialization
-    private DefaultPortDescription() {
-        this.number = null;
-        this.isEnabled = false;
-    }
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/device/PortDescription.java b/core/api/src/main/java/org/onlab/onos/net/device/PortDescription.java
index cdee005..b134d83 100644
--- a/core/api/src/main/java/org/onlab/onos/net/device/PortDescription.java
+++ b/core/api/src/main/java/org/onlab/onos/net/device/PortDescription.java
@@ -18,13 +18,13 @@
 import org.onlab.onos.net.Description;
 import org.onlab.onos.net.PortNumber;
 
+import static org.onlab.onos.net.Port.Type;
+
 /**
  * Information about a port.
  */
 public interface PortDescription extends Description {
 
-    // TODO: possibly relocate this to a common ground so that this can also used by host tracking if required
-
     /**
      * Returns the port number.
      *
@@ -39,4 +39,18 @@
      */
     boolean isEnabled();
 
+    /**
+     * Returns the port type.
+     *
+     * @return port type
+     */
+    Type type();
+
+    /**
+     * Returns the current port speed in Mbps.
+     *
+     * @return current port speed
+     */
+    long portSpeed();
+
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java
index 721a8fd..0789db0 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java
@@ -20,6 +20,7 @@
 import org.onlab.onos.net.Path;
 import org.onlab.onos.net.flow.TrafficSelector;
 import org.onlab.onos.net.flow.TrafficTreatment;
+import org.onlab.onos.net.resource.LinkResourceRequest;
 
 /**
  * Abstraction of explicitly path specified connectivity intent.
@@ -27,6 +28,7 @@
 public class PathIntent extends ConnectivityIntent {
 
     private final Path path;
+    private final LinkResourceRequest[] resourceRequests;
 
     /**
      * Creates a new point-to-point intent with the supplied ingress/egress
@@ -39,10 +41,11 @@
      * @throws NullPointerException {@code path} is null
      */
     public PathIntent(ApplicationId appId, TrafficSelector selector,
-                      TrafficTreatment treatment, Path path) {
+                      TrafficTreatment treatment, Path path, LinkResourceRequest[] resourceRequests) {
         super(id(PathIntent.class, selector, treatment, path), appId,
               resources(path.links()), selector, treatment);
         this.path = path;
+        this.resourceRequests = resourceRequests;
     }
 
     /**
@@ -51,6 +54,7 @@
     protected PathIntent() {
         super();
         this.path = null;
+        this.resourceRequests = new LinkResourceRequest[0];
     }
 
     /**
@@ -67,6 +71,10 @@
         return true;
     }
 
+    public LinkResourceRequest[] resourceRequests() {
+        return resourceRequests;
+    }
+
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(getClass())
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/PointToPointIntentWithBandwidthConstraint.java b/core/api/src/main/java/org/onlab/onos/net/intent/PointToPointIntentWithBandwidthConstraint.java
new file mode 100644
index 0000000..d5c4d57
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/PointToPointIntentWithBandwidthConstraint.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.onlab.onos.net.intent;
+
+import org.onlab.onos.core.ApplicationId;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.flow.TrafficSelector;
+import org.onlab.onos.net.flow.TrafficTreatment;
+import org.onlab.onos.net.resource.BandwidthResourceRequest;
+
+import com.google.common.base.MoreObjects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Abstraction of point-to-point connectivity.
+ */
+public class PointToPointIntentWithBandwidthConstraint extends ConnectivityIntent {
+
+    private final ConnectPoint ingressPoint;
+    private final ConnectPoint egressPoint;
+    private final BandwidthResourceRequest bandwidthResourceRequest;
+
+    /**
+     * Creates a new point-to-point intent with the supplied ingress/egress
+     * ports.
+     *
+     * @param appId        application identifier
+     * @param selector     traffic selector
+     * @param treatment    treatment
+     * @param ingressPoint ingress port
+     * @param egressPoint  egress port
+     * @throws NullPointerException if {@code ingressPoint} or {@code egressPoints} is null.
+     */
+    public PointToPointIntentWithBandwidthConstraint(ApplicationId appId, TrafficSelector selector,
+                                                     TrafficTreatment treatment,
+                                                     ConnectPoint ingressPoint,
+                                                     ConnectPoint egressPoint,
+                                                     BandwidthResourceRequest bandwidthResourceRequest) {
+        super(id(PointToPointIntentWithBandwidthConstraint.class, selector,
+                 treatment, ingressPoint, egressPoint, bandwidthResourceRequest.bandwidth()),
+              appId, null, selector, treatment);
+        this.ingressPoint = checkNotNull(ingressPoint);
+        this.egressPoint = checkNotNull(egressPoint);
+        this.bandwidthResourceRequest = bandwidthResourceRequest;
+    }
+
+    /**
+     * Constructor for serializer.
+     */
+    protected PointToPointIntentWithBandwidthConstraint() {
+        super();
+        this.ingressPoint = null;
+        this.egressPoint = null;
+        bandwidthResourceRequest = new BandwidthResourceRequest(0.0);
+    }
+
+    /**
+     * Returns the port on which the ingress traffic should be connected to
+     * the egress.
+     *
+     * @return ingress port
+     */
+    public ConnectPoint ingressPoint() {
+        return ingressPoint;
+    }
+
+    /**
+     * Returns the port on which the traffic should egress.
+     *
+     * @return egress port
+     */
+    public ConnectPoint egressPoint() {
+        return egressPoint;
+    }
+
+    public BandwidthResourceRequest bandwidthRequest() {
+        return this.bandwidthResourceRequest;
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                .add("id", id())
+                .add("appId", appId())
+                .add("selector", selector())
+                .add("treatment", treatment())
+                .add("ingress", ingressPoint)
+                .add("egress", egressPoint)
+                .add("bandwidth", bandwidthResourceRequest.bandwidth().toString())
+                .toString();
+    }
+
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/resource/LinkResourceStore.java b/core/api/src/main/java/org/onlab/onos/net/resource/LinkResourceStore.java
new file mode 100644
index 0000000..30ea79c
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/resource/LinkResourceStore.java
@@ -0,0 +1,39 @@
+/*
+ * 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.resource;
+
+import java.util.Set;
+
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.intent.IntentId;
+
+
+/**
+ * Manages link resources.
+ */
+public interface LinkResourceStore {
+    Set<ResourceAllocation> getFreeResources(Link link);
+
+    void allocateResources(LinkResourceAllocations allocations);
+
+    void releaseResources(LinkResourceAllocations allocations);
+
+    LinkResourceAllocations getAllocations(IntentId intentId);
+
+    Iterable<LinkResourceAllocations> getAllocations(Link link);
+
+    Iterable<LinkResourceAllocations> getAllocations();
+}
diff --git a/core/api/src/test/java/org/onlab/onos/net/DefaultPortTest.java b/core/api/src/test/java/org/onlab/onos/net/DefaultPortTest.java
index 3d3d3d3..ebc3107 100644
--- a/core/api/src/test/java/org/onlab/onos/net/DefaultPortTest.java
+++ b/core/api/src/test/java/org/onlab/onos/net/DefaultPortTest.java
@@ -23,6 +23,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.onlab.onos.net.Device.Type.SWITCH;
 import static org.onlab.onos.net.DeviceId.deviceId;
+import static org.onlab.onos.net.Port.Type.COPPER;
+import static org.onlab.onos.net.Port.Type.FIBER;
 import static org.onlab.onos.net.PortNumber.portNumber;
 
 /**
@@ -35,15 +37,16 @@
     private static final DeviceId DID2 = deviceId("of:bar");
     private static final PortNumber P1 = portNumber(1);
     private static final PortNumber P2 = portNumber(2);
+    private static final long SP1 = 1_000_000;
 
     @Test
     public void testEquality() {
         Device device = new DefaultDevice(PID, DID1, SWITCH, "m", "h", "s", "n",
                                           new ChassisId());
-        Port p1 = new DefaultPort(device, portNumber(1), true);
-        Port p2 = new DefaultPort(device, portNumber(1), true);
-        Port p3 = new DefaultPort(device, portNumber(2), true);
-        Port p4 = new DefaultPort(device, portNumber(2), true);
+        Port p1 = new DefaultPort(device, portNumber(1), true, COPPER, SP1);
+        Port p2 = new DefaultPort(device, portNumber(1), true, COPPER, SP1);
+        Port p3 = new DefaultPort(device, portNumber(2), true, FIBER, SP1);
+        Port p4 = new DefaultPort(device, portNumber(2), true, FIBER, SP1);
         Port p5 = new DefaultPort(device, portNumber(1), false);
 
         new EqualsTester().addEqualityGroup(p1, p2)
@@ -56,10 +59,12 @@
     public void basics() {
         Device device = new DefaultDevice(PID, DID1, SWITCH, "m", "h", "s", "n",
                                           new ChassisId());
-        Port port = new DefaultPort(device, portNumber(1), true);
+        Port port = new DefaultPort(device, portNumber(1), true, FIBER, SP1);
         assertEquals("incorrect element", device, port.element());
         assertEquals("incorrect number", portNumber(1), port.number());
         assertEquals("incorrect state", true, port.isEnabled());
+        assertEquals("incorrect speed", SP1, port.portSpeed());
+        assertEquals("incorrect type", FIBER, port.type());
     }
 
 }
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/PathIntentTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/PathIntentTest.java
index 21efe49..02ca0f5 100644
--- a/core/api/src/test/java/org/onlab/onos/net/intent/PathIntentTest.java
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/PathIntentTest.java
@@ -15,11 +15,12 @@
  */
 package org.onlab.onos.net.intent;
 
-import static org.junit.Assert.assertEquals;
-
 import org.junit.Test;
 import org.onlab.onos.net.NetTestTools;
 import org.onlab.onos.net.Path;
+import org.onlab.onos.net.resource.LinkResourceRequest;
+
+import static org.junit.Assert.assertEquals;
 
 public class PathIntentTest extends ConnectivityIntentTest {
     // 111:11 --> 222:22
@@ -39,11 +40,11 @@
 
     @Override
     protected PathIntent createOne() {
-        return new PathIntent(APPID, MATCH, NOP, PATH1);
+        return new PathIntent(APPID, MATCH, NOP, PATH1, new LinkResourceRequest[0]);
     }
 
     @Override
     protected PathIntent createAnother() {
-        return new PathIntent(APPID, MATCH, NOP, PATH2);
+        return new PathIntent(APPID, MATCH, NOP, PATH2, new LinkResourceRequest[0]);
     }
 }
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 e163dc5..d1dab70 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
@@ -67,8 +67,8 @@
 @Component(immediate = true)
 @Service
 public class DeviceManager
-    extends AbstractProviderRegistry<DeviceProvider, DeviceProviderService>
-    implements DeviceService, DeviceAdminService, DeviceProviderRegistry {
+        extends AbstractProviderRegistry<DeviceProvider, DeviceProviderService>
+        implements DeviceService, DeviceAdminService, DeviceProviderRegistry {
 
     private static final String DEVICE_ID_NULL = "Device ID cannot be null";
     private static final String PORT_NUMBER_NULL = "Port number cannot be null";
@@ -227,8 +227,8 @@
 
     // Personalized device provider service issued to the supplied provider.
     private class InternalDeviceProviderService
-    extends AbstractProviderService<DeviceProvider>
-    implements DeviceProviderService {
+            extends AbstractProviderService<DeviceProvider>
+            implements DeviceProviderService {
 
         InternalDeviceProviderService(DeviceProvider provider) {
             super(provider);
@@ -236,7 +236,7 @@
 
         @Override
         public void deviceConnected(DeviceId deviceId,
-                DeviceDescription deviceDescription) {
+                                    DeviceDescription deviceDescription) {
             checkNotNull(deviceId, DEVICE_ID_NULL);
             checkNotNull(deviceDescription, DEVICE_DESCRIPTION_NULL);
             checkValidity();
@@ -267,7 +267,7 @@
             deviceClockProviderService.setMastershipTerm(deviceId, term);
 
             DeviceEvent event = store.createOrUpdateDevice(provider().id(),
-                    deviceId, deviceDescription);
+                                                           deviceId, deviceDescription);
 
             // If there was a change of any kind, tell the provider
             // that this instance is the master.
@@ -337,14 +337,14 @@
 
         @Override
         public void updatePorts(DeviceId deviceId,
-                List<PortDescription> portDescriptions) {
+                                List<PortDescription> portDescriptions) {
             checkNotNull(deviceId, DEVICE_ID_NULL);
             checkNotNull(portDescriptions,
-                    "Port descriptions list cannot be null");
+                         "Port descriptions list cannot be null");
             checkValidity();
 
             List<DeviceEvent> events = store.updatePorts(this.provider().id(),
-                    deviceId, portDescriptions);
+                                                         deviceId, portDescriptions);
             for (DeviceEvent event : events) {
                 post(event);
             }
@@ -352,13 +352,13 @@
 
         @Override
         public void portStatusChanged(DeviceId deviceId,
-                PortDescription portDescription) {
+                                      PortDescription portDescription) {
             checkNotNull(deviceId, DEVICE_ID_NULL);
             checkNotNull(portDescription, PORT_DESCRIPTION_NULL);
             checkValidity();
 
             final DeviceEvent event = store.updatePortStatus(this.provider().id(),
-                        deviceId, portDescription);
+                                                             deviceId, portDescription);
             if (event != null) {
                 log.info("Device {} port {} status changed", deviceId, event
                         .port().number());
@@ -370,7 +370,7 @@
         public void unableToAssertRole(DeviceId deviceId, MastershipRole role) {
             // FIXME: implement response to this notification
             log.warn("Failed to assert role [{}] onto Device {}", role,
-                    deviceId);
+                     deviceId);
             if (role == MastershipRole.MASTER) {
                 mastershipService.relinquishMastership(deviceId);
                 // TODO: Shouldn't we be triggering event?
@@ -393,7 +393,7 @@
         // random cache size
         private final int cacheSize = 5;
         // temporarily stores term number + events to check for duplicates. A hack.
-        private HashMultimap<Integer, RoleInfo>  eventCache =
+        private HashMultimap<Integer, RoleInfo> eventCache =
                 HashMultimap.create();
 
         @Override
@@ -407,14 +407,14 @@
                 // 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 (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);
+                                     + "Term Service suggests {} for {}", term, did);
                     // FIXME: Is it possible to let go of MASTER role
                     //        but remain on STANDBY list?
                     mastershipService.relinquishMastership(did);
@@ -435,11 +435,12 @@
                         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()));
+                    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);
@@ -476,7 +477,7 @@
 
     // Store delegate to re-post events emitted from the store.
     private class InternalStoreDelegate
-    implements DeviceStoreDelegate {
+            implements DeviceStoreDelegate {
         @Override
         public void notify(DeviceEvent event) {
             post(event);
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/HostToHostIntentCompiler.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/HostToHostIntentCompiler.java
index e045cec..37cf84b 100644
--- a/core/net/src/main/java/org/onlab/onos/net/intent/impl/HostToHostIntentCompiler.java
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/HostToHostIntentCompiler.java
@@ -15,6 +15,10 @@
  */
 package org.onlab.onos.net.intent.impl;
 
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -32,13 +36,10 @@
 import org.onlab.onos.net.intent.IntentExtensionService;
 import org.onlab.onos.net.intent.PathIntent;
 import org.onlab.onos.net.topology.LinkWeight;
+import org.onlab.onos.net.resource.LinkResourceRequest;
 import org.onlab.onos.net.topology.PathService;
 import org.onlab.onos.net.topology.TopologyEdge;
 
-import java.util.Arrays;
-import java.util.List;
-import java.util.Set;
-
 import static org.onlab.onos.net.flow.DefaultTrafficSelector.builder;
 
 /**
@@ -85,7 +86,7 @@
         TrafficSelector selector = builder(intent.selector())
                 .matchEthSrc(src.mac()).matchEthDst(dst.mac()).build();
         return new PathIntent(intent.appId(), selector, intent.treatment(),
-                              path);
+                              path, new LinkResourceRequest[0]);
     }
 
     private Path getPath(HostId one, HostId two) {
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathIntentInstaller.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathIntentInstaller.java
index d0666cc..c3b27bf 100644
--- a/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathIntentInstaller.java
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PathIntentInstaller.java
@@ -15,9 +15,6 @@
  */
 package org.onlab.onos.net.intent.impl;
 
-import static org.onlab.onos.net.flow.DefaultTrafficTreatment.builder;
-import static org.slf4j.LoggerFactory.getLogger;
-
 import java.util.Iterator;
 import java.util.List;
 
@@ -41,10 +38,15 @@
 import org.onlab.onos.net.intent.IntentExtensionService;
 import org.onlab.onos.net.intent.IntentInstaller;
 import org.onlab.onos.net.intent.PathIntent;
+import org.onlab.onos.net.resource.LinkResourceAllocations;
+import org.onlab.onos.net.resource.LinkResourceService;
 import org.slf4j.Logger;
 
 import com.google.common.collect.Lists;
 
+import static org.onlab.onos.net.flow.DefaultTrafficTreatment.builder;
+import static org.slf4j.LoggerFactory.getLogger;
+
 /**
  * Installer for {@link PathIntent packet path connectivity intents}.
  */
@@ -59,6 +61,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected CoreService coreService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected LinkResourceService resourceService;
+
     private ApplicationId appId;
 
     @Activate
@@ -74,6 +79,14 @@
 
     @Override
     public List<FlowRuleBatchOperation> install(PathIntent intent) {
+        if (intent.resourceRequests().length > 0) {
+            LinkResourceAllocations allocations = allocateBandwidth(intent);
+            if (allocations == null) {
+                log.debug("Insufficient bandwidth available to install path intent {}", intent);
+                return null;
+            }
+        }
+
         TrafficSelector.Builder builder =
                 DefaultTrafficSelector.builder(intent.selector());
         Iterator<Link> links = intent.path().links().iterator();
@@ -117,6 +130,10 @@
         return Lists.newArrayList(new FlowRuleBatchOperation(rules));
     }
 
+    private LinkResourceAllocations allocateBandwidth(PathIntent intent) {
+        return resourceService.requestResources(intent.resourceRequests()[0]);
+    }
+
     // TODO refactor below this line... ----------------------------
 
     /**
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/PointToPointIntentCompiler.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PointToPointIntentCompiler.java
index 5c79d5c..893b7ea 100644
--- a/core/net/src/main/java/org/onlab/onos/net/intent/impl/PointToPointIntentCompiler.java
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PointToPointIntentCompiler.java
@@ -15,6 +15,10 @@
  */
 package org.onlab.onos.net.intent.impl;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -31,15 +35,12 @@
 import org.onlab.onos.net.intent.PathIntent;
 import org.onlab.onos.net.intent.PointToPointIntent;
 import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.onos.net.resource.LinkResourceRequest;
 import org.onlab.onos.net.topology.LinkWeight;
 import org.onlab.onos.net.topology.Topology;
 import org.onlab.onos.net.topology.TopologyEdge;
 import org.onlab.onos.net.topology.TopologyService;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
 import static java.util.Arrays.asList;
 
 /**
@@ -76,7 +77,7 @@
         links.add(DefaultEdgeLink.createEdgeLink(intent.egressPoint(), false));
 
         return asList(createPathIntent(new DefaultPath(PID, links, path.cost() + 2,
-                                                       path.annotations()), intent));
+                path.annotations()), intent));
     }
 
     /**
@@ -89,7 +90,8 @@
     private Intent createPathIntent(Path path,
                                     PointToPointIntent intent) {
         return new PathIntent(intent.appId(),
-                              intent.selector(), intent.treatment(), path);
+                              intent.selector(), intent.treatment(), path,
+                              new LinkResourceRequest[0]);
     }
 
     /**
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/PointToPointIntentWithBandwidthConstraintCompiler.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PointToPointIntentWithBandwidthConstraintCompiler.java
new file mode 100644
index 0000000..a192a9f
--- /dev/null
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PointToPointIntentWithBandwidthConstraintCompiler.java
@@ -0,0 +1,146 @@
+package org.onlab.onos.net.intent.impl;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DefaultEdgeLink;
+import org.onlab.onos.net.DefaultPath;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.Path;
+import org.onlab.onos.net.intent.Intent;
+import org.onlab.onos.net.intent.IntentCompiler;
+import org.onlab.onos.net.intent.IntentExtensionService;
+import org.onlab.onos.net.intent.PathIntent;
+import org.onlab.onos.net.intent.PointToPointIntent;
+import org.onlab.onos.net.intent.PointToPointIntentWithBandwidthConstraint;
+import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.onos.net.resource.BandwidthResourceRequest;
+import org.onlab.onos.net.resource.DefaultLinkResourceRequest;
+import org.onlab.onos.net.resource.LinkResourceRequest;
+import org.onlab.onos.net.resource.LinkResourceService;
+import org.onlab.onos.net.resource.ResourceRequest;
+import org.onlab.onos.net.resource.ResourceType;
+import org.onlab.onos.net.topology.LinkWeight;
+import org.onlab.onos.net.topology.Topology;
+import org.onlab.onos.net.topology.TopologyEdge;
+import org.onlab.onos.net.topology.TopologyService;
+
+/**
+ * A intent compiler for {@link org.onlab.onos.net.intent.HostToHostIntent}.
+ */
+@Component(immediate = true)
+public class PointToPointIntentWithBandwidthConstraintCompiler
+        implements IntentCompiler<PointToPointIntentWithBandwidthConstraint> {
+
+    private static final ProviderId PID = new ProviderId("core", "org.onlab.onos.core", true);
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected IntentExtensionService intentManager;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected TopologyService topologyService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected LinkResourceService resourceService;
+
+    @Activate
+    public void activate() {
+        intentManager.registerCompiler(PointToPointIntentWithBandwidthConstraint.class, this);
+    }
+
+    @Deactivate
+    public void deactivate() {
+        intentManager.unregisterCompiler(PointToPointIntent.class);
+    }
+
+    @Override
+    public List<Intent> compile(PointToPointIntentWithBandwidthConstraint intent) {
+        Path path = getPath(intent.ingressPoint(), intent.egressPoint(), intent.bandwidthRequest());
+
+        List<Link> links = new ArrayList<>();
+        links.add(DefaultEdgeLink.createEdgeLink(intent.ingressPoint(), true));
+        links.addAll(path.links());
+        links.add(DefaultEdgeLink.createEdgeLink(intent.egressPoint(), false));
+
+        return Arrays.asList(createPathIntent(new DefaultPath(PID, links, path.cost() + 2,
+                                                              path.annotations()),
+                                              intent));
+    }
+
+    /**
+     * Creates a path intent from the specified path and original
+     * connectivity intent.
+     *
+     * @param path   path to create an intent for
+     * @param intent original intent
+     */
+    private Intent createPathIntent(Path path,
+                                    PointToPointIntentWithBandwidthConstraint intent) {
+        LinkResourceRequest.Builder request = DefaultLinkResourceRequest.builder(intent.id(),
+                path.links())
+                // TODO - this seems awkward, maybe allow directly attaching a BandwidthRequest
+                .addBandwidthRequest(intent.bandwidthRequest().bandwidth().toDouble());
+        LinkResourceRequest bandwidthRequest  = request.build();
+        LinkResourceRequest[] bandwidthRequests = {bandwidthRequest};
+        return new PathIntent(intent.appId(),
+                              intent.selector(), intent.treatment(), path,
+                              bandwidthRequests);
+    }
+
+    /**
+     * Computes a path between two ConnectPoints.
+     *
+     * @param one start of the path
+     * @param two end of the path
+     * @return Path between the two
+     * @throws org.onlab.onos.net.intent.impl.PathNotFoundException if a path cannot be found
+     */
+    private Path getPath(ConnectPoint one, ConnectPoint two, final BandwidthResourceRequest bandwidthRequest) {
+        Topology topology = topologyService.currentTopology();
+        LinkWeight weight = new LinkWeight() {
+            @Override
+            public double weight(TopologyEdge edge) {
+                if (bandwidthRequest != null) {
+                    double allocatedBandwidth = 0.0;
+                    Iterable<ResourceRequest> availableResources = resourceService.getAvailableResources(edge.link());
+                    for (ResourceRequest availableResource : availableResources) {
+                        if (availableResource.type() == ResourceType.BANDWIDTH) {
+                            BandwidthResourceRequest bandwidthRequest = (BandwidthResourceRequest) availableResource;
+                            allocatedBandwidth += bandwidthRequest.bandwidth().toDouble();
+                        }
+                    }
+
+                    // TODO this needs to be discovered from switch/ports somehow
+                    double maxBandwidth = 1000;
+
+                    double availableBandwidth = maxBandwidth - allocatedBandwidth;
+                    if (availableBandwidth >= bandwidthRequest.bandwidth().toDouble()) {
+                        return 1;
+                    } else {
+                        return -1;
+                    }
+                } else {
+                    return 1;
+                }
+            }
+        };
+
+        Set<Path> paths = topologyService.getPaths(topology,
+                one.deviceId(),
+                two.deviceId(),
+                weight);
+
+        if (paths.isEmpty()) {
+            throw new PathNotFoundException("No packet path from " + one + " to " + two);
+        }
+        // TODO: let's be more intelligent about this eventually
+        return paths.iterator().next();
+    }
+}
diff --git a/core/net/src/main/java/org/onlab/onos/net/resource/impl/LinkResourceManager.java b/core/net/src/main/java/org/onlab/onos/net/resource/impl/LinkResourceManager.java
index bdd0761..7f01d37 100644
--- a/core/net/src/main/java/org/onlab/onos/net/resource/impl/LinkResourceManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/resource/impl/LinkResourceManager.java
@@ -15,9 +15,13 @@
  */
 package org.onlab.onos.net.resource.impl;
 
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
 import static org.slf4j.LoggerFactory.getLogger;
 
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
@@ -25,6 +29,8 @@
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
 import org.onlab.onos.net.Link;
 import org.onlab.onos.net.intent.IntentId;
@@ -32,15 +38,16 @@
 import org.onlab.onos.net.resource.BandwidthResourceRequest;
 import org.onlab.onos.net.resource.Lambda;
 import org.onlab.onos.net.resource.LambdaResourceAllocation;
+import org.onlab.onos.net.resource.LambdaResourceRequest;
 import org.onlab.onos.net.resource.LinkResourceAllocations;
 import org.onlab.onos.net.resource.LinkResourceRequest;
 import org.onlab.onos.net.resource.LinkResourceService;
+import org.onlab.onos.net.resource.LinkResourceStore;
 import org.onlab.onos.net.resource.ResourceAllocation;
 import org.onlab.onos.net.resource.ResourceRequest;
+import org.onlab.onos.net.resource.ResourceType;
 import org.slf4j.Logger;
 
-import com.google.common.collect.Sets;
-
 /**
  * Provides basic implementation of link resources allocation.
  */
@@ -50,6 +57,9 @@
 
     private final Logger log = getLogger(getClass());
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private LinkResourceStore store;
+
     @Activate
     public void activate() {
         log.info("Started");
@@ -60,27 +70,65 @@
         log.info("Stopped");
     }
 
+    /**
+     * Returns available lambdas on specified link.
+     *
+     * @param link the link
+     * @return available lambdas on specified link
+     */
+    private Set<Lambda> getAvailableLambdas(Link link) {
+        checkNotNull(link);
+        Set<ResourceAllocation> resAllocs = store.getFreeResources(link);
+        if (resAllocs == null) {
+            return Collections.emptySet();
+        }
+        Set<Lambda> lambdas = new HashSet<>();
+        for (ResourceAllocation res : resAllocs) {
+            if (res.type() == ResourceType.LAMBDA) {
+                lambdas.add(((LambdaResourceAllocation) res).lambda());
+            }
+        }
+        return lambdas;
+    }
+
+    /**
+     * Returns available lambdas on specified links.
+     *
+     * @param links the links
+     * @return available lambdas on specified links
+     */
     private Iterable<Lambda> getAvailableLambdas(Iterable<Link> links) {
-        return Sets.newHashSet(Lambda.valueOf(7));
+        checkNotNull(links);
+        Iterator<Link> i = links.iterator();
+        checkArgument(i.hasNext());
+        Set<Lambda> lambdas = new HashSet<>(getAvailableLambdas(i.next()));
+        while (i.hasNext()) {
+            lambdas.retainAll(getAvailableLambdas(i.next()));
+        }
+        return lambdas;
     }
 
     @Override
     public LinkResourceAllocations requestResources(LinkResourceRequest req) {
-        // TODO implement it using a resource data store.
+        // TODO Concatenate multiple bandwidth requests.
+        // TODO Support multiple lambda resource requests.
+        // TODO Throw appropriate exception.
 
-        ResourceAllocation alloc = null;
+        Set<ResourceAllocation> allocs = new HashSet<>();
         for (ResourceRequest r : req.resources()) {
             switch (r.type()) {
             case BANDWIDTH:
-                log.info("requestResources() always returns requested bandwidth");
                 BandwidthResourceRequest br = (BandwidthResourceRequest) r;
-                alloc = new BandwidthResourceAllocation(br.bandwidth());
+                allocs.add(new BandwidthResourceAllocation(br.bandwidth()));
                 break;
             case LAMBDA:
-                log.info("requestResources() always returns lambda 7");
-                Iterator<Lambda> lambdaIterator = getAvailableLambdas(req.links()).iterator();
+                Iterator<Lambda> lambdaIterator =
+                        getAvailableLambdas(req.links()).iterator();
                 if (lambdaIterator.hasNext()) {
-                    alloc = new LambdaResourceAllocation(lambdaIterator.next());
+                    allocs.add(new LambdaResourceAllocation(lambdaIterator.next()));
+                } else {
+                    log.info("Failed to allocate lambda resource.");
+                    return null;
                 }
                 break;
             default:
@@ -90,50 +138,66 @@
 
         Map<Link, Set<ResourceAllocation>> allocations = new HashMap<>();
         for (Link link : req.links()) {
-            allocations.put(link, Sets.newHashSet(alloc));
+            allocations.put(link, allocs);
         }
-        return new DefaultLinkResourceAllocations(req, allocations);
+        LinkResourceAllocations result =
+                new DefaultLinkResourceAllocations(req, allocations);
+        store.allocateResources(result);
+        return result;
+
     }
 
     @Override
     public void releaseResources(LinkResourceAllocations allocations) {
-        // TODO Auto-generated method stub
-
+        store.releaseResources(allocations);
     }
 
     @Override
     public LinkResourceAllocations updateResources(LinkResourceRequest req,
-                                                   LinkResourceAllocations oldAllocations) {
+            LinkResourceAllocations oldAllocations) {
+        // TODO
         return null;
     }
 
     @Override
     public Iterable<LinkResourceAllocations> getAllocations() {
-        // TODO Auto-generated method stub
-        return null;
+        return store.getAllocations();
     }
 
     @Override
     public Iterable<LinkResourceAllocations> getAllocations(Link link) {
-        // TODO Auto-generated method stub
-        return null;
+        return store.getAllocations(link);
     }
 
     @Override
     public LinkResourceAllocations getAllocations(IntentId intentId) {
-        // TODO Auto-generated method stub
-        return null;
+        return store.getAllocations(intentId);
     }
 
     @Override
     public Iterable<ResourceRequest> getAvailableResources(Link link) {
-        // TODO Auto-generated method stub
-        return null;
+        Set<ResourceAllocation> freeRes = store.getFreeResources(link);
+        Set<ResourceRequest> result = new HashSet<>();
+        for (ResourceAllocation alloc : freeRes) {
+            switch (alloc.type()) {
+            case BANDWIDTH:
+                result.add(new BandwidthResourceRequest(
+                        ((BandwidthResourceAllocation) alloc).bandwidth()));
+                break;
+            case LAMBDA:
+                result.add(new LambdaResourceRequest());
+                break;
+            default:
+                break;
+            }
+        }
+        return result;
     }
 
     @Override
     public ResourceRequest getAvailableResources(Link link,
-                                                 LinkResourceAllocations allocations) {
+            LinkResourceAllocations allocations) {
+        // TODO
         return null;
     }
 
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 f3ae7a4..5449277 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
@@ -543,8 +543,9 @@
                                    Port newPort,
                                    Map<PortNumber, Port> ports) {
         if (oldPort.isEnabled() != newPort.isEnabled() ||
-            !AnnotationsUtil.isEqual(oldPort.annotations(), newPort.annotations())) {
-
+                oldPort.type() != newPort.type() ||
+                oldPort.portSpeed() != newPort.portSpeed() ||
+                !AnnotationsUtil.isEqual(oldPort.annotations(), newPort.annotations())) {
             ports.put(oldPort.number(), newPort);
             return new DeviceEvent(PORT_UPDATED, device, newPort);
         }
@@ -867,7 +868,10 @@
             }
         }
 
-        return new DefaultPort(device, number, isEnabled, annotations);
+        return portDesc == null ?
+                new DefaultPort(device, number, false, annotations) :
+                new DefaultPort(device, number, isEnabled, portDesc.value().type(),
+                                portDesc.value().portSpeed(), annotations);
     }
 
     /**
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 07120fd..0e0b240 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,7 +16,9 @@
 package org.onlab.onos.store.mastership.impl;
 
 import static org.onlab.onos.mastership.MastershipEvent.Type.MASTER_CHANGED;
+import static org.apache.commons.lang3.concurrent.ConcurrentUtils.putIfAbsent;
 
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
@@ -41,7 +43,6 @@
 import org.onlab.onos.store.serializers.KryoSerializer;
 import org.onlab.util.KryoNamespace;
 
-import com.google.common.collect.ImmutableSet;
 import com.hazelcast.core.EntryEvent;
 import com.hazelcast.core.EntryListener;
 import com.hazelcast.core.IAtomicLong;
@@ -105,46 +106,50 @@
 
     @Override
     public MastershipRole getRole(NodeId nodeId, DeviceId deviceId) {
-        final RoleValue roleInfo = getRoleValue(deviceId);
-        if (roleInfo.contains(MASTER, nodeId)) {
-            return MASTER;
-        }
-        if (roleInfo.contains(STANDBY, nodeId)) {
-            return STANDBY;
+        final RoleValue roleInfo = roleMap.get(deviceId);
+        if (roleInfo != null) {
+            return roleInfo.getRole(nodeId);
         }
         return NONE;
     }
 
     @Override
-    public MastershipEvent setMaster(NodeId nodeId, DeviceId deviceId) {
+    public MastershipEvent setMaster(NodeId newMaster, DeviceId deviceId) {
 
-        MastershipRole role = getRole(nodeId, deviceId);
         roleMap.lock(deviceId);
         try {
-            RoleValue rv = getRoleValue(deviceId);
-            switch (role) {
+            final RoleValue rv = getRoleValue(deviceId);
+            final MastershipRole currentRole = rv.getRole(newMaster);
+            switch (currentRole) {
                 case MASTER:
                     //reinforce mastership
-                    rv.reassign(nodeId, STANDBY, NONE);
-                    roleMap.put(deviceId, rv);
+                    // RoleInfo integrity check
+                    boolean modified = rv.reassign(newMaster, STANDBY, NONE);
+                    if (modified) {
+                        roleMap.put(deviceId, rv);
+                        // should never reach here.
+                        log.warn("{} was in both MASTER and STANDBY for {}", newMaster, deviceId);
+                        // trigger BACKUPS_CHANGED?
+                    }
                     return null;
                 case STANDBY:
                 case NONE:
-                    NodeId current = rv.get(MASTER);
-                    if (current != null) {
-                        //backup and replace current master
-                        rv.reassign(current, NONE, STANDBY);
-                        rv.replace(current, nodeId, MASTER);
+                    final NodeId currentMaster = rv.get(MASTER);
+                    if (currentMaster != null) {
+                        // place current master in STANDBY
+                        rv.reassign(currentMaster, NONE, STANDBY);
+                        rv.replace(currentMaster, newMaster, MASTER);
                     } else {
                         //no master before so just add.
-                        rv.add(MASTER, nodeId);
+                        rv.add(MASTER, newMaster);
                     }
-                    rv.reassign(nodeId, STANDBY, NONE);
-                    roleMap.put(deviceId, rv);
+                    // remove newMaster from STANDBY
+                    rv.reassign(newMaster, STANDBY, NONE);
                     updateTerm(deviceId);
+                    roleMap.put(deviceId, rv);
                     return new MastershipEvent(MASTER_CHANGED, deviceId, rv.roleInfo());
                 default:
-                    log.warn("unknown Mastership Role {}", role);
+                    log.warn("unknown Mastership Role {}", currentRole);
                     return null;
             }
         } finally {
@@ -160,66 +165,83 @@
 
     @Override
     public RoleInfo getNodes(DeviceId deviceId) {
-        roleMap.lock(deviceId);
-        try {
-            RoleValue rv = getRoleValue(deviceId);
+        RoleValue rv = roleMap.get(deviceId);
+        if (rv != null) {
             return rv.roleInfo();
-        } finally {
-            roleMap.unlock(deviceId);
+        } else {
+            return new RoleInfo();
         }
     }
 
     @Override
     public Set<DeviceId> getDevices(NodeId nodeId) {
-        ImmutableSet.Builder<DeviceId> builder = ImmutableSet.builder();
+        Set<DeviceId> devices = new HashSet<>();
 
         for (Map.Entry<DeviceId, RoleValue> el : roleMap.entrySet()) {
             if (nodeId.equals(el.getValue().get(MASTER))) {
-                builder.add(el.getKey());
+                devices.add(el.getKey());
             }
         }
 
-        return builder.build();
+        return devices;
     }
 
     @Override
     public MastershipRole requestRole(DeviceId deviceId) {
-        NodeId local = clusterService.getLocalNode().id();
 
+        // if no master => become master
+        // if there already exists a master:
+        //     if I was the master return MASTER
+        //     else put myself in STANDBY and return STANDBY
+
+        final NodeId local = clusterService.getLocalNode().id();
+        boolean modified = false;
         roleMap.lock(deviceId);
         try {
-            RoleValue rv = getRoleValue(deviceId);
-            MastershipRole role = getRole(local, deviceId);
-            switch (role) {
+            final RoleValue rv = getRoleValue(deviceId);
+            if (rv.get(MASTER) == null) {
+                // there's no master become one
+                // move out from STANDBY
+                rv.reassign(local, STANDBY, NONE);
+                rv.add(MASTER, local);
+
+                updateTerm(deviceId);
+                roleMap.put(deviceId, rv);
+                return MASTER;
+            }
+            final MastershipRole currentRole = rv.getRole(local);
+            switch (currentRole) {
                 case MASTER:
-                    rv.reassign(local, STANDBY, NONE);
-                    terms.putIfAbsent(deviceId, INIT);
-                    roleMap.put(deviceId, rv);
-                    break;
+                    // RoleInfo integrity check
+                    modified = rv.reassign(local, STANDBY, NONE);
+                    if (modified) {
+                        log.warn("{} was in both MASTER and STANDBY for {}", local, deviceId);
+                        // should never reach here,
+                        // but heal if we happened to be there
+                        roleMap.put(deviceId, rv);
+                        // trigger BACKUPS_CHANGED?
+                    }
+                    return currentRole;
                 case STANDBY:
+                    // RoleInfo integrity check
+                    modified = rv.reassign(local, NONE, STANDBY);
+                    if (modified) {
+                        log.warn("{} was in both NONE and STANDBY for {}", local, deviceId);
+                        // should never reach here,
+                        // but heal if we happened to be there
+                        roleMap.put(deviceId, rv);
+                        // trigger BACKUPS_CHANGED?
+                    }
+                    return currentRole;
+                case NONE:
                     rv.reassign(local, NONE, STANDBY);
                     roleMap.put(deviceId, rv);
-                    terms.putIfAbsent(deviceId, INIT);
-                    break;
-                case NONE:
-                    //either we're the first standby, or first to device.
-                    //for latter, claim mastership.
-                    if (rv.get(MASTER) == null) {
-                        rv.add(MASTER, local);
-                        rv.reassign(local, STANDBY, NONE);
-                        updateTerm(deviceId);
-                        role = MastershipRole.MASTER;
-                    } else {
-                        rv.add(STANDBY, local);
-                        rv.reassign(local, NONE, STANDBY);
-                        role = MastershipRole.STANDBY;
-                    }
-                    roleMap.put(deviceId, rv);
-                    break;
+                    // TODO: notifyDelegate BACKUPS_CHANGED
+                    return STANDBY;
                 default:
-                    log.warn("unknown Mastership Role {}", role);
+                    log.warn("unknown Mastership Role {}", currentRole);
             }
-            return role;
+            return currentRole;
         } finally {
             roleMap.unlock(deviceId);
         }
@@ -227,35 +249,58 @@
 
     @Override
     public MastershipTerm getTermFor(DeviceId deviceId) {
-        RoleValue rv = getRoleValue(deviceId);
-        if ((rv.get(MASTER) == null) || (terms.get(deviceId) == null)) {
-            return null;
+        // term information and role must be read atomically
+        // acquiring write lock for the device
+        roleMap.lock(deviceId);
+        try {
+            RoleValue rv = getRoleValue(deviceId);
+            final Integer term = terms.get(deviceId);
+            final NodeId master = rv.get(MASTER);
+            if ((master == null) || (term == null)) {
+                return null;
+            }
+            return MastershipTerm.of(master, term);
+        } finally {
+            roleMap.unlock(deviceId);
         }
-        return MastershipTerm.of(rv.get(MASTER), terms.get(deviceId));
     }
 
     @Override
     public MastershipEvent setStandby(NodeId nodeId, DeviceId deviceId) {
-        MastershipEvent event = null;
+        // if nodeId was MASTER, rotate STANDBY
+        // if nodeId was STANDBY no-op
+        // if nodeId was NONE, add to STANDBY
 
         roleMap.lock(deviceId);
         try {
-            RoleValue rv = getRoleValue(deviceId);
-            MastershipRole role = getRole(nodeId, deviceId);
-            switch (role) {
+            final RoleValue rv = getRoleValue(deviceId);
+            final MastershipRole currentRole = getRole(nodeId, deviceId);
+            switch (currentRole) {
                 case MASTER:
-                    event = reelect(nodeId, deviceId, rv);
-                    //fall through to reinforce role
+                    NodeId newMaster = reelect(nodeId, deviceId, rv);
+                    rv.reassign(nodeId, NONE, STANDBY);
+                    if (newMaster != null) {
+                        updateTerm(deviceId);
+                        roleMap.put(deviceId, rv);
+                        return new MastershipEvent(MASTER_CHANGED, deviceId, rv.roleInfo());
+                    } else {
+                        // no master candidate
+                        roleMap.put(deviceId, rv);
+                        // FIXME: Should there be new event type?
+                        // or should we issue null Master event?
+                        return null;
+                    }
                 case STANDBY:
-                    //fall through to reinforce role
+                    return null;
                 case NONE:
                     rv.reassign(nodeId, NONE, STANDBY);
                     roleMap.put(deviceId, rv);
-                    break;
+                    // TODO: BACKUPS_CHANGED?
+                    return null;
                 default:
-                    log.warn("unknown Mastership Role {}", role);
+                    log.warn("unknown Mastership Role {}", currentRole);
             }
-            return event;
+            return null;
         } finally {
             roleMap.unlock(deviceId);
         }
@@ -263,57 +308,71 @@
 
     @Override
     public MastershipEvent relinquishRole(NodeId nodeId, DeviceId deviceId) {
-        MastershipEvent event = null;
+        // relinquishRole is basically set to None
+
+        // If nodeId was master reelect next and remove nodeId
+        // else remove from STANDBY
 
         roleMap.lock(deviceId);
         try {
-            RoleValue rv = getRoleValue(deviceId);
-            MastershipRole role = getRole(nodeId, deviceId);
-            switch (role) {
+            final RoleValue rv = getRoleValue(deviceId);
+            final MastershipRole currentRole = rv.getRole(nodeId);
+            switch (currentRole) {
                 case MASTER:
-                    event = reelect(nodeId, deviceId, rv);
-                    if (event != null) {
-                        Integer term = terms.get(deviceId);
-                        terms.put(deviceId, ++term);
+                    NodeId newMaster = reelect(nodeId, deviceId, rv);
+                    if (newMaster != null) {
+                        updateTerm(deviceId);
+                        roleMap.put(deviceId, rv);
+                        return new MastershipEvent(MASTER_CHANGED, deviceId, rv.roleInfo());
+                    } else {
+                        // no master candidate
+                        roleMap.put(deviceId, rv);
+                        // Should there be new event type?
+                        return null;
                     }
-                    //fall through to reinforce relinquishment
                 case STANDBY:
                     //fall through to reinforce relinquishment
                 case NONE:
-                    rv.reassign(nodeId, STANDBY, NONE);
-                    roleMap.put(deviceId, rv);
-                    break;
+                    boolean modified = rv.reassign(nodeId, STANDBY, NONE);
+                    if (modified) {
+                        roleMap.put(deviceId, rv);
+                        // TODO: BACKUPS_CHANGED?
+                        return null;
+                    }
+                    return null;
                 default:
-                log.warn("unknown Mastership Role {}", role);
+                log.warn("unknown Mastership Role {}", currentRole);
             }
-            return event;
+            return null;
         } finally {
             roleMap.unlock(deviceId);
         }
     }
 
+    // TODO: Consider moving this to RoleValue method
     //helper to fetch a new master candidate for a given device.
-    private MastershipEvent reelect(
+    private NodeId reelect(
             NodeId current, DeviceId deviceId, RoleValue rv) {
 
         //if this is an queue it'd be neater.
-        NodeId backup = null;
+        NodeId candidate = null;
         for (NodeId n : rv.nodesOfRole(STANDBY)) {
             if (!current.equals(n)) {
-                backup = n;
+                candidate = n;
                 break;
             }
         }
 
-        if (backup == null) {
+        if (candidate == null) {
             log.info("{} giving up and going to NONE for {}", current, deviceId);
             rv.remove(MASTER, current);
+            // master did change, but there is no master candidate.
             return null;
         } else {
-            log.info("{} trying to pass mastership for {} to {}", current, deviceId, backup);
-            rv.replace(current, backup, MASTER);
-            rv.reassign(backup, STANDBY, NONE);
-            return new MastershipEvent(MASTER_CHANGED, deviceId, rv.roleInfo());
+            log.info("{} trying to pass mastership for {} to {}", current, deviceId, candidate);
+            rv.replace(current, candidate, MASTER);
+            rv.reassign(candidate, STANDBY, NONE);
+            return candidate;
         }
     }
 
@@ -340,17 +399,30 @@
     }
 
     //adds or updates term information.
+    // must be guarded by roleMap.lock(deviceId)
     private void updateTerm(DeviceId deviceId) {
-        terms.lock(deviceId);
-        try {
-            Integer term = terms.get(deviceId);
+        Integer term = terms.get(deviceId);
+        if (term == null) {
+            term = terms.putIfAbsent(deviceId, INIT);
             if (term == null) {
-                terms.put(deviceId, INIT);
-            } else {
-                terms.put(deviceId, ++term);
+                // initial term set successfully
+                return;
             }
-        } finally {
-            terms.unlock(deviceId);
+            // concurrent initialization detected,
+            // fall through to try incrementing
+        }
+        Integer nextTerm = term + 1;
+        boolean success = terms.replace(deviceId, term, nextTerm);
+        while (!success) {
+            term = terms.get(deviceId);
+            if (term == null) {
+                // something is very wrong, but write something to avoid
+                // infinite loop.
+                log.warn("Term info for {} disappeared.", deviceId);
+                term = putIfAbsent(terms, deviceId, nextTerm);
+            }
+            nextTerm = term + 1;
+            success = terms.replace(deviceId, term, nextTerm);
         }
     }
 
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 3944a6a..8cc05e8 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
@@ -15,6 +15,10 @@
  */
 package org.onlab.onos.store.mastership.impl;
 
+import static org.onlab.onos.net.MastershipRole.MASTER;
+import static org.onlab.onos.net.MastershipRole.NONE;
+import static org.onlab.onos.net.MastershipRole.STANDBY;
+
 import java.util.Collections;
 import java.util.EnumMap;
 import java.util.LinkedList;
@@ -59,18 +63,30 @@
         return value.get(type).contains(nodeId);
     }
 
+    public MastershipRole getRole(NodeId nodeId) {
+        if (contains(MASTER, nodeId)) {
+            return MASTER;
+        }
+        if (contains(STANDBY, nodeId)) {
+            return STANDBY;
+        }
+        return NONE;
+    }
+
     /**
      * Associates a node to a certain role.
      *
      * @param type the role
      * @param nodeId the node ID of the node to associate
+     * @return true if modified
      */
-    public void add(MastershipRole type, NodeId nodeId) {
+    public boolean add(MastershipRole type, NodeId nodeId) {
         List<NodeId> nodes = value.get(type);
 
         if (!nodes.contains(nodeId)) {
-            nodes.add(nodeId);
+            return nodes.add(nodeId);
         }
+        return false;
     }
 
     /**
@@ -78,7 +94,7 @@
      *
      * @param type the role
      * @param nodeId the ID of the node to remove
-     * @return
+     * @return true if modified
      */
     public boolean remove(MastershipRole type, NodeId nodeId) {
         List<NodeId> nodes = value.get(type);
@@ -96,10 +112,12 @@
      * @param nodeId the Node ID of node changing roles
      * @param from the old role
      * @param to the new role
+     * @return true if modified
      */
-    public void reassign(NodeId nodeId, MastershipRole from, MastershipRole to) {
-        remove(from, nodeId);
-        add(to, nodeId);
+    public boolean reassign(NodeId nodeId, MastershipRole from, MastershipRole to) {
+        boolean modified = remove(from, nodeId);
+        modified |= add(to, nodeId);
+        return modified;
     }
 
     /**
@@ -109,10 +127,12 @@
      * @param from the old NodeId to replace
      * @param to the new NodeId
      * @param type the role associated with the old NodeId
+     * @return true if modified
      */
-    public void replace(NodeId from, NodeId to, MastershipRole type) {
-        remove(type, from);
-        add(type, to);
+    public boolean replace(NodeId from, NodeId to, MastershipRole type) {
+        boolean modified = remove(type, from);
+        modified |= add(type, to);
+        return modified;
     }
 
     /**
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 ed6a859..d03ffe6 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
@@ -142,7 +142,7 @@
         testStore.setCurrent(CN1);
 
         //if already MASTER, nothing should happen
-        testStore.put(DID2, N1, true, false, false);
+        testStore.put(DID2, N1, true, false, true);
         assertEquals("wrong role for MASTER:", MASTER, dms.requestRole(DID2));
 
         //populate maps with DID1, N1 thru NONE case
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoNamespaces.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoNamespaces.java
index 4fc88b6..9341cba 100644
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoNamespaces.java
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoNamespaces.java
@@ -22,11 +22,11 @@
 import java.util.HashSet;
 import java.util.LinkedList;
 
-import org.onlab.onos.core.DefaultApplicationId;
 import org.onlab.onos.cluster.ControllerNode;
 import org.onlab.onos.cluster.DefaultControllerNode;
 import org.onlab.onos.cluster.NodeId;
 import org.onlab.onos.cluster.RoleInfo;
+import org.onlab.onos.core.DefaultApplicationId;
 import org.onlab.onos.mastership.MastershipTerm;
 import org.onlab.onos.net.ConnectPoint;
 import org.onlab.onos.net.DefaultAnnotations;
@@ -59,6 +59,9 @@
 import org.onlab.onos.net.flow.criteria.Criteria;
 import org.onlab.onos.net.flow.criteria.Criterion;
 import org.onlab.onos.net.flow.instructions.Instructions;
+import org.onlab.onos.net.flow.instructions.L0ModificationInstruction;
+import org.onlab.onos.net.flow.instructions.L2ModificationInstruction;
+import org.onlab.onos.net.flow.instructions.L3ModificationInstruction;
 import org.onlab.onos.net.host.DefaultHostDescription;
 import org.onlab.onos.net.host.HostDescription;
 import org.onlab.onos.net.intent.ConnectivityIntent;
@@ -66,6 +69,7 @@
 import org.onlab.onos.net.intent.Intent;
 import org.onlab.onos.net.intent.IntentId;
 import org.onlab.onos.net.intent.IntentState;
+import org.onlab.onos.net.intent.LinkCollectionIntent;
 import org.onlab.onos.net.intent.MultiPointToSinglePointIntent;
 import org.onlab.onos.net.intent.PathIntent;
 import org.onlab.onos.net.intent.PointToPointIntent;
@@ -115,6 +119,7 @@
                     //
                     ControllerNode.State.class,
                     Device.Type.class,
+                    Port.Type.class,
                     ChassisId.class,
                     DefaultAnnotations.class,
                     DefaultControllerNode.class,
@@ -149,6 +154,17 @@
                     DefaultTrafficTreatment.class,
                     Instructions.DropInstruction.class,
                     Instructions.OutputInstruction.class,
+                    L0ModificationInstruction.class,
+                    L0ModificationInstruction.L0SubType.class,
+                    L0ModificationInstruction.ModLambdaInstruction.class,
+                    L2ModificationInstruction.class,
+                    L2ModificationInstruction.L2SubType.class,
+                    L2ModificationInstruction.ModEtherInstruction.class,
+                    L2ModificationInstruction.ModVlanIdInstruction.class,
+                    L2ModificationInstruction.ModVlanPcpInstruction.class,
+                    L3ModificationInstruction.class,
+                    L3ModificationInstruction.L3SubType.class,
+                    L3ModificationInstruction.ModIPInstruction.class,
                     RoleInfo.class,
                     FlowRuleBatchOperation.class,
                     CompletedBatchOperation.class,
@@ -163,7 +179,8 @@
                     DefaultEdgeLink.class,
                     HostToHostIntent.class,
                     PointToPointIntent.class,
-                    MultiPointToSinglePointIntent.class
+                    MultiPointToSinglePointIntent.class,
+                    LinkCollectionIntent.class
                     )
             .register(DefaultApplicationId.class, new DefaultApplicationIdSerializer())
             .register(URI.class, new URISerializer())
diff --git a/core/store/serializers/src/test/java/org/onlab/onos/store/serializers/KryoSerializerTest.java b/core/store/serializers/src/test/java/org/onlab/onos/store/serializers/KryoSerializerTest.java
index 4e7a8e0..a4f098a 100644
--- a/core/store/serializers/src/test/java/org/onlab/onos/store/serializers/KryoSerializerTest.java
+++ b/core/store/serializers/src/test/java/org/onlab/onos/store/serializers/KryoSerializerTest.java
@@ -15,13 +15,10 @@
  */
 package org.onlab.onos.store.serializers;
 
-import static org.junit.Assert.assertEquals;
-import static org.onlab.onos.net.DeviceId.deviceId;
-import static org.onlab.onos.net.PortNumber.portNumber;
-import static java.util.Arrays.asList;
-
-import java.nio.ByteBuffer;
-
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.testing.EqualsTester;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -50,10 +47,12 @@
 import org.onlab.packet.MacAddress;
 import org.onlab.util.KryoNamespace;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.testing.EqualsTester;
+import java.nio.ByteBuffer;
+
+import static java.util.Arrays.asList;
+import static org.junit.Assert.assertEquals;
+import static org.onlab.onos.net.DeviceId.deviceId;
+import static org.onlab.onos.net.PortNumber.portNumber;
 
 public class KryoSerializerTest {
 
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStore.java
index a4a47e2..0c7fb0c 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStore.java
@@ -291,8 +291,9 @@
                                    Port newPort,
                                    Map<PortNumber, Port> ports) {
         if (oldPort.isEnabled() != newPort.isEnabled() ||
+                oldPort.type() != newPort.type() ||
+                oldPort.portSpeed() != newPort.portSpeed() ||
                 !AnnotationsUtil.isEqual(oldPort.annotations(), newPort.annotations())) {
-
             ports.put(oldPort.number(), newPort);
             return new DeviceEvent(PORT_UPDATED, device, newPort);
         }
@@ -510,7 +511,10 @@
             }
         }
 
-        return new DefaultPort(device, number, isEnabled, annotations);
+        return portDesc == null ?
+                new DefaultPort(device, number, false, annotations) :
+                new DefaultPort(device, number, isEnabled, portDesc.type(),
+                                portDesc.portSpeed(), annotations);
     }
 
     /**
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleLinkResourceStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleLinkResourceStore.java
new file mode 100644
index 0000000..8af6fa3
--- /dev/null
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleLinkResourceStore.java
@@ -0,0 +1,207 @@
+/*
+ * 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.store.trivial.impl;
+
+import static com.google.common.base.Preconditions.*;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.intent.IntentId;
+import org.onlab.onos.net.resource.Bandwidth;
+import org.onlab.onos.net.resource.BandwidthResourceAllocation;
+import org.onlab.onos.net.resource.Lambda;
+import org.onlab.onos.net.resource.LambdaResourceAllocation;
+import org.onlab.onos.net.resource.LinkResourceAllocations;
+import org.onlab.onos.net.resource.LinkResourceStore;
+import org.onlab.onos.net.resource.ResourceAllocation;
+import org.onlab.onos.net.resource.ResourceType;
+import org.slf4j.Logger;
+
+/**
+ * Manages link resources using trivial in-memory structures implementation.
+ */
+@Component(immediate = true)
+@Service
+public class SimpleLinkResourceStore implements LinkResourceStore {
+    private final Logger log = getLogger(getClass());
+    private Map<IntentId, LinkResourceAllocations> linkResourceAllocationsMap;
+    private Map<Link, Set<LinkResourceAllocations>> allocatedResources;
+    private Map<Link, Set<ResourceAllocation>> freeResources;
+
+    @Activate
+    public void activate() {
+        linkResourceAllocationsMap = new HashMap<>();
+        allocatedResources = new HashMap<>();
+        freeResources = new HashMap<>();
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        log.info("Stopped");
+    }
+
+    private Set<ResourceAllocation> readOriginalFreeResources(Link link) {
+        // TODO read capacity and lambda resources from topology
+        Set<ResourceAllocation> allocations = new HashSet<>();
+        for (int i = 1; i <= 100; i++) {
+            allocations.add(new LambdaResourceAllocation(Lambda.valueOf(i)));
+        }
+        allocations.add(new BandwidthResourceAllocation(Bandwidth.valueOf(1000000)));
+        return allocations;
+    }
+
+    private BandwidthResourceAllocation getBandwidth(Set<ResourceAllocation> freeRes) {
+        for (ResourceAllocation res : freeRes) {
+            if (res.type() == ResourceType.BANDWIDTH) {
+                return (BandwidthResourceAllocation) res;
+            }
+        }
+        return new BandwidthResourceAllocation(Bandwidth.valueOf(0));
+    }
+
+    private void subtractFreeResources(Link link, LinkResourceAllocations allocations) {
+        // TODO Use lock or version for updating freeResources.
+        checkNotNull(link);
+        Set<ResourceAllocation> freeRes = freeResources.get(link);
+        checkNotNull(freeRes);
+        freeRes = new HashSet<>(freeRes);
+        Set<ResourceAllocation> subRes = allocations.getResourceAllocation(link);
+        for (ResourceAllocation res : subRes) {
+            switch (res.type()) {
+            case BANDWIDTH:
+                BandwidthResourceAllocation ba = getBandwidth(freeRes);
+                double requestedBandwidth =
+                        ((BandwidthResourceAllocation) res).bandwidth().toDouble();
+                double newBandwidth = ba.bandwidth().toDouble() - requestedBandwidth;
+                checkState(newBandwidth >= 0.0);
+                freeRes.remove(ba);
+                freeRes.add(new BandwidthResourceAllocation(
+                        Bandwidth.valueOf(newBandwidth)));
+                break;
+            case LAMBDA:
+                checkState(freeRes.remove(res));
+                break;
+            default:
+                break;
+            }
+        }
+        freeResources.put(link, freeRes);
+
+    }
+
+    private void addFreeResources(Link link, LinkResourceAllocations allocations) {
+        // TODO Use lock or version for updating freeResources.
+        Set<ResourceAllocation> freeRes = freeResources.get(link);
+        checkNotNull(freeRes);
+        freeRes = new HashSet<>(freeRes);
+        Set<ResourceAllocation> addRes = allocations.getResourceAllocation(link);
+        for (ResourceAllocation res : addRes) {
+            switch (res.type()) {
+            case BANDWIDTH:
+                BandwidthResourceAllocation ba = getBandwidth(freeRes);
+                double requestedBandwidth =
+                        ((BandwidthResourceAllocation) res).bandwidth().toDouble();
+                double newBandwidth = ba.bandwidth().toDouble() + requestedBandwidth;
+                freeRes.remove(ba);
+                freeRes.add(new BandwidthResourceAllocation(
+                        Bandwidth.valueOf(newBandwidth)));
+                break;
+            case LAMBDA:
+                checkState(freeRes.add(res));
+                break;
+            default:
+                break;
+            }
+        }
+        freeResources.put(link, freeRes);
+    }
+
+    @Override
+    public Set<ResourceAllocation> getFreeResources(Link link) {
+        checkNotNull(link);
+        Set<ResourceAllocation> freeRes = freeResources.get(link);
+        if (freeRes == null) {
+            freeRes = readOriginalFreeResources(link);
+        }
+
+        return freeRes;
+    }
+
+    @Override
+    public void allocateResources(LinkResourceAllocations allocations) {
+        checkNotNull(allocations);
+        linkResourceAllocationsMap.put(allocations.intendId(), allocations);
+        for (Link link : allocations.links()) {
+            subtractFreeResources(link, allocations);
+            Set<LinkResourceAllocations> linkAllocs = allocatedResources.get(link);
+            if (linkAllocs == null) {
+                linkAllocs = new HashSet<>();
+            }
+            linkAllocs.add(allocations);
+            allocatedResources.put(link, linkAllocs);
+        }
+    }
+
+    @Override
+    public void releaseResources(LinkResourceAllocations allocations) {
+        checkNotNull(allocations);
+        linkResourceAllocationsMap.remove(allocations);
+        for (Link link : allocations.links()) {
+            addFreeResources(link, allocations);
+            Set<LinkResourceAllocations> linkAllocs = allocatedResources.get(link);
+            if (linkAllocs == null) {
+                log.error("Missing resource allocation.");
+            } else {
+                linkAllocs.remove(allocations);
+            }
+            allocatedResources.put(link, linkAllocs);
+        }
+    }
+
+    @Override
+    public LinkResourceAllocations getAllocations(IntentId intentId) {
+        checkNotNull(intentId);
+        return linkResourceAllocationsMap.get(intentId);
+    }
+
+    @Override
+    public Iterable<LinkResourceAllocations> getAllocations(Link link) {
+        checkNotNull(link);
+        Set<LinkResourceAllocations> result = allocatedResources.get(link);
+        if (result == null) {
+            result = Collections.emptySet();
+        }
+        return Collections.unmodifiableSet(result);
+    }
+
+    @Override
+    public Iterable<LinkResourceAllocations> getAllocations() {
+        return Collections.unmodifiableCollection(linkResourceAllocationsMap.values());
+    }
+
+}
diff --git a/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/LinkDiscovery.java b/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/LinkDiscovery.java
index 07485c0..ce6d4fb 100644
--- a/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/LinkDiscovery.java
+++ b/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/LinkDiscovery.java
@@ -15,6 +15,22 @@
  */
 package org.onlab.onos.provider.lldp.impl;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.onlab.onos.net.MastershipRole.MASTER;
+import static org.onlab.onos.net.PortNumber.portNumber;
+import static org.onlab.onos.net.flow.DefaultTrafficTreatment.builder;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
 import org.jboss.netty.util.Timeout;
 import org.jboss.netty.util.TimerTask;
 import org.onlab.onos.mastership.MastershipService;
@@ -36,22 +52,6 @@
 import org.onlab.util.Timer;
 import org.slf4j.Logger;
 
-import java.nio.ByteBuffer;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static org.onlab.onos.net.MastershipRole.MASTER;
-import static org.onlab.onos.net.PortNumber.portNumber;
-import static org.onlab.onos.net.flow.DefaultTrafficTreatment.builder;
-import static org.slf4j.LoggerFactory.getLogger;
-
 /**
  * Run discovery process from a physical switch. Ports are initially labeled as
  * slow ports. When an LLDP is successfully received, label the remote port as
@@ -336,7 +336,7 @@
     private void sendProbes(Long portNumber) {
         // TODO: should have suppression port configuration, not by type
         if (device.type() != Device.Type.ROADM) {
-            log.debug("Sending probes out to {}@{}", portNumber, device.id());
+            log.trace("Sending probes out to {}@{}", portNumber, device.id());
             OutboundPacket pkt = this.createOutBoundLLDP(portNumber);
             pktService.emit(pkt);
             if (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 6ba0fe3..8aa3d77 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
@@ -23,6 +23,7 @@
 import org.onlab.onos.net.Device;
 import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.net.MastershipRole;
+import org.onlab.onos.net.Port;
 import org.onlab.onos.net.PortNumber;
 import org.onlab.onos.net.device.DefaultDeviceDescription;
 import org.onlab.onos.net.device.DefaultPortDescription;
@@ -43,14 +44,19 @@
 import org.projectfloodlight.openflow.protocol.OFFactory;
 import org.projectfloodlight.openflow.protocol.OFPortConfig;
 import org.projectfloodlight.openflow.protocol.OFPortDesc;
+import org.projectfloodlight.openflow.protocol.OFPortFeatures;
 import org.projectfloodlight.openflow.protocol.OFPortState;
 import org.projectfloodlight.openflow.protocol.OFPortStatus;
+import org.projectfloodlight.openflow.protocol.OFVersion;
+import org.projectfloodlight.openflow.types.PortSpeed;
 import org.slf4j.Logger;
 
 import java.util.ArrayList;
 import java.util.List;
 
 import static org.onlab.onos.net.DeviceId.deviceId;
+import static org.onlab.onos.net.Port.Type.COPPER;
+import static org.onlab.onos.net.Port.Type.FIBER;
 import static org.onlab.onos.openflow.controller.Dpid.dpid;
 import static org.onlab.onos.openflow.controller.Dpid.uri;
 import static org.slf4j.LoggerFactory.getLogger;
@@ -63,6 +69,7 @@
 public class OpenFlowDeviceProvider extends AbstractProvider implements DeviceProvider {
 
     private static final Logger LOG = getLogger(OpenFlowDeviceProvider.class);
+    private static final long MBPS = 1_000 * 1_000;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected DeviceProviderRegistry providerRegistry;
@@ -122,9 +129,9 @@
 
         OpenFlowSwitch sw = controller.getSwitch(dpid(device.id().uri()));
         //if (!checkChannel(device, sw)) {
-          //  LOG.error("Failed to probe device {} on sw={}", device, sw);
+        //  LOG.error("Failed to probe device {} on sw={}", device, sw);
         //  providerService.deviceDisconnected(device.id());
-            //return;
+        //return;
         //}
 
         // Prompt an update of port information. We can use any XID for this.
@@ -143,13 +150,13 @@
 
     // Checks if the OF channel is connected.
     //private boolean checkChannel(Device device, OpenFlowSwitch sw) {
-        // FIXME if possible, we might want this to be part of
-        // OpenFlowSwitch interface so the driver interface isn't misused.
+    // FIXME if possible, we might want this to be part of
+    // OpenFlowSwitch interface so the driver interface isn't misused.
     //    if (sw == null || !((OpenFlowSwitchDriver) sw).isConnected()) {
-      //      return false;
-  //      }
+    //      return false;
+    //      }
     //    return true;
-   // }
+    // }
 
     @Override
     public void roleChanged(Device device, MastershipRole newRole) {
@@ -188,7 +195,7 @@
                                                  sw.hardwareDescription(),
                                                  sw.softwareDescription(),
                                                  sw.serialNumber(),
-                                                cId);
+                                                 cId);
             providerService.deviceConnected(did, description);
             providerService.updatePorts(did, buildPortDescriptions(sw.getPorts()));
         }
@@ -244,8 +251,7 @@
          * @param ports the list of ports
          * @return list of portdescriptions
          */
-        private List<PortDescription> buildPortDescriptions(
-                List<OFPortDesc> ports) {
+        private List<PortDescription> buildPortDescriptions(List<OFPortDesc> ports) {
             final List<PortDescription> portDescs = new ArrayList<>(ports.size());
             for (OFPortDesc port : ports) {
                 portDescs.add(buildPortDescription(port));
@@ -260,12 +266,25 @@
          * @return portDescription for the port.
          */
         private PortDescription buildPortDescription(OFPortDesc port) {
-            final PortNumber portNo = PortNumber.portNumber(port.getPortNo().getPortNumber());
-            final boolean enabled = !port.getState().contains(OFPortState.LINK_DOWN) &&
-                    !port.getConfig().contains(OFPortConfig.PORT_DOWN);
-            return new DefaultPortDescription(portNo, enabled);
+            PortNumber portNo = PortNumber.portNumber(port.getPortNo().getPortNumber());
+            boolean enabled =
+                    !port.getState().contains(OFPortState.LINK_DOWN) &&
+                            !port.getConfig().contains(OFPortConfig.PORT_DOWN);
+            Port.Type type = port.getCurr().contains(OFPortFeatures.PF_FIBER) ? FIBER : COPPER;
+            return new DefaultPortDescription(portNo, enabled, type, portSpeed(port));
         }
 
+        private long portSpeed(OFPortDesc port) {
+            if (port.getVersion() == OFVersion.OF_13) {
+                return port.getCurrSpeed() / MBPS;
+            }
+
+            PortSpeed portSpeed = PortSpeed.SPEED_NONE;
+            for (OFPortFeatures feat : port.getCurr()) {
+                portSpeed = PortSpeed.max(portSpeed, feat.getPortSpeed());
+            }
+            return portSpeed.getSpeedBps() / MBPS;
+        }
     }
 
 }
diff --git a/tools/build/onos-build b/tools/build/onos-build
index 2f31eeb..6257340 100755
--- a/tools/build/onos-build
+++ b/tools/build/onos-build
@@ -6,4 +6,4 @@
 [ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1
 . $ONOS_ROOT/tools/build/envDefaults
 
-cd $ONOS_ROOT && mvn clean install && cd docs && mvn javadoc:aggregate
+cd $ONOS_ROOT && mvn clean install "$@" && cd docs && mvn javadoc:aggregate
diff --git a/tools/dev/bash_profile b/tools/dev/bash_profile
index 8d4a784..2e4c564 100644
--- a/tools/dev/bash_profile
+++ b/tools/dev/bash_profile
@@ -34,6 +34,7 @@
 
 # Short-hand for ONOS build, package and test.
 alias ob='onos-build'
+alias obi='onos-build -Dmaven.test.failure.ignore=true'
 alias obs='onos-build-selective'
 alias op='onos-package'
 alias ot='onos-test'
diff --git a/tools/test/cells/cbench b/tools/test/cells/cbench
index 2e0b478..8601929 100644
--- a/tools/test/cells/cbench
+++ b/tools/test/cells/cbench
@@ -1,7 +1,5 @@
 # Local VirtualBox-based single ONOS instance & ONOS mininet box
 
-export ONOS_CELL="cbench"
-
 export ONOS_NIC=192.168.56.*
 export OC1="192.168.56.103"
 export OCN="192.168.56.103"
diff --git a/tools/test/cells/local b/tools/test/cells/local
index 64b63ec..e8ca7e9 100644
--- a/tools/test/cells/local
+++ b/tools/test/cells/local
@@ -1,7 +1,5 @@
 # Local VirtualBox-based ONOS instances 1,2 & ONOS mininet box
 
-export ONOS_CELL="local"
-
 export ONOS_NIC=192.168.56.*
 export OC1="192.168.56.101"
 export OC2="192.168.56.102"
diff --git a/tools/test/cells/office b/tools/test/cells/office
index 7c6345b..aec5e27 100644
--- a/tools/test/cells/office
+++ b/tools/test/cells/office
@@ -1,7 +1,5 @@
 # ProxMox-based cell of ONOS instance; no mininet-box
 
-export ONOS_CELL="office"
-
 export ONOS_NIC="10.1.10.*"
 export OC1="10.1.10.223"
 export OCI="${OC1}"
diff --git a/tools/test/cells/prox b/tools/test/cells/prox
index 557388f..28e073f 100644
--- a/tools/test/cells/prox
+++ b/tools/test/cells/prox
@@ -1,7 +1,5 @@
 # ProxMox-based cell of ONOS instances 1,2 & ONOS mininet box
 
-export ONOS_CELL="prox"
-
 export ONOS_NIC="10.1.9.*"
 export OC1="10.1.9.94"
 export OC2="10.1.9.82"
diff --git a/tools/test/cells/single b/tools/test/cells/single
index 50296c1..032a583 100644
--- a/tools/test/cells/single
+++ b/tools/test/cells/single
@@ -1,7 +1,5 @@
 # Local VirtualBox-based single ONOS instance & ONOS mininet box
 
-export ONOS_CELL="single"
-
 export ONOS_NIC=192.168.56.*
 export OC1="192.168.56.101"
 export OCN="192.168.56.103"
diff --git a/tools/test/cells/triple b/tools/test/cells/triple
index 104eb05..439b837 100644
--- a/tools/test/cells/triple
+++ b/tools/test/cells/triple
@@ -1,7 +1,5 @@
 # Local VirtualBox-based ONOS instances 1,2,3 & ONOS mininet box
 
-export ONOS_CELL="triple"
-
 export ONOS_NIC=192.168.56.*
 export OC1="192.168.56.101"
 export OC2="192.168.56.102"
diff --git a/tools/test/topos/oe-linear-3.json b/tools/test/topos/oe-linear-3.json
index b3cd61b..91c4f34 100644
--- a/tools/test/topos/oe-linear-3.json
+++ b/tools/test/topos/oe-linear-3.json
@@ -3,28 +3,33 @@
         {
             "uri": "of:0000ffffffffff01", "mac": "ffffffffffff01", "type": "ROADM",
             "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "ROADM1",
-            "annotations": { "latitude": 37.6, "longitude": 122.3, "optical.regens": 0 }
+            "annotations": { "latitude": 37.6, "longitude": 122.3, "optical.regens": 0 },
+            "ports": [ { "port": 10, "speed": 100000, "type": "FIBER" }, { "port": 20, "speed": 0, "type": "FIBER" } ]
         },
         {
             "uri": "of:0000ffffffffff02", "mac": "ffffffffffff02", "type": "ROADM",
             "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "ROADM2",
-            "annotations": { "latitude": 37.3, "longitude": 121.9, "optical.regens": 0 }
+            "annotations": { "latitude": 37.3, "longitude": 121.9, "optical.regens": 0 },
+            "ports": [ { "port": 11, "speed": 100000, "type": "FIBER" }, { "port": 21, "speed": 0, "type": "FIBER" } ]
         },
         {
             "uri": "of:0000ffffffffff03", "mac": "ffffffffffff03", "type": "ROADM",
             "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "ROADM3",
-            "annotations": { "latitude": 33.9, "longitude": 118.4, "optical.regens": 2 }
+            "annotations": { "latitude": 33.9, "longitude": 118.4, "optical.regens": 2 },
+            "ports": [ { "port": 30, "speed": 0, "type": "FIBER" }, { "port": 31, "speed": 0, "type": "FIBER" } ]
         },
 
         {
-            "uri": "of:0000ffffffff0001", "mac": "ffffffffff0003", "type": "SWITCH",
+            "uri": "of:0000ffffffff0001", "mac": "ffffffffff0001", "type": "SWITCH",
             "mfr": "Linc", "hw": "PK", "sw": "?", "serial": "?", "name": "ROUTER1",
-            "annotations": { "latitude": 37.6, "longitude": 122.3 }
+            "annotations": { "latitude": 37.6, "longitude": 122.3 },
+            "ports": [ { "port": 1, "speed": 10000, "type": "COPPER" }, { "port": 2, "speed": 100000, "type": "FIBER" } ]
         },
         {
             "uri": "of:0000ffffffff0002", "mac": "ffffffffff0002", "type": "SWITCH",
             "mfr": "Linc", "hw": "PK", "sw": "?", "serial": "?", "name": "ROUTER2",
-            "annotations": { "latitude": 37.3, "longitude": 121.9 }
+            "annotations": { "latitude": 37.3, "longitude": 121.9 },
+            "ports": [ { "port": 1, "speed": 10000, "type": "COPPER" }, { "port": 2, "speed": 100000, "type": "FIBER" } ]
         }
     ],
 
@@ -36,10 +41,8 @@
         { "src": "of:0000ffffffff0002/2", "dst": "of:0000ffffffffff02/11", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect" } }
     ],
 
-    "Xhosts" : [
-        { "mac": "a0:00:00:00:00:11", "vlan": -1, "location": "of:0000ffffffff0001/11", "ip": "1.2.3.4" },
-        { "mac": "a0:00:00:00:00:12", "vlan": -1, "location": "of:0000ffffffff0001/12", "ip": "1.2.3.5" },
-        { "mac": "a0:00:00:00:00:21", "vlan": -1, "location": "of:0000ffffffff0002/11", "ip": "2.2.3.4" },
-        { "mac": "a0:00:00:00:00:22", "vlan": -1, "location": "of:0000ffffffff0002/12", "ip": "2.2.3.5" }
+    "hosts" : [
+        { "mac": "00:00:00:00:00:01", "vlan": -1, "location": "of:0000ffffffff0001/1", "ip": "10.0.0.1" },
+        { "mac": "00:00:00:00:00:02", "vlan": -1, "location": "of:0000ffffffff0002/1", "ip": "10.0.0.2" }
     ]
 }
\ No newline at end of file
diff --git a/tools/test/topos/oe-nonlinear-10.json b/tools/test/topos/oe-nonlinear-10.json
new file mode 100644
index 0000000..154cf4d
--- /dev/null
+++ b/tools/test/topos/oe-nonlinear-10.json
@@ -0,0 +1,140 @@
+{
+    "devices" : [
+        {
+            "uri": "of:0000ffffffffff01", "mac": "ffffffffffff01", "type": "ROADM",
+            "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "SFO-W10",
+            "annotations": { "latitude": 37.6, "longitude": 122.3, "optical.regens": 0 },
+            "ports": [ { "port": 10, "speed": 100000, "type": "FIBER" }, { "port": 20, "speed": 0, "type": "FIBER" } ]
+        },
+        {
+            "uri": "of:0000ffffffffff02", "mac": "ffffffffffff02", "type": "ROADM",
+            "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "SJC-W10",
+            "annotations": { "latitude": 37.3, "longitude": 121.9, "optical.regens": 0 },
+            "ports": [ { "port": 11, "speed": 100000, "type": "FIBER" }, { "port": 21, "speed": 0, "type": "FIBER" } ]
+        },
+        {
+            "uri": "of:0000ffffffffff03", "mac": "ffffffffffff03", "type": "ROADM",
+            "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "LAX-W10",
+            "annotations": { "latitude": 33.9, "longitude": 118.4, "optical.regens": 0 },
+            "ports": [ { "port": 30, "speed": 0, "type": "FIBER" }, { "port": 31, "speed": 0, "type": "FIBER" } ]
+        },
+        {
+            "uri": "of:0000ffffffffff04", "mac": "ffffffffffff04", "type": "ROADM",
+            "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "SDG-W10",
+            "annotations": { "latitude": 32.8, "longitude": 117.1, "optical.regens": 3 },
+            "ports": [ { "port": 30, "speed": 0, "type": "FIBER" }, { "port": 31, "speed": 0, "type": "FIBER" } ]
+        },
+        {
+            "uri": "of:0000ffffffffff05", "mac": "ffffffffffff05", "type": "ROADM",
+            "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "MSP-M10",
+            "annotations": { "latitude": 44.8, "longitude": 93.1, "optical.regens": 3 },
+            "ports": [ { "port": 30, "speed": 0, "type": "FIBER" }, { "port": 31, "speed": 0, "type": "FIBER" } ]
+        },
+        {
+            "uri": "of:0000ffffffffff06", "mac": "ffffffffffff06", "type": "ROADM",
+            "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "DFW-M10",
+            "annotations": { "latitude": 32.8, "longitude": 97.1, "optical.regens": 3 },
+            "ports": [ { "port": 30, "speed": 0, "type": "FIBER" }, { "port": 31, "speed": 0, "type": "FIBER" } ]
+        },
+        {
+            "uri": "of:0000ffffffffff07", "mac": "ffffffffffff07", "type": "ROADM",
+            "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "CHG-N10",
+            "annotations": { "latitude": 41.8, "longitude": 120.1, "optical.regens": 3 },
+            "ports": [ { "port": 30, "speed": 0, "type": "FIBER" }, { "port": 31, "speed": 0, "type": "FIBER" } ]
+        },
+        {
+            "uri": "of:0000ffffffffff08", "mac": "ffffffffffff08", "type": "ROADM",
+            "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "IAD-M10",
+            "annotations": { "latitude": 38.8, "longitude": 77.1, "optical.regens": 3 },
+            "ports": [ { "port": 30, "speed": 0, "type": "FIBER" }, { "port": 31, "speed": 0, "type": "FIBER" } ]
+        },
+        {
+            "uri": "of:0000ffffffffff09", "mac": "ffffffffffff09", "type": "ROADM",
+            "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "JFK-M10",
+            "annotations": { "latitude": 40.8, "longitude": 73.1, "optical.regens": 0 },
+            "ports": [ { "port": 30, "speed": 0, "type": "FIBER" }, { "port": 31, "speed": 0, "type": "FIBER" } ]
+        },
+        {
+            "uri": "of:0000ffffffffff0A", "mac": "ffffffffffff0A", "type": "ROADM",
+            "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "ATL-S10",
+            "annotations": { "latitude": 33.8, "longitude": 84.1, "optical.regens": 0 },
+            "ports": [ { "port": 30, "speed": 0, "type": "FIBER" }, { "port": 31, "speed": 0, "type": "FIBER" } ]
+        },
+        {
+            "uri": "of:0000ffffffffff06", "mac": "ffffffffffff06", "type": "ROADM",
+            "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "DFW-M10",
+            "annotations": { "latitude": 32.8, "longitude": 97.1, "optical.regens": 3 },
+            "ports": [ { "port": 30, "speed": 0, "type": "FIBER" }, { "port": 31, "speed": 0, "type": "FIBER" } ]
+        },
+
+
+        {
+            "uri": "of:0000ffffffff0001", "mac": "ffffffffff0001", "type": "SWITCH",
+            "mfr": "Linc", "hw": "PK", "sw": "?", "serial": "?", "name": "SFO-R10",
+            "annotations": { "latitude": 37.6, "longitude": 122.3 },
+            "ports": [ { "port": 1, "speed": 10000, "type": "COPPER" }, { "port": 2, "speed": 100000, "type": "FIBER" } ]
+        },
+        {
+            "uri": "of:0000ffffffff0003", "mac": "ffffffffff0003", "type": "SWITCH",
+            "mfr": "Linc", "hw": "PK", "sw": "?", "serial": "?", "name": "LAX-R10",
+            "annotations": { "latitude": 33.9, "longitude": 118.4 },
+            "ports": [ { "port": 1, "speed": 10000, "type": "COPPER" }, { "port": 2, "speed": 100000, "type": "FIBER" } ]
+        },
+        {
+            "uri": "of:0000ffffffff0004", "mac": "ffffffffff0004", "type": "SWITCH",
+            "mfr": "Linc", "hw": "PK", "sw": "?", "serial": "?", "name": "SDG-R10",
+            "annotations": { "latitude": 32.8, "longitude": 117.1 },
+            "ports": [ { "port": 1, "speed": 10000, "type": "COPPER" }, { "port": 2, "speed": 100000, "type": "FIBER" } ]
+        },
+        {
+            "uri": "of:0000ffffffff0007", "mac": "ffffffffff0007", "type": "SWITCH",
+            "mfr": "Linc", "hw": "PK", "sw": "?", "serial": "?", "name": "CHG-R10",
+            "annotations": { "latitude": 41.8, "longitude": 120.1 },
+            "ports": [ { "port": 1, "speed": 10000, "type": "COPPER" }, { "port": 2, "speed": 100000, "type": "FIBER" } ]
+        },
+        {
+            "uri": "of:0000ffffffff0009", "mac": "ffffffffff0009", "type": "SWITCH",
+            "mfr": "Linc", "hw": "PK", "sw": "?", "serial": "?", "name": "JFK-R10",
+            "annotations": { "latitude": 40.8, "longitude": 73.1 },
+            "ports": [ { "port": 1, "speed": 10000, "type": "COPPER" }, { "port": 2, "speed": 100000, "type": "FIBER" } ]
+        },
+        {
+            "uri": "of:0000ffffffff000A", "mac": "ffffffffff000A", "type": "SWITCH",
+            "mfr": "Linc", "hw": "PK", "sw": "?", "serial": "?", "name": "ATL-R10",
+            "annotations": { "latitude": 33.8, "longitude": 84.1 },
+            "ports": [ { "port": 1, "speed": 10000, "type": "COPPER" }, { "port": 2, "speed": 100000, "type": "FIBER" } ]
+        }
+
+    ],
+
+    "links" : [
+        { "src": "of:0000ffffffffff01/10", "dst": "of:0000ffffffffff02/10", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000 } },
+        { "src": "of:0000ffffffffff02/10", "dst": "of:0000ffffffffff03/10", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000 } },
+        { "src": "of:0000ffffffffff03/30", "dst": "of:0000ffffffffff04/10", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000 } },
+        { "src": "of:0000ffffffffff02/20", "dst": "of:0000ffffffffff05/10", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000 } },
+        { "src": "of:0000ffffffffff03/20", "dst": "of:0000ffffffffff06/10", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000 } },
+        { "src": "of:0000ffffffffff05/30", "dst": "of:0000ffffffffff06/20", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000 } },
+        { "src": "of:0000ffffffffff05/20", "dst": "of:0000ffffffffff07/21", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000 } },
+        { "src": "of:0000ffffffffff06/30", "dst": "of:0000ffffffffff08/10", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000 } },
+        { "src": "of:0000ffffffffff07/30", "dst": "of:0000ffffffffff08/20", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000 } },
+        { "src": "of:0000ffffffffff07/20", "dst": "of:0000ffffffffff09/10", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000 } },
+        { "src": "of:0000ffffffffff08/30", "dst": "of:0000ffffffffff0A/10", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000 } },
+        { "src": "of:0000ffffffffff09/20", "dst": "of:0000ffffffffff0A/20", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000 } },
+
+        { "src": "of:0000ffffffff0001/2", "dst": "of:0000ffffffffff01/1", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect" } },
+        { "src": "of:0000ffffffff0003/2", "dst": "of:0000ffffffffff03/1", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect" } },
+        { "src": "of:0000ffffffff0004/2", "dst": "of:0000ffffffffff04/1", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect" } },
+        { "src": "of:0000ffffffff0007/2", "dst": "of:0000ffffffffff07/1", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect" } },
+        { "src": "of:0000ffffffff0009/2", "dst": "of:0000ffffffffff09/1", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect" } },
+        { "src": "of:0000ffffffff000A/2", "dst": "of:0000ffffffffff0A/1", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect" } }
+    ],
+
+    "hosts" : [
+        { "mac": "00:00:00:00:00:01", "vlan": -1, "location": "of:0000ffffffff0001/1", "ip": "10.0.0.1" },
+        { "mac": "00:00:00:00:00:03", "vlan": -1, "location": "of:0000ffffffff0003/1", "ip": "10.0.0.3" },
+        { "mac": "00:00:00:00:00:04", "vlan": -1, "location": "of:0000ffffffff0004/1", "ip": "10.0.0.4" },
+        { "mac": "00:00:00:00:00:07", "vlan": -1, "location": "of:0000ffffffff0007/1", "ip": "10.0.0.7" },
+        { "mac": "00:00:00:00:00:09", "vlan": -1, "location": "of:0000ffffffff0009/1", "ip": "10.0.0.9" },
+        { "mac": "00:00:00:00:00:0A", "vlan": -1, "location": "of:0000ffffffff000A/1", "ip": "10.0.0.10" }
+    ]
+}
\ No newline at end of file
diff --git a/utils/misc/src/main/java/org/onlab/packet/Ip4Prefix.java b/utils/misc/src/main/java/org/onlab/packet/Ip4Prefix.java
index b32cbba..e3b5246 100644
--- a/utils/misc/src/main/java/org/onlab/packet/Ip4Prefix.java
+++ b/utils/misc/src/main/java/org/onlab/packet/Ip4Prefix.java
@@ -101,20 +101,6 @@
         return this.address.toString() + "/" + this.prefixLen;
     }
 
-    /**
-     * Compares the value of two Ip4Prefix objects.
-     * <p/>
-     * Note the value of the IPv4 address is compared directly between the
-     * objects, and must match exactly for the objects to be considered equal.
-     * This may result in objects which represent the same IP prefix being
-     * classified as unequal, because the unsignificant bits of the address
-     * field don't match (the bits to the right of the prefix length).
-     * <p/>
-     * TODO Change this behavior so that objects that represent the same prefix
-     * are classified as equal according to this equals method.
-     *
-     * @see Object#equals(Object)
-     */
     @Override
     public boolean equals(Object other) {
         if (other == this) {
diff --git a/utils/misc/src/main/java/org/onlab/packet/Ip6Prefix.java b/utils/misc/src/main/java/org/onlab/packet/Ip6Prefix.java
index 9603b68..5422ae1 100644
--- a/utils/misc/src/main/java/org/onlab/packet/Ip6Prefix.java
+++ b/utils/misc/src/main/java/org/onlab/packet/Ip6Prefix.java
@@ -101,20 +101,6 @@
         return this.address.toString() + "/" + this.prefixLen;
     }
 
-    /**
-     * Compares the value of two Ip6Prefix objects.
-     * <p/>
-     * Note the value of the IPv6 address is compared directly between the
-     * objects, and must match exactly for the objects to be considered equal.
-     * This may result in objects which represent the same IP prefix being
-     * classified as unequal, because the unsignificant bits of the address
-     * field don't match (the bits to the right of the prefix length).
-     * <p/>
-     * TODO Change this behavior so that objects that represent the same prefix
-     * are classified as equal according to this equals method.
-     *
-     * @see Object#equals(Object)
-     */
     @Override
     public boolean equals(Object other) {
         if (other == 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 9dd97c3..6af87a7 100644
--- a/utils/misc/src/main/java/org/onlab/packet/IpAddress.java
+++ b/utils/misc/src/main/java/org/onlab/packet/IpAddress.java
@@ -17,6 +17,8 @@
 
 import java.nio.ByteBuffer;
 import java.util.Arrays;
+import java.util.Objects;
+import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
  * A class representing an IPv4 address.
@@ -37,38 +39,71 @@
     /**
      * Constructor for given IP address version and address octets.
      *
-     * @param ver the IP address version
-     * @param octets the IP address octets
-     */
-    private IpAddress(Version ver, byte[] octets) {
-        this.version = ver;
-        this.octets = Arrays.copyOf(octets, INET_BYTE_LENGTH);
-    }
-
-    /**
-     * Converts a byte array into an IP address.
-     *
-     * @param address the IP address value stored in network byte order
+     * @param value the IP address value stored in network byte order
      * (i.e., the most significant byte first)
-     * @return an IP address
+     * @param value the IP address value
      */
-    public static IpAddress valueOf(byte[] address) {
-        return new IpAddress(Version.INET, address);
+    private IpAddress(Version version, byte[] value) {
+        checkNotNull(value);
+
+        this.version = version;
+        this.octets = Arrays.copyOf(value, INET_BYTE_LENGTH);
     }
 
     /**
      * Converts an integer into an IPv4 address.
      *
-     * @param address an integer representing an IPv4 value
+     * @param value an integer representing an IPv4 value
      * @return an IP address
      */
-    public static IpAddress valueOf(int address) {
+    public static IpAddress valueOf(int value) {
         byte[] bytes =
-            ByteBuffer.allocate(INET_BYTE_LENGTH).putInt(address).array();
+            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) {
+        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).
+     *
+     * @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);
+        }
+
+        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 a IP address in string form, e.g. "10.0.0.1".
@@ -77,8 +112,9 @@
     public static IpAddress valueOf(String address) {
         final String[] net = address.split("\\.");
         if (net.length != INET_BYTE_LENGTH) {
-            throw new IllegalArgumentException("Malformed IP address string; "
-                    + "Address must have four decimal values separated by dots (.)");
+            String msg = "Malformed IPv4 address string; " +
+                "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++) {
@@ -115,6 +151,48 @@
         return bb.getInt();
     }
 
+    /**
+     * Creates an IP network mask prefix.
+     *
+     * @param prefixLen the length of the mask prefix. Must be in the interval
+     * [0, 32] for IPv4
+     * @return a new IP address that contains a mask prefix of the
+     * specified length
+     */
+    public static IpAddress makeMaskPrefix(int prefixLen) {
+        // Verify the prefix length
+        if ((prefixLen < 0) || (prefixLen > INET_BIT_LENGTH)) {
+            final String msg = "Invalid IPv4 prefix length: " + prefixLen +
+                ". Must be in the interval [0, 32].";
+            throw new IllegalArgumentException(msg);
+        }
+
+        long v = (0xffffffffL << (INET_BIT_LENGTH - prefixLen)) & 0xffffffffL;
+        return IpAddress.valueOf((int) v);
+    }
+
+    /**
+     * Creates an IP address by masking it with a network mask of given
+     * mask length.
+     *
+     * @param addr the address to mask
+     * @param prefixLen the length of the mask prefix. Must be in the interval
+     * [0, 32] for IPv4
+     * @return a new IP address that is masked with a mask prefix of the
+     * specified length
+     */
+    public static IpAddress makeMaskedAddress(final IpAddress addr,
+                                              int prefixLen) {
+        IpAddress mask = IpAddress.makeMaskPrefix(prefixLen);
+        byte[] net = new byte[INET_BYTE_LENGTH];
+
+        // Mask each byte
+        for (int i = 0; i < INET_BYTE_LENGTH; i++) {
+            net[i] = (byte) (addr.octets[i] & mask.octets[i]);
+        }
+        return IpAddress.valueOf(net);
+    }
+
     @Override
     public int compareTo(IpAddress o) {
         Long lv = ((long) this.toInt()) & 0xffffffffL;
@@ -124,32 +202,20 @@
 
     @Override
     public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result + Arrays.hashCode(octets);
-        result = prime * result + ((version == null) ? 0 : version.hashCode());
-        return result;
+        return Objects.hash(version, Arrays.hashCode(octets));
     }
 
     @Override
     public boolean equals(Object obj) {
-        if (this == obj) {
+        if (obj == this) {
             return true;
         }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
+        if ((obj == null) || (getClass() != obj.getClass())) {
             return false;
         }
         IpAddress other = (IpAddress) obj;
-        if (!Arrays.equals(octets, other.octets)) {
-            return false;
-        }
-        if (version != other.version) {
-            return false;
-        }
-        return true;
+        return (version == other.version) &&
+            Arrays.equals(octets, other.octets);
     }
 
     @Override
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 08e3546..57e247f 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
@@ -19,17 +19,21 @@
 import org.onlab.onos.net.ConnectPoint;
 import org.onlab.onos.net.DefaultAnnotations;
 import org.onlab.onos.net.Device;
+import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.net.Host;
 import org.onlab.onos.net.HostId;
 import org.onlab.onos.net.HostLocation;
 import org.onlab.onos.net.Link;
 import org.onlab.onos.net.MastershipRole;
+import org.onlab.onos.net.Port;
 import org.onlab.onos.net.SparseAnnotations;
 import org.onlab.onos.net.device.DefaultDeviceDescription;
+import org.onlab.onos.net.device.DefaultPortDescription;
 import org.onlab.onos.net.device.DeviceDescription;
 import org.onlab.onos.net.device.DeviceProvider;
 import org.onlab.onos.net.device.DeviceProviderRegistry;
 import org.onlab.onos.net.device.DeviceProviderService;
+import org.onlab.onos.net.device.PortDescription;
 import org.onlab.onos.net.host.DefaultHostDescription;
 import org.onlab.onos.net.host.HostProvider;
 import org.onlab.onos.net.host.HostProviderRegistry;
@@ -45,7 +49,9 @@
 import org.onlab.packet.VlanId;
 
 import java.net.URI;
+import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.List;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.onlab.onos.net.DeviceId.deviceId;
@@ -120,7 +126,30 @@
         DeviceDescription desc =
                 new DefaultDeviceDescription(uri, type, mfr, hw, sw, serial,
                                              cid, annotations);
-        dps.deviceConnected(deviceId(uri), desc);
+        DeviceId deviceId = deviceId(uri);
+        dps.deviceConnected(deviceId, desc);
+
+        JsonNode ports = node.get("ports");
+        if (ports != null) {
+            parsePorts(dps, deviceId, ports);
+        }
+    }
+
+    // Parses the given node with list of device ports.
+    private void parsePorts(DeviceProviderService dps, DeviceId deviceId, JsonNode nodes) {
+        List<PortDescription> ports = new ArrayList<>();
+        for (JsonNode node : nodes) {
+            ports.add(parsePort(node));
+        }
+        dps.updatePorts(deviceId, ports);
+    }
+
+    // Parses the given node with port information.
+    private PortDescription parsePort(JsonNode node) {
+        Port.Type type = Port.Type.valueOf(node.path("type").asText("COPPER"));
+        return new DefaultPortDescription(portNumber(node.path("port").asLong(0)),
+                                          node.path("enabled").asBoolean(true),
+                                          type, node.path("speed").asLong(1_000));
     }
 
     // Parses the given JSON and provides links as configured.
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 ed14353..4b2e93c 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
@@ -18,9 +18,12 @@
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.onos.net.Annotations;
 import org.onlab.onos.net.ConnectPoint;
 import org.onlab.onos.net.Device;
+import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.net.Host;
+import org.onlab.onos.net.HostId;
 import org.onlab.onos.net.HostLocation;
 import org.onlab.onos.net.Link;
 import org.onlab.onos.net.device.DeviceService;
@@ -35,6 +38,7 @@
 import org.onlab.rest.BaseResource;
 
 import javax.ws.rs.GET;
+import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.Response;
 import java.util.HashMap;
@@ -43,12 +47,17 @@
 import java.util.Map;
 import java.util.Set;
 
+import static org.onlab.onos.net.DeviceId.deviceId;
+import static org.onlab.onos.net.HostId.hostId;
+
 /**
  * Topology viewer resource.
  */
 @javax.ws.rs.Path("topology")
 public class TopologyResource extends BaseResource {
 
+    private static final String HOST_SEP = "/";
+
     @javax.ws.rs.Path("/graph")
     @GET
     @Produces("application/json")
@@ -70,6 +79,66 @@
         return Response.ok(rootNode.toString()).build();
     }
 
+    @javax.ws.rs.Path("/graph/{id}")
+    @GET
+    @Produces("application/json")
+    public Response details(@PathParam("id") String id) {
+        if (id.contains(HOST_SEP)) {
+            return hostDetails(hostId(id));
+        }
+        return deviceDetails(deviceId(id));
+    }
+
+    // Returns device details response.
+    private Response deviceDetails(DeviceId deviceId) {
+        DeviceService deviceService = get(DeviceService.class);
+        Device device = deviceService.getDevice(deviceId);
+        Annotations annot = device.annotations();
+        int portCount = deviceService.getPorts(deviceId).size();
+        ObjectNode r = json(deviceId.toString(),
+                            device.type().toString().toLowerCase(),
+                            new Prop("Name", annot.value("name")),
+                            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 Separator(),
+                            new Prop("Latitude", annot.value("latitude")),
+                            new Prop("Longitude", annot.value("longitude")),
+                            new Prop("Ports", Integer.toString(portCount)));
+        return Response.ok(r.toString()).build();
+    }
+
+    // Returns host details response.
+    private Response hostDetails(HostId hostId) {
+        HostService hostService = get(HostService.class);
+        Host host = hostService.getHost(hostId);
+        Annotations annot = host.annotations();
+        ObjectNode r = json(hostId.toString(), "host",
+                            new Prop("MAC", host.mac().toString()),
+                            new Prop("IP", host.ipAddresses().toString()),
+                            new Separator(),
+                            new Prop("Latitude", annot.value("latitude")),
+                            new Prop("Longitude", annot.value("longitude")));
+        return Response.ok(r.toString()).build();
+    }
+
+    // Produces JSON property details.
+    private ObjectNode json(String id, String type, Prop... props) {
+        ObjectMapper mapper = new ObjectMapper();
+        ObjectNode result = mapper.createObjectNode()
+                .put("id", id).put("type", type);
+        ObjectNode pnode = mapper.createObjectNode();
+        ArrayNode porder = mapper.createArrayNode();
+        for (Prop p : props) {
+            porder.add(p.key);
+            pnode.put(p.key, p.value);
+        }
+        result.set("propOrder", porder);
+        result.set("props", pnode);
+        return result;
+    }
+
     // Encodes all infrastructure devices.
     private ArrayNode getDevices(ObjectMapper mapper, DeviceService deviceService,
                                  TopologyGraph graph) {
@@ -209,4 +278,20 @@
         return cp.elementId().toString();
     }
 
+    // Auxiliary key/value carrier.
+    private class Prop {
+        private final String key;
+        private final String value;
+
+        protected Prop(String key, String value) {
+            this.key = key;
+            this.value = value;
+        }
+    }
+
+    private class Separator extends Prop {
+        protected Separator() {
+            super("-", "");
+        }
+    }
 }
diff --git a/web/gui/src/main/webapp/img/onos-logo.png b/web/gui/src/main/webapp/img/onos-logo.png
index 5e0e5a8..b8e0651 100644
--- a/web/gui/src/main/webapp/img/onos-logo.png
+++ b/web/gui/src/main/webapp/img/onos-logo.png
Binary files differ
diff --git a/web/gui/src/main/webapp/img/roadm.png b/web/gui/src/main/webapp/img/roadm.png
new file mode 100644
index 0000000..75ff527
--- /dev/null
+++ b/web/gui/src/main/webapp/img/roadm.png
Binary files differ
diff --git a/web/gui/src/main/webapp/img/switch.png b/web/gui/src/main/webapp/img/switch.png
new file mode 100644
index 0000000..d608153
--- /dev/null
+++ b/web/gui/src/main/webapp/img/switch.png
Binary files differ
diff --git a/web/gui/src/main/webapp/index.html b/web/gui/src/main/webapp/index.html
index 9e91669..337eca7 100644
--- a/web/gui/src/main/webapp/index.html
+++ b/web/gui/src/main/webapp/index.html
@@ -39,7 +39,7 @@
 <body>
     <div id="frame">
         <div id="mast">
-            <img id="logo" src="img/onos-logo.png" width="60" height="38">
+            <img id="logo" src="img/onos-logo.png">
             <span class="title">Open Network Operating System</span>
             <span id="displayModes" class="right">
                 <span id="showAll" class="radio active">All Layers</span>
diff --git a/web/gui/src/main/webapp/json/network.json b/web/gui/src/main/webapp/json/network.json
index eacb01c..b2f6d3a 100644
--- a/web/gui/src/main/webapp/json/network.json
+++ b/web/gui/src/main/webapp/json/network.json
@@ -59,54 +59,72 @@
             "src": "of:0000000000000001",
             "dst": "of:0000000000000002",
             "type": "optical",
+            "srcPort": 1,
+            "dstPort": 2,
             "linkWidth": 1.5
         },
         {
             "src": "of:0000000000000001",
             "dst": "of:0000000000000003",
             "type": "optical",
+            "srcPort": 2,
+            "dstPort": 5,
             "linkWidth": 1.5
         },
         {
             "src": "of:0000000000000001",
             "dst": "of:0000000000000004",
             "type": "optical",
+            "srcPort": 3,
+            "dstPort": 2,
             "linkWidth": 1.5
         },
         {
             "src": "of:0000000000000002",
             "dst": "of:0000000000000003",
             "type": "optical",
+            "srcPort": 3,
+            "dstPort": 4,
             "linkWidth": 1.5
         },
         {
             "src": "of:0000000000000002",
             "dst": "of:0000000000000004",
             "type": "optical",
+            "srcPort": 4,
+            "dstPort": 1,
             "linkWidth": 1.5
         },
         {
             "src": "of:0000000000000003",
             "dst": "of:0000000000000004",
             "type": "optical",
+            "srcPort": 3,
+            "dstPort": 3,
             "linkWidth": 1.5
         },
         {
             "src": "of:0000000000000013",
             "dst": "of:0000000000000003",
             "type": "direct",
+            "srcPort": 1,
+            "dstPort": 7,
             "linkWidth": 1.0
         },
         {
             "src": "of:0000000000000012",
             "dst": "of:0000000000000002",
             "type": "direct",
+            "srcPort": 1,
+            "dstPort": 9,
             "linkWidth": 1.0
         },
         {
             "src": "of:0000000000000011",
             "dst": "of:0000000000000001",
             "type": "direct",
+            "srcPort": 1,
+            "dstPort": 6,
             "linkWidth": 1.0
         }
     ],
diff --git a/web/gui/src/main/webapp/json/of_0000000000000001.json b/web/gui/src/main/webapp/json/of_0000000000000001.json
index c484812..719af80 100644
--- a/web/gui/src/main/webapp/json/of_0000000000000001.json
+++ b/web/gui/src/main/webapp/json/of_0000000000000001.json
@@ -1,13 +1,13 @@
 {
     "comment": "sample device properties",
     "id": "of:0000000000000001",
-    "propOrder": [ "name", "type", "dpid", "latitude", "longitude", "allowed" ],
+    "type": "roadm",
+    "propOrder": [ "name", "type", "-", "dpid", "latitude", "longitude", "allowed" ],
     "props": {
         "allowed": true,
         "latitude": 37.6,
         "longitude": 122.3,
         "name": "SFO-W10",
-        "dpid": "00:00:00:00:00:00:00:01",
-        "type": "Roadm"
+        "dpid": "00:00:00:00:00:00:00:01"
     }
 }
diff --git a/web/gui/src/main/webapp/json/of_0000000000000002.json b/web/gui/src/main/webapp/json/of_0000000000000002.json
index 049329d..a6b0e7c 100644
--- a/web/gui/src/main/webapp/json/of_0000000000000002.json
+++ b/web/gui/src/main/webapp/json/of_0000000000000002.json
@@ -1,6 +1,7 @@
 {
     "comment": "sample device properties",
     "id": "of:0000000000000002",
+    "type": "switch",
     "propOrder": [ "name", "type", "dpid", "latitude", "longitude", "allowed" ],
     "props": {
         "allowed": true,
diff --git a/web/gui/src/main/webapp/json/of_0000000000000003.json b/web/gui/src/main/webapp/json/of_0000000000000003.json
index 8561cf4..9fd2790 100644
--- a/web/gui/src/main/webapp/json/of_0000000000000003.json
+++ b/web/gui/src/main/webapp/json/of_0000000000000003.json
@@ -1,6 +1,7 @@
 {
     "comment": "sample device properties",
     "id": "of:0000000000000003",
+    "type": "switch",
     "propOrder": [ "name", "type", "dpid", "latitude", "longitude", "allowed" ],
     "props": {
         "allowed": true,
diff --git a/web/gui/src/main/webapp/json/of_0000000000000004.json b/web/gui/src/main/webapp/json/of_0000000000000004.json
index eb03114..f3f2132 100644
--- a/web/gui/src/main/webapp/json/of_0000000000000004.json
+++ b/web/gui/src/main/webapp/json/of_0000000000000004.json
@@ -1,6 +1,7 @@
 {
     "comment": "sample device properties",
     "id": "of:0000000000000004",
+    "type": "switch",
     "propOrder": [ "name", "type", "dpid", "latitude", "longitude", "allowed" ],
     "props": {
         "allowed": true,
diff --git a/web/gui/src/main/webapp/json/of_0000000000000011.json b/web/gui/src/main/webapp/json/of_0000000000000011.json
index 0e73a57..5792dab 100644
--- a/web/gui/src/main/webapp/json/of_0000000000000011.json
+++ b/web/gui/src/main/webapp/json/of_0000000000000011.json
@@ -1,6 +1,7 @@
 {
     "comment": "sample device properties",
     "id": "of:0000000000000011",
+    "type": "switch",
     "propOrder": [ "name", "type", "dpid", "optLink" ],
     "props": {
         "name": "SFO-pkt",
diff --git a/web/gui/src/main/webapp/json/of_0000000000000012.json b/web/gui/src/main/webapp/json/of_0000000000000012.json
index 2a00e0c..b65163e 100644
--- a/web/gui/src/main/webapp/json/of_0000000000000012.json
+++ b/web/gui/src/main/webapp/json/of_0000000000000012.json
@@ -1,6 +1,7 @@
 {
     "comment": "sample device properties",
     "id": "of:0000000000000012",
+    "type": "switch",
     "propOrder": [ "name", "type", "dpid", "optLink" ],
     "props": {
         "name": "SJC-pkt",
diff --git a/web/gui/src/main/webapp/json/of_0000000000000013.json b/web/gui/src/main/webapp/json/of_0000000000000013.json
index b71738a..ba96b07 100644
--- a/web/gui/src/main/webapp/json/of_0000000000000013.json
+++ b/web/gui/src/main/webapp/json/of_0000000000000013.json
@@ -1,6 +1,7 @@
 {
     "comment": "sample device properties",
     "id": "of:0000000000000013",
+    "type": "switch",
     "propOrder": [ "name", "type", "dpid", "optLink" ],
     "props": {
         "name": "LAX-pkt",
diff --git a/web/gui/src/main/webapp/network.js b/web/gui/src/main/webapp/network.js
index 4f8f76e..578aa3a 100644
--- a/web/gui/src/main/webapp/network.js
+++ b/web/gui/src/main/webapp/network.js
@@ -37,9 +37,10 @@
                 layering: true,
                 collisionPrevention: true
             },
-            XjsonUrl: 'rs/topology/graph',
-            jsonUrl: 'json/network.json',
-            jsonPrefix: 'json/',
+            jsonUrl: 'rs/topology/graph',
+            jsonPrefix: '',
+            XjsonUrl: 'json/network.json',
+            XjsonPrefix: 'json/',
             iconUrl: {
                 device: 'img/device.png',
                 host: 'img/host.png',
@@ -238,6 +239,9 @@
     function processKeyEvent() {
         var code = d3.event.keyCode;
         switch (code) {
+            case 71:    // G
+                cycleLayout();
+                break;
             case 76:    // L
                 cycleLabels();
                 break;
@@ -251,6 +255,11 @@
 
     }
 
+    function cycleLayout() {
+        config.options.layering = !config.options.layering;
+        network.force.resume();
+    }
+
     function cycleLabels() {
         console.log('Cycle Labels - context = ' + contextLabel());
     }
@@ -688,7 +697,8 @@
     }
 
     function iconUrl(d) {
-        return config.iconUrl[d.icon];
+        return 'img/' + d.type + '.png';
+//        return config.iconUrl[d.icon];
     }
 
     function translate(x, y) {
@@ -889,8 +899,11 @@
     }
 
     function detailUrl(id) {
-        var safeId = id.replace(/[^a-z0-9]/gi, '_');
-        return config.jsonPrefix + safeId + '.json';
+        if (config.jsonPrefix) {
+            var safeId = id.replace(/[^a-z0-9]/gi, '_');
+            return config.jsonPrefix + safeId + '.json';
+        }
+        return config.jsonUrl + '/' + encodeURIComponent(id);
     }
 
     function flyinPane(obj) {
@@ -924,17 +937,29 @@
     function displayDetails(data, pane) {
         $('#flyout').empty();
 
-        pane.append('h2').text(data.id);
-
-        var table = pane.append("table"),
+        var title = pane.append("h2"),
+            table = pane.append("table"),
             tbody = table.append("tbody");
 
+        $('<img src="img/' + data.type + '.png">').appendTo(title);
+        $('<span>').attr('class', 'icon').text(data.id).appendTo(title);
+
+
         // TODO: consider using d3 data bind to TR/TD
 
         data.propOrder.forEach(function(p) {
-            addProp(tbody, p, data.props[p]);
+            if (p === '-') {
+                addSep(tbody);
+            } else {
+                addProp(tbody, p, data.props[p]);
+            }
         });
 
+        function addSep(tbody) {
+            var tr = tbody.append('tr');
+            $('<hr>').appendTo(tr.append('td').attr('colspan', 2));
+        }
+
         function addProp(tbody, label, value) {
             var tr = tbody.append('tr');
 
diff --git a/web/gui/src/main/webapp/onos.css b/web/gui/src/main/webapp/onos.css
index 06e5b63..3c1fb07 100644
--- a/web/gui/src/main/webapp/onos.css
+++ b/web/gui/src/main/webapp/onos.css
@@ -28,11 +28,17 @@
  * Classes
  */
 
+img#logo {
+    height: 38px;
+    padding-left: 8px;
+    padding-right: 8px;
+}
+
 span.title {
-    color: #37b;
+    color: #369;
     font-size: 14pt;
     font-style: italic;
-    vertical-align: 10px;
+    vertical-align: 12px;
 }
 
 span.radio {
@@ -41,6 +47,8 @@
 }
 
 span.right {
+    padding-top: 8px;
+    padding-right: 16px;
     float: right;
 }
 
@@ -89,14 +97,13 @@
 }
 
 svg .link.host {
-    stroke: #6a6;
-    stroke-dasharray: 3,3;
+    stroke: #666;
+    stroke-width: 1px;
+    Xstroke-dasharray: 3,3;
 }
 
 svg .node.device rect {
-    stroke-width: 3.0px;
-    stroke: white;
-    stroke-dasharray: 2,2;
+    stroke-width: 1.5px;
 
     transition: opacity 250ms;
     -webkit-transition: opacity 250ms;
@@ -104,19 +111,21 @@
 }
 
 svg .node.device.fixed rect {
-    stroke-width: 0;
+    stroke-width: 1.5;
+    stroke: #ccc;
+    Xstroke-dasharray: 4,2;
 }
 
 svg .node.device.roadm rect {
-    fill: #229;
+    fill: #03c;
 }
 
 svg .node.device.switch rect {
-    fill: #55f;
+    fill: #06f;
 }
 
 svg .node.host circle {
-    fill: #898;
+    fill: #c96;
     stroke: #000;
 }
 
@@ -148,7 +157,7 @@
 svg .node.inactive circle,
 svg .node.inactive text,
 svg .node.inactive image {
-    opacity: .05;
+    opacity: .1;
 }
 
 svg .node.inactive.selected rect,
@@ -199,8 +208,9 @@
 #mast {
     height: 36px;
     padding: 4px;
-    background-color: #ccc;
+    background-color: #bbb;
     vertical-align: baseline;
+    box-shadow: 0px 2px 8px #777;
 }
 
 #frame {
@@ -214,19 +224,27 @@
     z-index: 100;
     display: block;
     top: 10%;
-    width: 300px;
-    height: 80%;
-    right: -320px;
+    width: 280px;
+    right: -300px;
     opacity: 0;
-    background-color: rgba(0,0,0,0.5);
+    background-color: rgba(255,255,255,0.5);
+
     padding: 10px;
-    color: white;
+    color: black;
     font-size: 10pt;
+    box-shadow: 2px 2px 16px #777;
 }
 
 #flyout h2 {
     margin: 8px 4px;
-    color: yellow;
+    color: black;
+    vertical-align: middle;
+}
+
+#flyout h2 img {
+    height: 32px;
+    padding-right: 8px;
+    vertical-align: middle;
 }
 
 #flyout p, table {
@@ -235,7 +253,7 @@
 
 #flyout td.label {
     font-style: italic;
-    color: #ccf;
+    color: #777;
     padding-right: 12px;
 }
 
@@ -243,3 +261,10 @@
 
 }
 
+#flyout hr {
+    height: 1px;
+    color: #ccc;
+    background-color: #ccc;
+    border: 0;
+}
+