Added unit tests for the event abstractions.
Added Element as the notion of common ancestry between Device and Host.
diff --git a/net/api/src/main/java/org/onlab/onos/event/AbstractListenerManager.java b/net/api/src/main/java/org/onlab/onos/event/AbstractListenerManager.java
index 7d4836f..d063b59 100644
--- a/net/api/src/main/java/org/onlab/onos/event/AbstractListenerManager.java
+++ b/net/api/src/main/java/org/onlab/onos/event/AbstractListenerManager.java
@@ -51,8 +51,13 @@
         }
     }
 
-    @Override
-    public void reportProblem(E event, Throwable error) {
+    /**
+     * Reports a problem encountered while processing an event.
+     *
+     * @param event event being processed
+     * @param error error encountered while processing
+     */
+    protected void reportProblem(E event, Throwable error) {
         log.warn("Exception encountered while processing event " + event, error);
     }
 
diff --git a/net/api/src/main/java/org/onlab/onos/event/AbstractEventSinkBroker.java b/net/api/src/main/java/org/onlab/onos/event/DefaultEventSinkBroker.java
similarity index 91%
rename from net/api/src/main/java/org/onlab/onos/event/AbstractEventSinkBroker.java
rename to net/api/src/main/java/org/onlab/onos/event/DefaultEventSinkBroker.java
index 4ca3c3c..c791544 100644
--- a/net/api/src/main/java/org/onlab/onos/event/AbstractEventSinkBroker.java
+++ b/net/api/src/main/java/org/onlab/onos/event/DefaultEventSinkBroker.java
@@ -12,7 +12,7 @@
 /**
  * Base implementation of event sink broker.
  */
-public class AbstractEventSinkBroker implements EventSinkBroker {
+public class DefaultEventSinkBroker implements EventSinkBroker {
 
     private final Map<Class<? extends Event>, EventSink<? extends Event>> sinks =
             new ConcurrentHashMap<>();
@@ -36,6 +36,7 @@
     @Override
     @SuppressWarnings("unchecked")
     public <E extends Event> EventSink<E> getSink(Class<E> eventClass) {
+        // TODO: add implicit registration of descendant classes
         return (EventSink<E>) sinks.get(eventClass);
     }
 
diff --git a/net/api/src/main/java/org/onlab/onos/event/EventSink.java b/net/api/src/main/java/org/onlab/onos/event/EventSink.java
index f46458c..45ad408 100644
--- a/net/api/src/main/java/org/onlab/onos/event/EventSink.java
+++ b/net/api/src/main/java/org/onlab/onos/event/EventSink.java
@@ -12,12 +12,4 @@
      */
     void process(E event);
 
-    /**
-     * Reports a problem encountered while processing an event.
-     *
-     * @param event event being processed
-     * @param error error encountered while processing
-     */
-    void reportProblem(E event, Throwable error);
-
 }
diff --git a/net/api/src/main/java/org/onlab/onos/net/Device.java b/net/api/src/main/java/org/onlab/onos/net/Device.java
index 700654f..9e6018e 100644
--- a/net/api/src/main/java/org/onlab/onos/net/Device.java
+++ b/net/api/src/main/java/org/onlab/onos/net/Device.java
@@ -1,11 +1,9 @@
 package org.onlab.onos.net;
 
-import org.onlab.onos.net.provider.Provided;
-
 /**
  * Representation of a network infrastructure device.
  */
-public interface Device extends Provided {
+public interface Device extends Element {
 
     /**
      * Coarse classification of the type of the infrastructure device.
diff --git a/net/api/src/main/java/org/onlab/onos/net/DeviceId.java b/net/api/src/main/java/org/onlab/onos/net/DeviceId.java
index 45528a4..124fa96 100644
--- a/net/api/src/main/java/org/onlab/onos/net/DeviceId.java
+++ b/net/api/src/main/java/org/onlab/onos/net/DeviceId.java
@@ -1,49 +1,19 @@
 package org.onlab.onos.net;
 
 import java.net.URI;
-import java.util.Objects;
-
-import static com.google.common.base.Objects.toStringHelper;
 
 /**
  * Immutable representation of a device identity.
  */
-public class DeviceId {
-
-    private final URI uri;
-
-    public DeviceId(URI uri) {
-        this.uri = uri;
-    }
+public class DeviceId extends ElementId {
 
     /**
-     * Returns the backing URI.
+     * Creates a device id using the supplied URI.
      *
-     * @return backing device URI
+     * @param uri backing device URI
      */
-    public URI uri() {
-        return uri;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(uri);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null || getClass() != obj.getClass()) {
-            return false;
-        }
-        final DeviceId other = (DeviceId) obj;
-        return Objects.equals(this.uri, other.uri);
-    }
-    @Override
-    public String toString() {
-        return toStringHelper(this).add("uri", uri).toString();
+    public DeviceId(URI uri) {
+        super(uri);
     }
 
 }
diff --git a/net/api/src/main/java/org/onlab/onos/net/Element.java b/net/api/src/main/java/org/onlab/onos/net/Element.java
new file mode 100644
index 0000000..c11d103
--- /dev/null
+++ b/net/api/src/main/java/org/onlab/onos/net/Element.java
@@ -0,0 +1,17 @@
+package org.onlab.onos.net;
+
+import org.onlab.onos.net.provider.Provided;
+
+/**
+ * Base abstraction of a network element, i.e. an infrastructure device or an end-station host.
+ */
+public interface Element extends Provided {
+
+    /**
+     * Returns the network element identifier.
+     *
+     * @return element identifier
+     */
+    ElementId id();
+
+}
diff --git a/net/api/src/main/java/org/onlab/onos/net/ElementId.java b/net/api/src/main/java/org/onlab/onos/net/ElementId.java
new file mode 100644
index 0000000..e0f3add
--- /dev/null
+++ b/net/api/src/main/java/org/onlab/onos/net/ElementId.java
@@ -0,0 +1,55 @@
+package org.onlab.onos.net;
+
+import java.net.URI;
+import java.util.Objects;
+
+import static com.google.common.base.Objects.toStringHelper;
+
+/**
+ * Immutable representation of a network element identity.
+ */
+public class ElementId {
+
+    private final URI uri;
+
+    /**
+     * Creates an element identifier using the supplied URI.
+     *
+     * @param uri backing URI
+     */
+    public ElementId(URI uri) {
+        this.uri = uri;
+    }
+
+    /**
+     * Returns the backing URI.
+     *
+     * @return backing URI
+     */
+    public URI uri() {
+        return uri;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(uri);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        final ElementId other = (ElementId) obj;
+        return Objects.equals(this.uri, other.uri);
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this).add("uri", uri).toString();
+    }
+
+}
diff --git a/net/api/src/main/java/org/onlab/onos/net/Host.java b/net/api/src/main/java/org/onlab/onos/net/Host.java
index f8e2e31..17eaf22 100644
--- a/net/api/src/main/java/org/onlab/onos/net/Host.java
+++ b/net/api/src/main/java/org/onlab/onos/net/Host.java
@@ -1,14 +1,21 @@
 package org.onlab.onos.net;
 
-import org.onlab.onos.net.provider.Provided;
-
 /**
  * Abstraction of an end-station host on the network, essentially a NIC.
  */
-public interface Host extends Provided {
+public interface Host extends Element {
 
     // MAC, IP(s), optional VLAN ID
 
-    // Location (current, recent locations?
+
+    /**
+     * Returns the most recent host location where the host attaches to the
+     * network edge.
+     *
+     * @return host location
+     */
+    HostLocation location();
+
+    // list of recent locations?
 
 }
diff --git a/net/api/src/main/java/org/onlab/onos/net/HostLocation.java b/net/api/src/main/java/org/onlab/onos/net/HostLocation.java
new file mode 100644
index 0000000..5c124db
--- /dev/null
+++ b/net/api/src/main/java/org/onlab/onos/net/HostLocation.java
@@ -0,0 +1,17 @@
+package org.onlab.onos.net;
+
+/**
+ * Representation of a network edge location where an end-station host is
+ * connected.
+ */
+public interface HostLocation extends ConnectPoint {
+
+    /**
+     * Returns the timestamp when the location was established, given in
+     * milliseconds since start of epoch.
+     *
+     * @return timestamp in milliseconds since start of epoch
+     */
+    long timestamp();
+
+}
diff --git a/net/api/src/main/java/org/onlab/onos/net/Port.java b/net/api/src/main/java/org/onlab/onos/net/Port.java
new file mode 100644
index 0000000..3135b77
--- /dev/null
+++ b/net/api/src/main/java/org/onlab/onos/net/Port.java
@@ -0,0 +1,26 @@
+package org.onlab.onos.net;
+
+/**
+ * Abstraction of a network port.
+ */
+public interface Port {
+
+    // Notion of port state: enabled, disabled, blocked
+
+    /**
+     * Returns the port number.
+     *
+     * @return port number
+     */
+    PortNumber number();
+
+    /**
+     * Returns the identifier of the network element to which this port belongs.
+     *
+     * @return parent network element
+     */
+    Element parent();
+
+    // set of port attributes
+
+}
diff --git a/net/api/src/main/java/org/onlab/onos/net/device/DeviceService.java b/net/api/src/main/java/org/onlab/onos/net/device/DeviceService.java
index 4048fbb..ac2f4a4 100644
--- a/net/api/src/main/java/org/onlab/onos/net/device/DeviceService.java
+++ b/net/api/src/main/java/org/onlab/onos/net/device/DeviceService.java
@@ -3,6 +3,10 @@
 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 java.util.List;
 
 /**
  * Service for interacting with the inventory of infrastructure devices.
@@ -34,7 +38,21 @@
     Device getDevice(DeviceId deviceId);
 
 
-//    List<Port> getPorts(DeviceId deviceId);
+    /**
+     * Returns the list of ports associated with the device.
+     *
+     * @param deviceId device identifier
+     * @return list of ports
+     */
+    List<Port> getPorts(DeviceId deviceId);
+
+    /**
+     * Returns the port with the specified number and hosted by the given device.
+     * @param deviceId device identifier
+     * @param portNumber port number
+     * @return device port
+     */
+    Port getPort(DeviceId deviceId, PortNumber portNumber);
 
     /**
      * Adds the specified device listener.
@@ -49,4 +67,5 @@
      * @param listener device listener
      */
     void removeListener(DeviceListener listener);
+
 }
diff --git a/net/api/src/test/java/org/onlab/onos/event/AbstractEventTest.java b/net/api/src/test/java/org/onlab/onos/event/AbstractEventTest.java
new file mode 100644
index 0000000..5ec3669
--- /dev/null
+++ b/net/api/src/test/java/org/onlab/onos/event/AbstractEventTest.java
@@ -0,0 +1,31 @@
+package org.onlab.onos.event;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.onlab.onos.event.TestEvent.Type.FOO;
+
+/**
+ * Tests of the base event abstraction.
+ */
+public class AbstractEventTest {
+
+    @Test
+    public void withTime() {
+        TestEvent event = new TestEvent(FOO, "foo", 123L);
+        assertEquals("incorrect type", FOO, event.type());
+        assertEquals("incorrect subject", "foo", event.subject());
+        assertEquals("incorrect time", 123L, event.time());
+    }
+
+    @Test
+    public void withoutTime() {
+        long before = System.currentTimeMillis();
+        TestEvent event = new TestEvent(FOO, "foo");
+        long after = System.currentTimeMillis();
+        assertEquals("incorrect type", FOO, event.type());
+        assertEquals("incorrect subject", "foo", event.subject());
+        assertTrue("incorrect time", before <= event.time() && event.time() <= after);
+    }
+}
diff --git a/net/api/src/test/java/org/onlab/onos/event/AbstractListenerManagerTest.java b/net/api/src/test/java/org/onlab/onos/event/AbstractListenerManagerTest.java
new file mode 100644
index 0000000..effcc79
--- /dev/null
+++ b/net/api/src/test/java/org/onlab/onos/event/AbstractListenerManagerTest.java
@@ -0,0 +1,49 @@
+package org.onlab.onos.event;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests of the base listener manager.
+ */
+public class AbstractListenerManagerTest {
+
+    @Test
+    public void basics() {
+        TestListener listener = new TestListener();
+        TestListener secondListener = new TestListener();
+        TestListenerManager manager = new TestListenerManager();
+        manager.addListener(listener);
+        manager.addListener(secondListener);
+
+        TestEvent event = new TestEvent(TestEvent.Type.BAR, "bar");
+        manager.process(event);
+        assertTrue("event not processed", listener.events.contains(event));
+        assertTrue("event not processed", secondListener.events.contains(event));
+
+        manager.removeListener(listener);
+
+        TestEvent another = new TestEvent(TestEvent.Type.FOO, "foo");
+        manager.process(another);
+        assertFalse("event processed", listener.events.contains(another));
+        assertTrue("event not processed", secondListener.events.contains(event));
+    }
+
+    @Test
+    public void badListener() {
+        TestListener listener = new BrokenListener();
+        TestListener secondListener = new TestListener();
+        TestListenerManager manager = new TestListenerManager();
+        manager.addListener(listener);
+        manager.addListener(secondListener);
+
+        TestEvent event = new TestEvent(TestEvent.Type.BAR, "bar");
+        manager.process(event);
+        assertFalse("event processed", listener.events.contains(event));
+        assertFalse("error not reported", manager.errors.isEmpty());
+        assertTrue("event not processed", secondListener.events.contains(event));
+    }
+
+}
diff --git a/net/api/src/test/java/org/onlab/onos/event/BrokenListener.java b/net/api/src/test/java/org/onlab/onos/event/BrokenListener.java
new file mode 100644
index 0000000..dc83419
--- /dev/null
+++ b/net/api/src/test/java/org/onlab/onos/event/BrokenListener.java
@@ -0,0 +1,13 @@
+package org.onlab.onos.event;
+
+/**
+ * Test event listener fixture.
+ */
+public class BrokenListener extends TestListener {
+
+    public void event(TestEvent event) {
+        throw new IllegalStateException("boom");
+    }
+
+}
+
diff --git a/net/api/src/test/java/org/onlab/onos/event/DefaultEventSinkBrokerTest.java b/net/api/src/test/java/org/onlab/onos/event/DefaultEventSinkBrokerTest.java
new file mode 100644
index 0000000..f676325
--- /dev/null
+++ b/net/api/src/test/java/org/onlab/onos/event/DefaultEventSinkBrokerTest.java
@@ -0,0 +1,52 @@
+package org.onlab.onos.event;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests of the default event sink broker.
+ */
+public class DefaultEventSinkBrokerTest {
+
+    private DefaultEventSinkBroker broker;
+
+    private static class FooEvent extends TestEvent {
+        public FooEvent(String subject) { super(Type.FOO, subject); }
+    }
+
+    private static class BarEvent extends TestEvent {
+        public BarEvent(String subject) { super(Type.BAR, subject); }
+    }
+
+    private static class FooSink implements EventSink<FooEvent> {
+        @Override public void process(FooEvent event) {}
+    }
+
+    private static class BarSink implements EventSink<BarEvent> {
+        @Override public void process(BarEvent event) {}
+    }
+
+    @Before
+    public void setUp() {
+        broker = new DefaultEventSinkBroker();
+    }
+
+    @Test
+    public void basics() {
+        FooSink fooSink = new FooSink();
+        BarSink barSink = new BarSink();
+        broker.addSink(FooEvent.class, fooSink);
+        broker.addSink(BarEvent.class, barSink);
+
+        assertEquals("incorrect sink count", 2, broker.getSinks().size());
+        assertEquals("incorrect sink", fooSink, broker.getSink(FooEvent.class));
+        assertEquals("incorrect sink", barSink, broker.getSink(BarEvent.class));
+
+        broker.removeSink(FooEvent.class);
+        assertNull("incorrect sink", broker.getSink(FooEvent.class));
+        assertEquals("incorrect sink", barSink, broker.getSink(BarEvent.class));
+
+    }
+}
diff --git a/net/api/src/test/java/org/onlab/onos/event/TestEvent.java b/net/api/src/test/java/org/onlab/onos/event/TestEvent.java
new file mode 100644
index 0000000..25c8f46
--- /dev/null
+++ b/net/api/src/test/java/org/onlab/onos/event/TestEvent.java
@@ -0,0 +1,19 @@
+package org.onlab.onos.event;
+
+/**
+ * Test event fixture.
+ */
+public class TestEvent extends AbstractEvent<TestEvent.Type, String> {
+
+    public enum Type { FOO, BAR };
+
+    public TestEvent(Type type, String subject) {
+        super(type, subject);
+    }
+
+    public TestEvent(Type type, String subject, long timestamp) {
+        super(type, subject, timestamp);
+    }
+
+}
+
diff --git a/net/api/src/test/java/org/onlab/onos/event/TestListener.java b/net/api/src/test/java/org/onlab/onos/event/TestListener.java
new file mode 100644
index 0000000..ec2185a
--- /dev/null
+++ b/net/api/src/test/java/org/onlab/onos/event/TestListener.java
@@ -0,0 +1,19 @@
+package org.onlab.onos.event;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test event listener fixture.
+ */
+public class TestListener implements EventListener<TestEvent> {
+
+    public final List<TestEvent> events = new ArrayList<>();
+
+    @Override
+    public void event(TestEvent event) {
+        events.add(event);
+    }
+
+}
+
diff --git a/net/api/src/test/java/org/onlab/onos/event/TestListenerManager.java b/net/api/src/test/java/org/onlab/onos/event/TestListenerManager.java
new file mode 100644
index 0000000..a9f6213
--- /dev/null
+++ b/net/api/src/test/java/org/onlab/onos/event/TestListenerManager.java
@@ -0,0 +1,21 @@
+package org.onlab.onos.event;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test event listener manager fixture.
+ */
+public class TestListenerManager
+        extends AbstractListenerManager<TestEvent, TestListener> {
+
+    public final List<Throwable> errors = new ArrayList<>();
+
+    @Override
+    protected void reportProblem(TestEvent event, Throwable error) {
+        super.reportProblem(event, error);
+        errors.add(error);
+    }
+
+}
+