Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next
diff --git a/net/api/src/main/java/org/onlab/onos/net/DefaultEdgeLink.java b/net/api/src/main/java/org/onlab/onos/net/DefaultEdgeLink.java
new file mode 100644
index 0000000..fe2d34a
--- /dev/null
+++ b/net/api/src/main/java/org/onlab/onos/net/DefaultEdgeLink.java
@@ -0,0 +1,43 @@
+package org.onlab.onos.net;
+
+import org.onlab.onos.net.provider.ProviderId;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Default edge link model implementation.
+ */
+public class DefaultEdgeLink extends DefaultLink implements EdgeLink {
+
+    private final HostId hostId;
+    private final HostLocation hostLocation;
+
+    /**
+     * Creates an edge link using the supplied information.
+     *
+     * @param providerId   provider identity
+     * @param hostPoint    host-side connection point
+     * @param hostLocation location where host attaches to the network
+     * @param isIngress    true to indicated host-to-network direction; false
+     *                     for network-to-host direction
+     */
+    public DefaultEdgeLink(ProviderId providerId, ConnectPoint hostPoint,
+                           HostLocation hostLocation, boolean isIngress) {
+        super(providerId, isIngress ? hostLocation : hostPoint,
+              isIngress ? hostPoint : hostLocation, Type.EDGE);
+        checkArgument(hostPoint.elementId() instanceof HostId,
+                      "Host point does not refer to a host ID");
+        this.hostId = (HostId) hostPoint.elementId();
+        this.hostLocation = hostLocation;
+    }
+
+    @Override
+    public HostId hostId() {
+        return hostId;
+    }
+
+    @Override
+    public ConnectPoint connectPoint() {
+        return hostLocation;
+    }
+}
diff --git a/net/api/src/main/java/org/onlab/onos/net/HostLinks.java b/net/api/src/main/java/org/onlab/onos/net/EdgeLink.java
similarity index 86%
rename from net/api/src/main/java/org/onlab/onos/net/HostLinks.java
rename to net/api/src/main/java/org/onlab/onos/net/EdgeLink.java
index eabbeb1..356592d3 100644
--- a/net/api/src/main/java/org/onlab/onos/net/HostLinks.java
+++ b/net/api/src/main/java/org/onlab/onos/net/EdgeLink.java
@@ -4,14 +4,14 @@
  * Abstraction of a link between an end-station host and the network
  * infrastructure.
  */
-public interface HostLinks extends Link {
+public interface EdgeLink extends Link {
 
     /**
      * Returns the host identification.
      *
      * @return host identifier
      */
-    ElementId hostId();
+    HostId hostId();
 
     /**
      * Returns the connection point where the host attaches to the
diff --git a/net/api/src/main/java/org/onlab/onos/net/HostId.java b/net/api/src/main/java/org/onlab/onos/net/HostId.java
new file mode 100644
index 0000000..a7f550b
--- /dev/null
+++ b/net/api/src/main/java/org/onlab/onos/net/HostId.java
@@ -0,0 +1,33 @@
+package org.onlab.onos.net;
+
+import java.net.URI;
+
+/**
+ * Immutable representation of a host identity.
+ */
+public final class HostId extends ElementId {
+
+    // Public construction is prohibited
+    private HostId(URI uri) {
+        super(uri);
+    }
+
+    /**
+     * Creates a device id using the supplied URI.
+     *
+     * @param uri device URI
+     */
+    public static HostId hostId(URI uri) {
+        return new HostId(uri);
+    }
+
+    /**
+     * Creates a device id using the supplied URI string.
+     *
+     * @param string device URI string
+     */
+    public static HostId hostId(String string) {
+        return new HostId(URI.create(string));
+    }
+
+}
diff --git a/net/api/src/main/java/org/onlab/onos/net/Link.java b/net/api/src/main/java/org/onlab/onos/net/Link.java
index 0f4275e..0b60b9c 100644
--- a/net/api/src/main/java/org/onlab/onos/net/Link.java
+++ b/net/api/src/main/java/org/onlab/onos/net/Link.java
@@ -20,7 +20,12 @@
          * links traversing optical paths, tunnels or intervening 'dark'
          * switches.
          */
-        INDIRECT
+        INDIRECT,
+
+        /**
+         * Signifies that this link is an edge, i.e. host link.
+         */
+        EDGE
     }
 
     /**
diff --git a/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceManager.java b/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceManager.java
index e522064..ac84e7b 100644
--- a/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceManager.java
+++ b/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceManager.java
@@ -27,8 +27,11 @@
 import org.slf4j.Logger;
 
 import java.util.List;
+import java.util.concurrent.ExecutorService;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.namedThreads;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -53,6 +56,9 @@
 
     private final SimpleDeviceStore store = new SimpleDeviceStore();
 
+    private final ExecutorService executor =
+            newSingleThreadExecutor(namedThreads("onos-device-%d"));
+
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected EventDeliveryService eventDispatcher;
 
@@ -163,6 +169,11 @@
             DeviceEvent event = store.createOrUpdateDevice(provider().id(),
                                                            deviceId, deviceDescription);
             post(event);
+
+            // If there was a change of any kind, trigger role selection process.
+            if (event != null) {
+                triggerRoleSelection(event.subject(), provider());
+            }
         }
 
         @Override
@@ -199,6 +210,22 @@
         }
     }
 
+    /**
+     * Triggers asynchronous role selection.
+     *
+     * @param device   device
+     * @param provider device provider
+     */
+    private void triggerRoleSelection(final Device device,
+                                      final DeviceProvider provider) {
+        executor.execute(new Runnable() {
+            @Override
+            public void run() {
+                provider.roleChanged(device, store.getRole(device.id()));
+            }
+        });
+    }
+
     // Posts the specified event to the local event dispatcher.
     private void post(DeviceEvent event) {
         if (event != null && eventDispatcher != null) {
diff --git a/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceStore.java b/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceStore.java
index fdffad2..e219a63 100644
--- a/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceStore.java
+++ b/net/core/trivial/src/main/java/org/onlab/onos/net/trivial/impl/SimpleDeviceStore.java
@@ -96,6 +96,9 @@
         synchronized (this) {
             devices.put(deviceId, device);
             availableDevices.add(deviceId);
+
+            // For now claim the device as a master automatically.
+            roles.put(deviceId, MastershipRole.MASTER);
         }
         return new DeviceEvent(DeviceEvent.Type.DEVICE_ADDED, device, null);
     }
diff --git a/net/core/trivial/src/test/java/org/onlab/onos/net/trivial/impl/SimpleDeviceManagerTest.java b/net/core/trivial/src/test/java/org/onlab/onos/net/trivial/impl/SimpleDeviceManagerTest.java
index e5224db..5bc60c0 100644
--- a/net/core/trivial/src/test/java/org/onlab/onos/net/trivial/impl/SimpleDeviceManagerTest.java
+++ b/net/core/trivial/src/test/java/org/onlab/onos/net/trivial/impl/SimpleDeviceManagerTest.java
@@ -139,17 +139,17 @@
     @Test
     public void getRole() {
         connectDevice(DID1, SW1);
-        assertEquals("incorrect role", MastershipRole.NONE, service.getRole(DID1));
+        assertEquals("incorrect role", MastershipRole.MASTER, service.getRole(DID1));
     }
 
     @Test
     public void setRole() {
         connectDevice(DID1, SW1);
-        admin.setRole(DID1, MastershipRole.MASTER);
+        admin.setRole(DID1, MastershipRole.STANDBY);
         validateEvents(DEVICE_ADDED, DEVICE_MASTERSHIP_CHANGED);
-        assertEquals("incorrect role", MastershipRole.MASTER, service.getRole(DID1));
+        assertEquals("incorrect role", MastershipRole.STANDBY, service.getRole(DID1));
         assertEquals("incorrect device", DID1, provider.deviceReceived.id());
-        assertEquals("incorrect role", MastershipRole.MASTER, provider.roleReceived);
+        assertEquals("incorrect role", MastershipRole.STANDBY, provider.roleReceived);
     }
 
     @Test