Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next

Conflicts:
	core/store/hz/net/src/main/java/org/onlab/onos/store/device/impl/DistributedDeviceStore.java
	core/store/hz/net/src/test/java/org/onlab/onos/store/device/impl/DistributedDeviceStoreTest.java
	features/features.xml
	tools/test/cells/office

Change-Id: I08e6d7c6a0bdaae072dd50ff7ac36d94f16d77e1
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/IntentPushTestCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/IntentPushTestCommand.java
index 60181bd..4c3ed8e 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/IntentPushTestCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/IntentPushTestCommand.java
@@ -90,11 +90,15 @@
             service.submit(intent);
         }
         try {
-            latch.await(5, TimeUnit.SECONDS);
-            printResults(count);
+            if (latch.await(10, TimeUnit.SECONDS)) {
+                printResults(count);
+            } else {
+                print("I FAIL MISERABLY -> %d", latch.getCount());
+            }
         } catch (InterruptedException e) {
             print(e.toString());
         }
+
         service.removeListener(this);
     }
 
@@ -140,6 +144,8 @@
             } else {
                 log.warn("install event latch is null");
             }
+        } else {
+            log.info("I FAIL -> {}", event);
         }
     }
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/DefaultDevice.java b/core/api/src/main/java/org/onlab/onos/net/DefaultDevice.java
index 8b3eee1..53f2676 100644
--- a/core/api/src/main/java/org/onlab/onos/net/DefaultDevice.java
+++ b/core/api/src/main/java/org/onlab/onos/net/DefaultDevice.java
@@ -1,6 +1,7 @@
 package org.onlab.onos.net;
 
 import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.packet.ChassisId;
 
 import java.util.Objects;
 
@@ -16,6 +17,7 @@
     private final String serialNumber;
     private final String hwVersion;
     private final String swVersion;
+    private final ChassisId chassisId;
 
     // For serialization
     private DefaultDevice() {
@@ -24,6 +26,7 @@
         this.hwVersion = null;
         this.swVersion = null;
         this.serialNumber = null;
+        this.chassisId = null;
     }
 
     /**
@@ -40,13 +43,15 @@
      */
     public DefaultDevice(ProviderId providerId, DeviceId id, Type type,
                          String manufacturer, String hwVersion, String swVersion,
-                         String serialNumber, Annotations... annotations) {
+                         String serialNumber, ChassisId chassisId,
+                         Annotations... annotations) {
         super(providerId, id, annotations);
         this.type = type;
         this.manufacturer = manufacturer;
         this.hwVersion = hwVersion;
         this.swVersion = swVersion;
         this.serialNumber = serialNumber;
+        this.chassisId = chassisId;
     }
 
     @Override
@@ -80,6 +85,11 @@
     }
 
     @Override
+    public ChassisId chassisId() {
+        return chassisId;
+    }
+
+    @Override
     public int hashCode() {
         return Objects.hash(id, type, manufacturer, hwVersion, swVersion, serialNumber);
     }
diff --git a/core/api/src/main/java/org/onlab/onos/net/Device.java b/core/api/src/main/java/org/onlab/onos/net/Device.java
index 9e6018e..0294d99 100644
--- a/core/api/src/main/java/org/onlab/onos/net/Device.java
+++ b/core/api/src/main/java/org/onlab/onos/net/Device.java
@@ -1,5 +1,7 @@
 package org.onlab.onos.net;
 
+import org.onlab.packet.ChassisId;
+
 /**
  * Representation of a network infrastructure device.
  */
@@ -54,6 +56,13 @@
      */
     String serialNumber();
 
+    /**
+     * Returns the device chassis id.
+     *
+     * @return chassis id
+     */
+    ChassisId chassisId();
+
     // Device realizedBy(); ?
 
     // ports are not provided directly, but rather via DeviceService.getPorts(Device device);
diff --git a/core/api/src/main/java/org/onlab/onos/net/device/DefaultDeviceDescription.java b/core/api/src/main/java/org/onlab/onos/net/device/DefaultDeviceDescription.java
index ede2eb2..79710ae 100644
--- a/core/api/src/main/java/org/onlab/onos/net/device/DefaultDeviceDescription.java
+++ b/core/api/src/main/java/org/onlab/onos/net/device/DefaultDeviceDescription.java
@@ -2,6 +2,7 @@
 
 import org.onlab.onos.net.AbstractDescription;
 import org.onlab.onos.net.SparseAnnotations;
+import org.onlab.packet.ChassisId;
 
 import java.net.URI;
 
@@ -20,6 +21,7 @@
     private final String hwVersion;
     private final String swVersion;
     private final String serialNumber;
+    private final ChassisId chassisId;
 
     /**
      * Creates a device description using the supplied information.
@@ -34,7 +36,7 @@
      */
     public DefaultDeviceDescription(URI uri, Type type, String manufacturer,
                                     String hwVersion, String swVersion,
-                                    String serialNumber,
+                                    String serialNumber, ChassisId chassis,
                                     SparseAnnotations... annotations) {
         super(annotations);
         this.uri = checkNotNull(uri, "Device URI cannot be null");
@@ -43,6 +45,7 @@
         this.hwVersion = hwVersion;
         this.swVersion = swVersion;
         this.serialNumber = serialNumber;
+        this.chassisId = chassis;
     }
 
     /**
@@ -54,7 +57,7 @@
                                     SparseAnnotations... annotations) {
         this(base.deviceURI(), base.type(), base.manufacturer(),
              base.hwVersion(), base.swVersion(), base.serialNumber(),
-             annotations);
+             base.chassisId(), annotations);
     }
 
     @Override
@@ -88,6 +91,11 @@
     }
 
     @Override
+    public ChassisId chassisId() {
+        return chassisId;
+    }
+
+    @Override
     public String toString() {
         return toStringHelper(this)
                 .add("uri", uri).add("type", type).add("mfr", manufacturer)
@@ -104,5 +112,6 @@
         this.hwVersion = null;
         this.swVersion = null;
         this.serialNumber = null;
+        this.chassisId = null;
     }
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/device/DeviceDescription.java b/core/api/src/main/java/org/onlab/onos/net/device/DeviceDescription.java
index e32c19d..99b49ab 100644
--- a/core/api/src/main/java/org/onlab/onos/net/device/DeviceDescription.java
+++ b/core/api/src/main/java/org/onlab/onos/net/device/DeviceDescription.java
@@ -2,6 +2,7 @@
 
 import org.onlab.onos.net.Description;
 import org.onlab.onos.net.Device;
+import org.onlab.packet.ChassisId;
 
 import java.net.URI;
 
@@ -54,4 +55,11 @@
      */
     String serialNumber();
 
+    /**
+     * Returns a device chassis id.
+     *
+     * @return chassis id
+     */
+    ChassisId chassisId();
+
 }
diff --git a/core/api/src/test/java/org/onlab/onos/net/DefaultDeviceTest.java b/core/api/src/test/java/org/onlab/onos/net/DefaultDeviceTest.java
index 329e128..63f1daa 100644
--- a/core/api/src/test/java/org/onlab/onos/net/DefaultDeviceTest.java
+++ b/core/api/src/test/java/org/onlab/onos/net/DefaultDeviceTest.java
@@ -3,6 +3,7 @@
 import com.google.common.testing.EqualsTester;
 import org.junit.Test;
 import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.packet.ChassisId;
 
 import static org.junit.Assert.assertEquals;
 import static org.onlab.onos.net.Device.Type.SWITCH;
@@ -21,14 +22,15 @@
     static final String SW = "3.9.1";
     static final String SN1 = "43311-12345";
     static final String SN2 = "42346-43512";
+    static final ChassisId CID = new ChassisId();
 
     @Test
     public void testEquality() {
-        Device d1 = new DefaultDevice(PID, DID1, SWITCH, MFR, HW, SW, SN1);
-        Device d2 = new DefaultDevice(PID, DID1, SWITCH, MFR, HW, SW, SN1);
-        Device d3 = new DefaultDevice(PID, DID2, SWITCH, MFR, HW, SW, SN2);
-        Device d4 = new DefaultDevice(PID, DID2, SWITCH, MFR, HW, SW, SN2);
-        Device d5 = new DefaultDevice(PID, DID2, SWITCH, MFR, HW, SW, SN1);
+        Device d1 = new DefaultDevice(PID, DID1, SWITCH, MFR, HW, SW, SN1, CID);
+        Device d2 = new DefaultDevice(PID, DID1, SWITCH, MFR, HW, SW, SN1, CID);
+        Device d3 = new DefaultDevice(PID, DID2, SWITCH, MFR, HW, SW, SN2, CID);
+        Device d4 = new DefaultDevice(PID, DID2, SWITCH, MFR, HW, SW, SN2, CID);
+        Device d5 = new DefaultDevice(PID, DID2, SWITCH, MFR, HW, SW, SN1, CID);
 
         new EqualsTester().addEqualityGroup(d1, d2)
                 .addEqualityGroup(d3, d4)
@@ -38,13 +40,13 @@
 
     @Test
     public void basics() {
-        Device device = new DefaultDevice(PID, DID1, SWITCH, MFR, HW, SW, SN1);
+        Device device = new DefaultDevice(PID, DID1, SWITCH, MFR, HW, SW, SN1, CID);
         validate(device);
     }
 
     @Test
     public void annotations() {
-        Device device = new DefaultDevice(PID, DID1, SWITCH, MFR, HW, SW, SN1,
+        Device device = new DefaultDevice(PID, DID1, SWITCH, MFR, HW, SW, SN1, CID,
                                           DefaultAnnotations.builder().set("foo", "bar").build());
         validate(device);
         assertEquals("incorrect provider", "bar", device.annotations().value("foo"));
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 1069fd1..b9720ea 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
@@ -3,6 +3,7 @@
 import com.google.common.testing.EqualsTester;
 import org.junit.Test;
 import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.packet.ChassisId;
 
 import static org.junit.Assert.assertEquals;
 import static org.onlab.onos.net.Device.Type.SWITCH;
@@ -22,7 +23,8 @@
 
     @Test
     public void testEquality() {
-        Device device = new DefaultDevice(PID, DID1, SWITCH, "m", "h", "s", "n");
+        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);
@@ -37,7 +39,8 @@
 
     @Test
     public void basics() {
-        Device device = new DefaultDevice(PID, DID1, SWITCH, "m", "h", "s", "n");
+        Device device = new DefaultDevice(PID, DID1, SWITCH, "m", "h", "s", "n",
+                                          new ChassisId());
         Port port = new DefaultPort(device, portNumber(1), true);
         assertEquals("incorrect element", device, port.element());
         assertEquals("incorrect number", portNumber(1), port.number());
diff --git a/core/api/src/test/java/org/onlab/onos/net/NetTestTools.java b/core/api/src/test/java/org/onlab/onos/net/NetTestTools.java
index bd2e3ee..6fc38e3 100644
--- a/core/api/src/test/java/org/onlab/onos/net/NetTestTools.java
+++ b/core/api/src/test/java/org/onlab/onos/net/NetTestTools.java
@@ -1,6 +1,7 @@
 package org.onlab.onos.net;
 
 import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.packet.ChassisId;
 import org.onlab.packet.IpPrefix;
 
 import java.util.ArrayList;
@@ -37,7 +38,7 @@
     // Crates a new device with the specified id
     public static Device device(String id) {
         return new DefaultDevice(PID, did(id), Device.Type.SWITCH,
-                                 "mfg", "1.0", "1.1", "1234");
+                                 "mfg", "1.0", "1.1", "1234", new ChassisId());
     }
 
     // Crates a new host with the specified id
diff --git a/core/api/src/test/java/org/onlab/onos/net/device/DefaultDeviceDescriptionTest.java b/core/api/src/test/java/org/onlab/onos/net/device/DefaultDeviceDescriptionTest.java
index 9d06edf..243d148 100644
--- a/core/api/src/test/java/org/onlab/onos/net/device/DefaultDeviceDescriptionTest.java
+++ b/core/api/src/test/java/org/onlab/onos/net/device/DefaultDeviceDescriptionTest.java
@@ -1,6 +1,7 @@
 package org.onlab.onos.net.device;
 
 import org.junit.Test;
+import org.onlab.packet.ChassisId;
 
 import java.net.URI;
 
@@ -18,12 +19,13 @@
     private static final String HW = "1.1.x";
     private static final String SW = "3.9.1";
     private static final String SN = "43311-12345";
+    private static final ChassisId CID = new ChassisId();
 
 
     @Test
     public void basics() {
         DeviceDescription device =
-                new DefaultDeviceDescription(DURI, SWITCH, MFR, HW, SW, SN);
+                new DefaultDeviceDescription(DURI, SWITCH, MFR, HW, SW, SN, CID);
         assertEquals("incorrect uri", DURI, device.deviceURI());
         assertEquals("incorrect type", SWITCH, device.type());
         assertEquals("incorrect manufacturer", MFR, device.manufacturer());
@@ -31,6 +33,7 @@
         assertEquals("incorrect sw", SW, device.swVersion());
         assertEquals("incorrect serial", SN, device.serialNumber());
         assertTrue("incorrect toString", device.toString().contains("uri=of:foo"));
+        assertTrue("Incorrect chassis", device.chassisId().value() == 0);
     }
 
 }
diff --git a/core/api/src/test/java/org/onlab/onos/net/device/DeviceEventTest.java b/core/api/src/test/java/org/onlab/onos/net/device/DeviceEventTest.java
index 9c45b96..312b9c2 100644
--- a/core/api/src/test/java/org/onlab/onos/net/device/DeviceEventTest.java
+++ b/core/api/src/test/java/org/onlab/onos/net/device/DeviceEventTest.java
@@ -11,6 +11,7 @@
 import org.onlab.onos.net.Port;
 import org.onlab.onos.net.PortNumber;
 import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.packet.ChassisId;
 
 /**
  * Tests of the device event.
@@ -19,7 +20,7 @@
 
     private Device createDevice() {
         return new DefaultDevice(new ProviderId("of", "foo"), deviceId("of:foo"),
-                Device.Type.SWITCH, "box", "hw", "sw", "sn");
+                Device.Type.SWITCH, "box", "hw", "sw", "sn", new ChassisId());
     }
 
     @Override
diff --git a/core/api/src/test/java/org/onlab/onos/net/topology/DefaultGraphDescriptionTest.java b/core/api/src/test/java/org/onlab/onos/net/topology/DefaultGraphDescriptionTest.java
index 5f7d47b..81e6393 100644
--- a/core/api/src/test/java/org/onlab/onos/net/topology/DefaultGraphDescriptionTest.java
+++ b/core/api/src/test/java/org/onlab/onos/net/topology/DefaultGraphDescriptionTest.java
@@ -18,9 +18,9 @@
 
     private static final DeviceId D3 = deviceId("3");
 
-    static final Device DEV1 = new DefaultDevice(PID, D1, SWITCH, "", "", "", "");
-    static final Device DEV2 = new DefaultDevice(PID, D2, SWITCH, "", "", "", "");
-    static final Device DEV3 = new DefaultDevice(PID, D3, SWITCH, "", "", "", "");
+    static final Device DEV1 = new DefaultDevice(PID, D1, SWITCH, "", "", "", "", null);
+    static final Device DEV2 = new DefaultDevice(PID, D2, SWITCH, "", "", "", "", null);
+    static final Device DEV3 = new DefaultDevice(PID, D3, SWITCH, "", "", "", "", null);
 
     @Test
     public void basics() {
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 28bdac1..7cfe2f5 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
@@ -346,7 +346,7 @@
                             new DefaultDeviceDescription(
                                     did.uri(), device.type(), device.manufacturer(),
                                     device.hwVersion(), device.swVersion(),
-                                    device.serialNumber()));
+                                    device.serialNumber(), device.chassisId()));
                 }
 
                 applyRole(did, MastershipRole.MASTER);
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 8111681..dd6d7e5 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
@@ -96,7 +96,7 @@
 
             FlowRule rule = new DefaultFlowRule(link.src().deviceId(),
                     builder.build(), treatment,
-                    123, appId, 600);
+                    123, appId, 15);
             rules.add(new FlowRuleBatchEntry(FlowRuleOperation.ADD, rule));
             prev = link.dst();
         }
diff --git a/core/net/src/test/java/org/onlab/onos/net/device/impl/DeviceManagerTest.java b/core/net/src/test/java/org/onlab/onos/net/device/impl/DeviceManagerTest.java
index 0b017cf..a86502f 100644
--- a/core/net/src/test/java/org/onlab/onos/net/device/impl/DeviceManagerTest.java
+++ b/core/net/src/test/java/org/onlab/onos/net/device/impl/DeviceManagerTest.java
@@ -37,6 +37,7 @@
 import org.onlab.onos.net.provider.AbstractProvider;
 import org.onlab.onos.net.provider.ProviderId;
 import org.onlab.onos.store.trivial.impl.SimpleDeviceStore;
+import org.onlab.packet.ChassisId;
 import org.onlab.packet.IpPrefix;
 
 import java.util.ArrayList;
@@ -62,6 +63,7 @@
     private static final String SW1 = "3.8.1";
     private static final String SW2 = "3.9.5";
     private static final String SN = "43311-12345";
+    private static final ChassisId CID = new ChassisId();
 
     private static final PortNumber P1 = PortNumber.portNumber(1);
     private static final PortNumber P2 = PortNumber.portNumber(2);
@@ -111,7 +113,7 @@
     private void connectDevice(DeviceId deviceId, String swVersion) {
         DeviceDescription description =
                 new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR,
-                                             HW, swVersion, SN);
+                                             HW, swVersion, SN, CID);
         providerService.deviceConnected(deviceId, description);
         assertNotNull("device should be found", service.getDevice(DID1));
     }
diff --git a/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java b/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java
index 68223f0..86164fd 100644
--- a/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java
+++ b/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java
@@ -68,7 +68,7 @@
     private static final DeviceId DID = DeviceId.deviceId("of:001");
     private static final int TIMEOUT = 10;
     private static final Device DEV = new DefaultDevice(
-            PID, DID, Type.SWITCH, "", "", "", "");
+            PID, DID, Type.SWITCH, "", "", "", "", null);
 
     private FlowRuleManager mgr;
 
@@ -407,7 +407,6 @@
                                  FlowEntryState.PENDING_ADD));
 
 
-
     }
 
 
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 5642ea5..7ffcb3f 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
@@ -41,6 +41,7 @@
 import org.onlab.onos.store.impl.Timestamped;
 import org.onlab.onos.store.serializers.KryoSerializer;
 import org.onlab.onos.store.serializers.DistributedStoreSerializers;
+import org.onlab.packet.ChassisId;
 import org.onlab.util.KryoPool;
 import org.onlab.util.NewConcurrentHashMap;
 import org.slf4j.Logger;
@@ -745,6 +746,7 @@
         String hwVersion = base.hwVersion();
         String swVersion = base.swVersion();
         String serialNumber = base.serialNumber();
+        ChassisId chassisId = base.chassisId();
         DefaultAnnotations annotations = DefaultAnnotations.builder().build();
         annotations = merge(annotations, base.annotations());
 
@@ -762,7 +764,8 @@
         }
 
         return new DefaultDevice(primary, deviceId , type, manufacturer,
-                            hwVersion, swVersion, serialNumber, annotations);
+                            hwVersion, swVersion, serialNumber,
+                            chassisId, annotations);
     }
 
     /**
diff --git a/core/store/dist/src/test/java/org/onlab/onos/store/device/impl/GossipDeviceStoreTest.java b/core/store/dist/src/test/java/org/onlab/onos/store/device/impl/GossipDeviceStoreTest.java
index 141f2b2..55fa1f4 100644
--- a/core/store/dist/src/test/java/org/onlab/onos/store/device/impl/GossipDeviceStoreTest.java
+++ b/core/store/dist/src/test/java/org/onlab/onos/store/device/impl/GossipDeviceStoreTest.java
@@ -53,6 +53,7 @@
 import org.onlab.onos.store.cluster.messaging.ClusterMessage;
 import org.onlab.onos.store.cluster.messaging.ClusterMessageHandler;
 import org.onlab.onos.store.cluster.messaging.MessageSubject;
+import org.onlab.packet.ChassisId;
 import org.onlab.packet.IpPrefix;
 
 import com.google.common.collect.Iterables;
@@ -74,6 +75,7 @@
     private static final String SW1 = "3.8.1";
     private static final String SW2 = "3.9.5";
     private static final String SN = "43311-12345";
+    private static final ChassisId CID = new ChassisId();
 
     private static final PortNumber P1 = PortNumber.portNumber(1);
     private static final PortNumber P2 = PortNumber.portNumber(2);
@@ -158,7 +160,7 @@
                            SparseAnnotations... annotations) {
         DeviceDescription description =
                 new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR,
-                        HW, swVersion, SN, annotations);
+                        HW, swVersion, SN, CID, annotations);
         reset(clusterCommunicator);
         try {
             expect(clusterCommunicator.broadcast(anyObject(ClusterMessage.class)))
@@ -175,7 +177,7 @@
                                     SparseAnnotations... annotations) {
         DeviceDescription description =
                 new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR,
-                        HW, swVersion, SN, annotations);
+                        HW, swVersion, SN, CID, annotations);
         deviceStore.createOrUpdateDevice(PIDA, deviceId, description);
     }
 
@@ -315,7 +317,7 @@
     public final void testCreateOrUpdateDevice() throws IOException {
         DeviceDescription description =
                 new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
-                        HW, SW1, SN);
+                        HW, SW1, SN, CID);
         Capture<ClusterMessage> bcast = new Capture<>();
 
         resetCommunicatorExpectingSingleBroadcast(bcast);
@@ -328,7 +330,7 @@
 
         DeviceDescription description2 =
                 new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
-                        HW, SW2, SN);
+                        HW, SW2, SN, CID);
         resetCommunicatorExpectingSingleBroadcast(bcast);
         DeviceEvent event2 = deviceStore.createOrUpdateDevice(PID, DID1, description2);
         assertEquals(DEVICE_UPDATED, event2.type());
@@ -346,7 +348,7 @@
         // add
         DeviceDescription description =
                 new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
-                        HW, SW1, SN, A2);
+                        HW, SW1, SN, CID, A2);
         Capture<ClusterMessage> bcast = new Capture<>();
 
         resetCommunicatorExpectingSingleBroadcast(bcast);
@@ -362,7 +364,7 @@
         // update from primary
         DeviceDescription description2 =
                 new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
-                        HW, SW2, SN, A1);
+                        HW, SW2, SN, CID, A1);
         resetCommunicatorExpectingSingleBroadcast(bcast);
 
         DeviceEvent event2 = deviceStore.createOrUpdateDevice(PID, DID1, description2);
@@ -392,7 +394,7 @@
         // But, Ancillary annotations will be in effect
         DeviceDescription description3 =
                 new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
-                        HW, SW1, SN, A2_2);
+                        HW, SW1, SN, CID, A2_2);
         resetCommunicatorExpectingSingleBroadcast(bcast);
 
         DeviceEvent event3 = deviceStore.createOrUpdateDevice(PIDA, DID1, description3);
@@ -775,7 +777,7 @@
 
         DeviceDescription description =
                 new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
-                        HW, SW1, SN);
+                        HW, SW1, SN, CID);
         deviceStore.setDelegate(checkAdd);
         deviceStore.createOrUpdateDevice(PID, DID1, description);
         assertTrue("Add event fired", addLatch.await(1, TimeUnit.SECONDS));
@@ -783,7 +785,7 @@
 
         DeviceDescription description2 =
                 new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
-                        HW, SW2, SN);
+                        HW, SW2, SN, CID);
         deviceStore.unsetDelegate(checkAdd);
         deviceStore.setDelegate(checkUpdate);
         deviceStore.createOrUpdateDevice(PID, DID1, description2);
diff --git a/core/store/hz/net/src/main/java/org/onlab/onos/store/device/impl/DistributedDeviceStore.java b/core/store/hz/net/src/main/java/org/onlab/onos/store/device/impl/DistributedDeviceStore.java
new file mode 100644
index 0000000..894ee21
--- /dev/null
+++ b/core/store/hz/net/src/main/java/org/onlab/onos/store/device/impl/DistributedDeviceStore.java
@@ -0,0 +1,409 @@
+package org.onlab.onos.store.device.impl;
+
+import static com.google.common.base.Predicates.notNull;
+
+import com.google.common.base.Optional;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSet.Builder;
+import com.hazelcast.core.IMap;
+import com.hazelcast.core.ISet;
+
+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.DefaultDevice;
+import org.onlab.onos.net.DefaultPort;
+import org.onlab.onos.net.Device;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.Port;
+import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.device.DeviceDescription;
+import org.onlab.onos.net.device.DeviceEvent;
+import org.onlab.onos.net.device.DeviceStore;
+import org.onlab.onos.net.device.DeviceStoreDelegate;
+import org.onlab.onos.net.device.PortDescription;
+import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.onos.store.common.AbsentInvalidatingLoadingCache;
+import org.onlab.onos.store.common.AbstractHazelcastStore;
+import org.onlab.onos.store.common.OptionalCacheLoader;
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.cache.CacheBuilder.newBuilder;
+import static org.onlab.onos.net.device.DeviceEvent.Type.*;
+import static org.slf4j.LoggerFactory.getLogger;
+
+//TODO: Add support for multiple provider and annotations
+/**
+ * Manages inventory of infrastructure devices using Hazelcast-backed map.
+ */
+@Component(immediate = true)
+@Service
+public class DistributedDeviceStore
+        extends AbstractHazelcastStore<DeviceEvent, DeviceStoreDelegate>
+        implements DeviceStore {
+
+    private final Logger log = getLogger(getClass());
+
+    public static final String DEVICE_NOT_FOUND = "Device with ID %s not found";
+
+    // private IMap<DeviceId, DefaultDevice> cache;
+    private IMap<byte[], byte[]> rawDevices;
+    private LoadingCache<DeviceId, Optional<DefaultDevice>> devices;
+
+    // private ISet<DeviceId> availableDevices;
+    private ISet<byte[]> availableDevices;
+
+    // TODO DevicePorts is very inefficient consider restructuring.
+    // private IMap<DeviceId, Map<PortNumber, Port>> devicePorts;
+    private IMap<byte[], byte[]> rawDevicePorts;
+    private LoadingCache<DeviceId, Optional<Map<PortNumber, Port>>> devicePorts;
+
+    private String devicesListener;
+
+    private String portsListener;
+
+    @Override
+    @Activate
+    public void activate() {
+        super.activate();
+
+        // IMap event handler needs value
+        final boolean includeValue = true;
+
+        // TODO decide on Map name scheme to avoid collision
+        rawDevices = theInstance.getMap("devices");
+        final OptionalCacheLoader<DeviceId, DefaultDevice> deviceLoader
+                = new OptionalCacheLoader<>(serializer, rawDevices);
+        devices = new AbsentInvalidatingLoadingCache<>(newBuilder().build(deviceLoader));
+        // refresh/populate cache based on notification from other instance
+        devicesListener = rawDevices.addEntryListener(new RemoteDeviceEventHandler(devices), includeValue);
+
+        // TODO cache availableDevices
+        availableDevices = theInstance.getSet("availableDevices");
+
+        rawDevicePorts = theInstance.getMap("devicePorts");
+        final OptionalCacheLoader<DeviceId, Map<PortNumber, Port>> devicePortLoader
+                = new OptionalCacheLoader<>(serializer, rawDevicePorts);
+        devicePorts = new AbsentInvalidatingLoadingCache<>(newBuilder().build(devicePortLoader));
+        // refresh/populate cache based on notification from other instance
+        portsListener = rawDevicePorts.addEntryListener(new RemotePortEventHandler(devicePorts), includeValue);
+
+        loadDeviceCache();
+        loadDevicePortsCache();
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        rawDevicePorts.removeEntryListener(portsListener);
+        rawDevices.removeEntryListener(devicesListener);
+        log.info("Stopped");
+    }
+
+    @Override
+    public int getDeviceCount() {
+        return devices.asMap().size();
+    }
+
+    @Override
+    public Iterable<Device> getDevices() {
+        // TODO builder v.s. copyOf. Guava semms to be using copyOf?
+        Builder<Device> builder = ImmutableSet.builder();
+        for (Optional<DefaultDevice> e : devices.asMap().values()) {
+            if (e.isPresent()) {
+                builder.add(e.get());
+            }
+        }
+        return builder.build();
+    }
+
+    private void loadDeviceCache() {
+        for (byte[] keyBytes : rawDevices.keySet()) {
+            final DeviceId id = deserialize(keyBytes);
+            devices.refresh(id);
+        }
+    }
+
+    private void loadDevicePortsCache() {
+        for (byte[] keyBytes : rawDevicePorts.keySet()) {
+            final DeviceId id = deserialize(keyBytes);
+            devicePorts.refresh(id);
+        }
+    }
+
+    @Override
+    public Device getDevice(DeviceId deviceId) {
+        // TODO revisit if ignoring exception is safe.
+        return devices.getUnchecked(deviceId).orNull();
+    }
+
+    @Override
+    public DeviceEvent createOrUpdateDevice(ProviderId providerId, DeviceId deviceId,
+                                            DeviceDescription deviceDescription) {
+        DefaultDevice device = devices.getUnchecked(deviceId).orNull();
+        if (device == null) {
+            return createDevice(providerId, deviceId, deviceDescription);
+        }
+        return updateDevice(providerId, device, deviceDescription);
+    }
+
+    // Creates the device and returns the appropriate event if necessary.
+    private DeviceEvent createDevice(ProviderId providerId, DeviceId deviceId,
+                                     DeviceDescription desc) {
+        DefaultDevice device = new DefaultDevice(providerId, deviceId, desc.type(),
+                                                 desc.manufacturer(),
+                                                 desc.hwVersion(), desc.swVersion(),
+                                                 desc.serialNumber(), desc.chassisId());
+
+        synchronized (this) {
+            final byte[] deviceIdBytes = serialize(deviceId);
+            rawDevices.put(deviceIdBytes, serialize(device));
+            devices.put(deviceId, Optional.of(device));
+
+            availableDevices.add(deviceIdBytes);
+        }
+        return new DeviceEvent(DEVICE_ADDED, device, null);
+    }
+
+    // Updates the device and returns the appropriate event if necessary.
+    private DeviceEvent updateDevice(ProviderId providerId, DefaultDevice device,
+                                     DeviceDescription desc) {
+        // We allow only certain attributes to trigger update
+        if (!Objects.equals(device.hwVersion(), desc.hwVersion()) ||
+                !Objects.equals(device.swVersion(), desc.swVersion())) {
+
+            DefaultDevice updated = new DefaultDevice(providerId, device.id(),
+                                                      desc.type(),
+                                                      desc.manufacturer(),
+                                                      desc.hwVersion(),
+                                                      desc.swVersion(),
+                                                      desc.serialNumber(),
+                                                      desc.chassisId());
+            synchronized (this) {
+                final byte[] deviceIdBytes = serialize(device.id());
+                rawDevices.put(deviceIdBytes, serialize(updated));
+                devices.put(device.id(), Optional.of(updated));
+                availableDevices.add(serialize(device.id()));
+            }
+            return new DeviceEvent(DeviceEvent.Type.DEVICE_UPDATED, updated, null);
+        }
+
+        // Otherwise merely attempt to change availability
+        synchronized (this) {
+            boolean added = availableDevices.add(serialize(device.id()));
+            return !added ? null :
+                    new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device, null);
+        }
+    }
+
+    @Override
+    public DeviceEvent markOffline(DeviceId deviceId) {
+        synchronized (this) {
+            Device device = devices.getUnchecked(deviceId).orNull();
+            boolean removed = device != null && availableDevices.remove(serialize(deviceId));
+            return !removed ? null :
+                    new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device, null);
+        }
+    }
+
+    @Override
+    public List<DeviceEvent> updatePorts(ProviderId providerId, DeviceId deviceId,
+                                         List<PortDescription> portDescriptions) {
+        List<DeviceEvent> events = new ArrayList<>();
+        synchronized (this) {
+            Device device = devices.getUnchecked(deviceId).orNull();
+            checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
+            Map<PortNumber, Port> ports = getPortMap(deviceId);
+
+            // Add new ports
+            Set<PortNumber> processed = new HashSet<>();
+            for (PortDescription portDescription : portDescriptions) {
+                Port port = ports.get(portDescription.portNumber());
+                events.add(port == null ?
+                                   createPort(device, portDescription, ports) :
+                                   updatePort(device, port, portDescription, ports));
+                processed.add(portDescription.portNumber());
+            }
+
+            updatePortMap(deviceId, ports);
+
+            events.addAll(pruneOldPorts(device, ports, processed));
+        }
+        return FluentIterable.from(events).filter(notNull()).toList();
+    }
+
+    // Creates a new port based on the port description adds it to the map and
+    // Returns corresponding event.
+    //@GuardedBy("this")
+    private DeviceEvent createPort(Device device, PortDescription portDescription,
+                                   Map<PortNumber, Port> ports) {
+        DefaultPort port = new DefaultPort(device, portDescription.portNumber(),
+                                           portDescription.isEnabled());
+        ports.put(port.number(), port);
+        updatePortMap(device.id(), ports);
+        return new DeviceEvent(PORT_ADDED, device, port);
+    }
+
+    // Checks if the specified port requires update and if so, it replaces the
+    // existing entry in the map and returns corresponding event.
+    //@GuardedBy("this")
+    private DeviceEvent updatePort(Device device, Port port,
+                                   PortDescription portDescription,
+                                   Map<PortNumber, Port> ports) {
+        if (port.isEnabled() != portDescription.isEnabled()) {
+            DefaultPort updatedPort =
+                    new DefaultPort(device, portDescription.portNumber(),
+                                    portDescription.isEnabled());
+            ports.put(port.number(), updatedPort);
+            updatePortMap(device.id(), ports);
+            return new DeviceEvent(PORT_UPDATED, device, updatedPort);
+        }
+        return null;
+    }
+
+    // Prunes the specified list of ports based on which ports are in the
+    // processed list and returns list of corresponding events.
+    //@GuardedBy("this")
+    private List<DeviceEvent> pruneOldPorts(Device device,
+                                            Map<PortNumber, Port> ports,
+                                            Set<PortNumber> processed) {
+        List<DeviceEvent> events = new ArrayList<>();
+        Iterator<PortNumber> iterator = ports.keySet().iterator();
+        while (iterator.hasNext()) {
+            PortNumber portNumber = iterator.next();
+            if (!processed.contains(portNumber)) {
+                events.add(new DeviceEvent(PORT_REMOVED, device,
+                                           ports.get(portNumber)));
+                iterator.remove();
+            }
+        }
+        if (!events.isEmpty()) {
+            updatePortMap(device.id(), ports);
+        }
+        return events;
+    }
+
+    // Gets the map of ports for the specified device; if one does not already
+    // exist, it creates and registers a new one.
+    // WARN: returned value is a copy, changes made to the Map
+    //       needs to be written back using updatePortMap
+    //@GuardedBy("this")
+    private Map<PortNumber, Port> getPortMap(DeviceId deviceId) {
+        Map<PortNumber, Port> ports = devicePorts.getUnchecked(deviceId).orNull();
+        if (ports == null) {
+            ports = new HashMap<>();
+            // this probably is waste of time in most cases.
+            updatePortMap(deviceId, ports);
+        }
+        return ports;
+    }
+
+    //@GuardedBy("this")
+    private void updatePortMap(DeviceId deviceId, Map<PortNumber, Port> ports) {
+        rawDevicePorts.put(serialize(deviceId), serialize(ports));
+        devicePorts.put(deviceId, Optional.of(ports));
+    }
+
+    @Override
+    public DeviceEvent updatePortStatus(ProviderId providerId, DeviceId deviceId,
+                                        PortDescription portDescription) {
+        synchronized (this) {
+            Device device = devices.getUnchecked(deviceId).orNull();
+            checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
+            Map<PortNumber, Port> ports = getPortMap(deviceId);
+            Port port = ports.get(portDescription.portNumber());
+            return updatePort(device, port, portDescription, ports);
+        }
+    }
+
+    @Override
+    public List<Port> getPorts(DeviceId deviceId) {
+        Map<PortNumber, Port> ports = devicePorts.getUnchecked(deviceId).orNull();
+        return ports == null ? Collections.<Port>emptyList() : ImmutableList.copyOf(ports.values());
+    }
+
+    @Override
+    public Port getPort(DeviceId deviceId, PortNumber portNumber) {
+        Map<PortNumber, Port> ports = devicePorts.getUnchecked(deviceId).orNull();
+        return ports == null ? null : ports.get(portNumber);
+    }
+
+    @Override
+    public boolean isAvailable(DeviceId deviceId) {
+        return availableDevices.contains(serialize(deviceId));
+    }
+
+    @Override
+    public DeviceEvent removeDevice(DeviceId deviceId) {
+        synchronized (this) {
+            byte[] deviceIdBytes = serialize(deviceId);
+
+            // TODO conditional remove?
+            Device device = deserialize(rawDevices.remove(deviceIdBytes));
+            devices.invalidate(deviceId);
+            return device == null ? null :
+                    new DeviceEvent(DEVICE_REMOVED, device, null);
+        }
+    }
+
+    private class RemoteDeviceEventHandler extends RemoteCacheEventHandler<DeviceId, DefaultDevice> {
+        public RemoteDeviceEventHandler(LoadingCache<DeviceId, Optional<DefaultDevice>> cache) {
+            super(cache);
+        }
+
+        @Override
+        protected void onAdd(DeviceId deviceId, DefaultDevice device) {
+            notifyDelegate(new DeviceEvent(DEVICE_ADDED, device));
+        }
+
+        @Override
+        protected void onRemove(DeviceId deviceId, DefaultDevice device) {
+            notifyDelegate(new DeviceEvent(DEVICE_REMOVED, device));
+        }
+
+        @Override
+        protected void onUpdate(DeviceId deviceId, DefaultDevice oldDevice, DefaultDevice device) {
+            notifyDelegate(new DeviceEvent(DEVICE_UPDATED, device));
+        }
+    }
+
+    private class RemotePortEventHandler extends RemoteCacheEventHandler<DeviceId, Map<PortNumber, Port>> {
+        public RemotePortEventHandler(LoadingCache<DeviceId, Optional<Map<PortNumber, Port>>> cache) {
+            super(cache);
+        }
+
+        @Override
+        protected void onAdd(DeviceId deviceId, Map<PortNumber, Port> ports) {
+//            notifyDelegate(new DeviceEvent(PORT_ADDED, getDevice(deviceId)));
+        }
+
+        @Override
+        protected void onRemove(DeviceId deviceId, Map<PortNumber, Port> ports) {
+//            notifyDelegate(new DeviceEvent(PORT_REMOVED, getDevice(deviceId)));
+        }
+
+        @Override
+        protected void onUpdate(DeviceId deviceId, Map<PortNumber, Port> oldPorts, Map<PortNumber, Port> ports) {
+//            notifyDelegate(new DeviceEvent(PORT_UPDATED, getDevice(deviceId)));
+        }
+    }
+
+
+    // TODO cache serialized DeviceID if we suffer from serialization cost
+}
diff --git a/core/store/hz/net/src/test/java/org/onlab/onos/store/device/impl/DistributedDeviceStoreTest.java b/core/store/hz/net/src/test/java/org/onlab/onos/store/device/impl/DistributedDeviceStoreTest.java
new file mode 100644
index 0000000..43e3bcf
--- /dev/null
+++ b/core/store/hz/net/src/test/java/org/onlab/onos/store/device/impl/DistributedDeviceStoreTest.java
@@ -0,0 +1,392 @@
+/**
+ *
+ */
+package org.onlab.onos.store.device.impl;
+
+import static org.junit.Assert.*;
+import static org.onlab.onos.net.Device.Type.SWITCH;
+import static org.onlab.onos.net.DeviceId.deviceId;
+import static org.onlab.onos.net.device.DeviceEvent.Type.*;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.onlab.onos.net.Device;
+import org.onlab.onos.net.DeviceId;
+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;
+import org.onlab.onos.net.device.DeviceDescription;
+import org.onlab.onos.net.device.DeviceEvent;
+import org.onlab.onos.net.device.DeviceStoreDelegate;
+import org.onlab.onos.net.device.PortDescription;
+import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.onos.store.common.StoreManager;
+import org.onlab.onos.store.common.StoreService;
+import org.onlab.onos.store.common.TestStoreManager;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import com.hazelcast.config.Config;
+import com.hazelcast.core.Hazelcast;
+import org.onlab.packet.ChassisId;
+
+/**
+ * Test of the Hazelcast based distributed DeviceStore implementation.
+ */
+public class DistributedDeviceStoreTest {
+
+    private static final ProviderId PID = new ProviderId("of", "foo");
+    private static final DeviceId DID1 = deviceId("of:foo");
+    private static final DeviceId DID2 = deviceId("of:bar");
+    private static final String MFR = "whitebox";
+    private static final String HW = "1.1.x";
+    private static final String SW1 = "3.8.1";
+    private static final String SW2 = "3.9.5";
+    private static final String SN = "43311-12345";
+    private static final ChassisId CID = new ChassisId();
+
+    private static final PortNumber P1 = PortNumber.portNumber(1);
+    private static final PortNumber P2 = PortNumber.portNumber(2);
+    private static final PortNumber P3 = PortNumber.portNumber(3);
+
+    private DistributedDeviceStore deviceStore;
+
+    private StoreManager storeManager;
+
+
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception {
+    }
+
+    @AfterClass
+    public static void tearDownAfterClass() throws Exception {
+    }
+
+
+    @Before
+    public void setUp() throws Exception {
+        // TODO should find a way to clean Hazelcast instance without shutdown.
+        Config config = TestStoreManager.getTestConfig();
+
+        storeManager = new TestStoreManager(Hazelcast.newHazelcastInstance(config));
+        storeManager.activate();
+
+        deviceStore = new TestDistributedDeviceStore(storeManager);
+        deviceStore.activate();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        deviceStore.deactivate();
+
+        storeManager.deactivate();
+    }
+
+    private void putDevice(DeviceId deviceId, String swVersion) {
+        DeviceDescription description =
+                new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR,
+                        HW, swVersion, SN, CID);
+        deviceStore.createOrUpdateDevice(PID, deviceId, description);
+    }
+
+    private static void assertDevice(DeviceId id, String swVersion, Device device) {
+        assertNotNull(device);
+        assertEquals(id, device.id());
+        assertEquals(MFR, device.manufacturer());
+        assertEquals(HW, device.hwVersion());
+        assertEquals(swVersion, device.swVersion());
+        assertEquals(SN, device.serialNumber());
+    }
+
+    @Test
+    public final void testGetDeviceCount() {
+        assertEquals("initialy empty", 0, deviceStore.getDeviceCount());
+
+        putDevice(DID1, SW1);
+        putDevice(DID2, SW2);
+        putDevice(DID1, SW1);
+
+        assertEquals("expect 2 uniq devices", 2, deviceStore.getDeviceCount());
+    }
+
+    @Test
+    public final void testGetDevices() {
+        assertEquals("initialy empty", 0, Iterables.size(deviceStore.getDevices()));
+
+        putDevice(DID1, SW1);
+        putDevice(DID2, SW2);
+        putDevice(DID1, SW1);
+
+        assertEquals("expect 2 uniq devices",
+                2, Iterables.size(deviceStore.getDevices()));
+
+        Map<DeviceId, Device> devices = new HashMap<>();
+        for (Device device : deviceStore.getDevices()) {
+            devices.put(device.id(), device);
+        }
+
+        assertDevice(DID1, SW1, devices.get(DID1));
+        assertDevice(DID2, SW2, devices.get(DID2));
+
+        // add case for new node?
+    }
+
+    @Test
+    public final void testGetDevice() {
+
+        putDevice(DID1, SW1);
+
+        assertDevice(DID1, SW1, deviceStore.getDevice(DID1));
+        assertNull("DID2 shouldn't be there", deviceStore.getDevice(DID2));
+    }
+
+    @Test
+    public final void testCreateOrUpdateDevice() {
+        DeviceDescription description =
+                new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
+                        HW, SW1, SN, CID);
+        DeviceEvent event = deviceStore.createOrUpdateDevice(PID, DID1, description);
+        assertEquals(DEVICE_ADDED, event.type());
+        assertDevice(DID1, SW1, event.subject());
+
+        DeviceDescription description2 =
+                new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
+                        HW, SW2, SN, CID);
+        DeviceEvent event2 = deviceStore.createOrUpdateDevice(PID, DID1, description2);
+        assertEquals(DEVICE_UPDATED, event2.type());
+        assertDevice(DID1, SW2, event2.subject());
+
+        assertNull("No change expected", deviceStore.createOrUpdateDevice(PID, DID1, description2));
+    }
+
+    @Test
+    public final void testMarkOffline() {
+
+        putDevice(DID1, SW1);
+        assertTrue(deviceStore.isAvailable(DID1));
+
+        DeviceEvent event = deviceStore.markOffline(DID1);
+        assertEquals(DEVICE_AVAILABILITY_CHANGED, event.type());
+        assertDevice(DID1, SW1, event.subject());
+        assertFalse(deviceStore.isAvailable(DID1));
+
+        DeviceEvent event2 = deviceStore.markOffline(DID1);
+        assertNull("No change, no event", event2);
+}
+
+    @Test
+    public final void testUpdatePorts() {
+        putDevice(DID1, SW1);
+        List<PortDescription> pds = Arrays.<PortDescription>asList(
+                new DefaultPortDescription(P1, true),
+                new DefaultPortDescription(P2, true)
+                );
+
+        List<DeviceEvent> events = deviceStore.updatePorts(PID, DID1, pds);
+
+        Set<PortNumber> expectedPorts = Sets.newHashSet(P1, P2);
+        for (DeviceEvent event : events) {
+            assertEquals(PORT_ADDED, event.type());
+            assertDevice(DID1, SW1, event.subject());
+            assertTrue("PortNumber is one of expected",
+                    expectedPorts.remove(event.port().number()));
+            assertTrue("Port is enabled", event.port().isEnabled());
+        }
+        assertTrue("Event for all expectedport appeared", expectedPorts.isEmpty());
+
+
+        List<PortDescription> pds2 = Arrays.<PortDescription>asList(
+                new DefaultPortDescription(P1, false),
+                new DefaultPortDescription(P2, true),
+                new DefaultPortDescription(P3, true)
+                );
+
+        events = deviceStore.updatePorts(PID, DID1, pds2);
+        assertFalse("event should be triggered", events.isEmpty());
+        for (DeviceEvent event : events) {
+            PortNumber num = event.port().number();
+            if (P1.equals(num)) {
+                assertEquals(PORT_UPDATED, event.type());
+                assertDevice(DID1, SW1, event.subject());
+                assertFalse("Port is disabled", event.port().isEnabled());
+            } else if (P2.equals(num)) {
+                fail("P2 event not expected.");
+            } else if (P3.equals(num)) {
+                assertEquals(PORT_ADDED, event.type());
+                assertDevice(DID1, SW1, event.subject());
+                assertTrue("Port is enabled", event.port().isEnabled());
+            } else {
+                fail("Unknown port number encountered: " + num);
+            }
+        }
+
+        List<PortDescription> pds3 = Arrays.<PortDescription>asList(
+                new DefaultPortDescription(P1, false),
+                new DefaultPortDescription(P2, true)
+                );
+        events = deviceStore.updatePorts(PID, DID1, pds3);
+        assertFalse("event should be triggered", events.isEmpty());
+        for (DeviceEvent event : events) {
+            PortNumber num = event.port().number();
+            if (P1.equals(num)) {
+                fail("P1 event not expected.");
+            } else if (P2.equals(num)) {
+                fail("P2 event not expected.");
+            } else if (P3.equals(num)) {
+                assertEquals(PORT_REMOVED, event.type());
+                assertDevice(DID1, SW1, event.subject());
+                assertTrue("Port was enabled", event.port().isEnabled());
+            } else {
+                fail("Unknown port number encountered: " + num);
+            }
+        }
+
+    }
+
+    @Test
+    public final void testUpdatePortStatus() {
+        putDevice(DID1, SW1);
+        List<PortDescription> pds = Arrays.<PortDescription>asList(
+                new DefaultPortDescription(P1, true)
+                );
+        deviceStore.updatePorts(PID, DID1, pds);
+
+        DeviceEvent event = deviceStore.updatePortStatus(PID, DID1,
+                new DefaultPortDescription(P1, false));
+        assertEquals(PORT_UPDATED, event.type());
+        assertDevice(DID1, SW1, event.subject());
+        assertEquals(P1, event.port().number());
+        assertFalse("Port is disabled", event.port().isEnabled());
+    }
+
+    @Test
+    public final void testGetPorts() {
+        putDevice(DID1, SW1);
+        putDevice(DID2, SW1);
+        List<PortDescription> pds = Arrays.<PortDescription>asList(
+                new DefaultPortDescription(P1, true),
+                new DefaultPortDescription(P2, true)
+                );
+        deviceStore.updatePorts(PID, DID1, pds);
+
+        Set<PortNumber> expectedPorts = Sets.newHashSet(P1, P2);
+        List<Port> ports = deviceStore.getPorts(DID1);
+        for (Port port : ports) {
+            assertTrue("Port is enabled", port.isEnabled());
+            assertTrue("PortNumber is one of expected",
+                    expectedPorts.remove(port.number()));
+        }
+        assertTrue("Event for all expectedport appeared", expectedPorts.isEmpty());
+
+
+        assertTrue("DID2 has no ports", deviceStore.getPorts(DID2).isEmpty());
+    }
+
+    @Test
+    public final void testGetPort() {
+        putDevice(DID1, SW1);
+        putDevice(DID2, SW1);
+        List<PortDescription> pds = Arrays.<PortDescription>asList(
+                new DefaultPortDescription(P1, true),
+                new DefaultPortDescription(P2, false)
+                );
+        deviceStore.updatePorts(PID, DID1, pds);
+
+        Port port1 = deviceStore.getPort(DID1, P1);
+        assertEquals(P1, port1.number());
+        assertTrue("Port is enabled", port1.isEnabled());
+
+        Port port2 = deviceStore.getPort(DID1, P2);
+        assertEquals(P2, port2.number());
+        assertFalse("Port is disabled", port2.isEnabled());
+
+        Port port3 = deviceStore.getPort(DID1, P3);
+        assertNull("P3 not expected", port3);
+    }
+
+    @Test
+    public final void testRemoveDevice() {
+        putDevice(DID1, SW1);
+        putDevice(DID2, SW1);
+
+        assertEquals(2, deviceStore.getDeviceCount());
+
+        DeviceEvent event = deviceStore.removeDevice(DID1);
+        assertEquals(DEVICE_REMOVED, event.type());
+        assertDevice(DID1, SW1, event.subject());
+
+        assertEquals(1, deviceStore.getDeviceCount());
+    }
+
+    // TODO add test for Port events when we have them
+    @Ignore("Ignore until Delegate spec. is clear.")
+    @Test
+    public final void testEvents() throws InterruptedException {
+        final CountDownLatch addLatch = new CountDownLatch(1);
+        DeviceStoreDelegate checkAdd = new DeviceStoreDelegate() {
+            @Override
+            public void notify(DeviceEvent event) {
+                assertEquals(DEVICE_ADDED, event.type());
+                assertDevice(DID1, SW1, event.subject());
+                addLatch.countDown();
+            }
+        };
+        final CountDownLatch updateLatch = new CountDownLatch(1);
+        DeviceStoreDelegate checkUpdate = new DeviceStoreDelegate() {
+            @Override
+            public void notify(DeviceEvent event) {
+                assertEquals(DEVICE_UPDATED, event.type());
+                assertDevice(DID1, SW2, event.subject());
+                updateLatch.countDown();
+            }
+        };
+        final CountDownLatch removeLatch = new CountDownLatch(1);
+        DeviceStoreDelegate checkRemove = new DeviceStoreDelegate() {
+            @Override
+            public void notify(DeviceEvent event) {
+                assertEquals(DEVICE_REMOVED, event.type());
+                assertDevice(DID1, SW2, event.subject());
+                removeLatch.countDown();
+            }
+        };
+
+        DeviceDescription description =
+                new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
+                        HW, SW1, SN, CID);
+        deviceStore.setDelegate(checkAdd);
+        deviceStore.createOrUpdateDevice(PID, DID1, description);
+        assertTrue("Add event fired", addLatch.await(1, TimeUnit.SECONDS));
+
+
+        DeviceDescription description2 =
+                new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
+                        HW, SW2, SN, CID);
+        deviceStore.unsetDelegate(checkAdd);
+        deviceStore.setDelegate(checkUpdate);
+        deviceStore.createOrUpdateDevice(PID, DID1, description2);
+        assertTrue("Update event fired", updateLatch.await(1, TimeUnit.SECONDS));
+
+        deviceStore.unsetDelegate(checkUpdate);
+        deviceStore.setDelegate(checkRemove);
+        deviceStore.removeDevice(DID1);
+        assertTrue("Remove event fired", removeLatch.await(1, TimeUnit.SECONDS));
+    }
+
+    private class TestDistributedDeviceStore extends DistributedDeviceStore {
+        public TestDistributedDeviceStore(StoreService storeService) {
+            this.storeService = storeService;
+        }
+    }
+}
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoPoolUtil.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoPoolUtil.java
index b229063..b63f844 100644
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoPoolUtil.java
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoPoolUtil.java
@@ -31,6 +31,7 @@
 import org.onlab.onos.net.link.DefaultLinkDescription;
 import org.onlab.onos.net.provider.ProviderId;
 import org.onlab.onos.store.Timestamp;
+import org.onlab.packet.ChassisId;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
 import org.onlab.packet.MacAddress;
@@ -71,6 +72,7 @@
                     //
                     ControllerNode.State.class,
                     Device.Type.class,
+                    ChassisId.class,
                     DefaultAnnotations.class,
                     DefaultControllerNode.class,
                     DefaultDevice.class,
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 58956d5..1939a7e 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
@@ -26,6 +26,7 @@
 import org.onlab.onos.net.PortNumber;
 import org.onlab.onos.net.SparseAnnotations;
 import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.packet.ChassisId;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
 import org.onlab.util.KryoPool;
@@ -49,7 +50,9 @@
     private static final String SW1 = "3.8.1";
     private static final String SW2 = "3.9.5";
     private static final String SN = "43311-12345";
-    private static final Device DEV1 = new DefaultDevice(PID, DID1, Device.Type.SWITCH, MFR, HW, SW1, SN);
+    private static final ChassisId CID = new ChassisId();
+    private static final Device DEV1 = new DefaultDevice(PID, DID1, Device.Type.SWITCH, MFR, HW,
+                                                         SW1, SN, CID);
     private static final SparseAnnotations A1 = DefaultAnnotations.builder()
             .set("A1", "a1")
             .set("B1", "b1")
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 30904ba..fbfaf9d 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
@@ -28,6 +28,7 @@
 import org.onlab.onos.net.device.PortDescription;
 import org.onlab.onos.net.provider.ProviderId;
 import org.onlab.onos.store.AbstractStore;
+import org.onlab.packet.ChassisId;
 import org.onlab.util.NewConcurrentHashMap;
 import org.slf4j.Logger;
 
@@ -429,6 +430,7 @@
         String hwVersion = base.hwVersion();
         String swVersion = base.swVersion();
         String serialNumber = base.serialNumber();
+        ChassisId chassisId = base.chassisId();
         DefaultAnnotations annotations = DefaultAnnotations.builder().build();
         annotations = merge(annotations, base.annotations());
 
@@ -446,7 +448,8 @@
         }
 
         return new DefaultDevice(primary, deviceId , type, manufacturer,
-                            hwVersion, swVersion, serialNumber, annotations);
+                            hwVersion, swVersion, serialNumber,
+                            chassisId, annotations);
     }
 
     /**
diff --git a/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/DefaultTopologyTest.java b/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/DefaultTopologyTest.java
index ef383c8..879f123 100644
--- a/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/DefaultTopologyTest.java
+++ b/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/DefaultTopologyTest.java
@@ -17,6 +17,7 @@
 import org.onlab.onos.net.topology.LinkWeight;
 import org.onlab.onos.net.topology.TopologyCluster;
 import org.onlab.onos.net.topology.TopologyEdge;
+import org.onlab.packet.ChassisId;
 
 import java.util.Set;
 
@@ -119,7 +120,7 @@
     // Crates a new device with the specified id
     public static Device device(String id) {
         return new DefaultDevice(PID, did(id), Device.Type.SWITCH,
-                                 "mfg", "1.0", "1.1", "1234");
+                                 "mfg", "1.0", "1.1", "1234", new ChassisId());
     }
 
     // Short-hand for producing a device id from a string
diff --git a/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStoreTest.java b/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStoreTest.java
index 146086a..8338a77 100644
--- a/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStoreTest.java
+++ b/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStoreTest.java
@@ -40,6 +40,7 @@
 
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
+import org.onlab.packet.ChassisId;
 
 /**
  * Test of the simple DeviceStore implementation.
@@ -55,6 +56,7 @@
     private static final String SW1 = "3.8.1";
     private static final String SW2 = "3.9.5";
     private static final String SN = "43311-12345";
+    private static final ChassisId CID = new ChassisId();
 
     private static final PortNumber P1 = PortNumber.portNumber(1);
     private static final PortNumber P2 = PortNumber.portNumber(2);
@@ -107,7 +109,7 @@
                            SparseAnnotations... annotations) {
         DeviceDescription description =
                 new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR,
-                        HW, swVersion, SN, annotations);
+                        HW, swVersion, SN, CID, annotations);
         deviceStore.createOrUpdateDevice(PID, deviceId, description);
     }
 
@@ -115,7 +117,7 @@
                                     SparseAnnotations... annotations) {
         DeviceDescription description =
                 new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR,
-                        HW, swVersion, SN, annotations);
+                        HW, swVersion, SN, CID, annotations);
         deviceStore.createOrUpdateDevice(PIDA, deviceId, description);
     }
 
@@ -193,14 +195,14 @@
     public final void testCreateOrUpdateDevice() {
         DeviceDescription description =
                 new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
-                        HW, SW1, SN);
+                        HW, SW1, SN, CID);
         DeviceEvent event = deviceStore.createOrUpdateDevice(PID, DID1, description);
         assertEquals(DEVICE_ADDED, event.type());
         assertDevice(DID1, SW1, event.subject());
 
         DeviceDescription description2 =
                 new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
-                        HW, SW2, SN);
+                        HW, SW2, SN, CID);
         DeviceEvent event2 = deviceStore.createOrUpdateDevice(PID, DID1, description2);
         assertEquals(DEVICE_UPDATED, event2.type());
         assertDevice(DID1, SW2, event2.subject());
@@ -212,7 +214,7 @@
     public final void testCreateOrUpdateDeviceAncillary() {
         DeviceDescription description =
                 new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
-                        HW, SW1, SN, A2);
+                        HW, SW1, SN, CID, A2);
         DeviceEvent event = deviceStore.createOrUpdateDevice(PIDA, DID1, description);
         assertEquals(DEVICE_ADDED, event.type());
         assertDevice(DID1, SW1, event.subject());
@@ -222,7 +224,7 @@
 
         DeviceDescription description2 =
                 new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
-                        HW, SW2, SN, A1);
+                        HW, SW2, SN, CID, A1);
         DeviceEvent event2 = deviceStore.createOrUpdateDevice(PID, DID1, description2);
         assertEquals(DEVICE_UPDATED, event2.type());
         assertDevice(DID1, SW2, event2.subject());
@@ -238,7 +240,7 @@
         // But, Ancillary annotations will be in effect
         DeviceDescription description3 =
                 new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
-                        HW, SW1, SN, A2_2);
+                        HW, SW1, SN, CID, A2_2);
         DeviceEvent event3 = deviceStore.createOrUpdateDevice(PIDA, DID1, description3);
         assertEquals(DEVICE_UPDATED, event3.type());
         // basic information will be the one from Primary
@@ -508,7 +510,7 @@
 
         DeviceDescription description =
                 new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
-                        HW, SW1, SN);
+                        HW, SW1, SN, CID);
         deviceStore.setDelegate(checkAdd);
         deviceStore.createOrUpdateDevice(PID, DID1, description);
         assertTrue("Add event fired", addLatch.await(1, TimeUnit.SECONDS));
@@ -516,7 +518,7 @@
 
         DeviceDescription description2 =
                 new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
-                        HW, SW2, SN);
+                        HW, SW2, SN, CID);
         deviceStore.unsetDelegate(checkAdd);
         deviceStore.setDelegate(checkUpdate);
         deviceStore.createOrUpdateDevice(PID, DID1, description2);
diff --git a/features/features.xml b/features/features.xml
index 899a0e2..b66a56a 100644
--- a/features/features.xml
+++ b/features/features.xml
@@ -104,12 +104,10 @@
         <bundle>mvn:org.onlab.onos/onos-of-api/1.0.0-SNAPSHOT</bundle>
         <bundle>mvn:org.onlab.onos/onos-of-ctl/1.0.0-SNAPSHOT</bundle>
 
-        <bundle>mvn:org.onlab.onos/onos-of-provider-device/1.0.0-SNAPSHOT
-        </bundle>
-        <bundle>mvn:org.onlab.onos/onos-of-provider-link/1.0.0-SNAPSHOT</bundle>
+        <bundle>mvn:org.onlab.onos/onos-lldp-provider/1.0.0-SNAPSHOT</bundle>
+        <bundle>mvn:org.onlab.onos/onos-of-provider-device/1.0.0-SNAPSHOT</bundle>
         <bundle>mvn:org.onlab.onos/onos-of-provider-host/1.0.0-SNAPSHOT</bundle>
-        <bundle>mvn:org.onlab.onos/onos-of-provider-packet/1.0.0-SNAPSHOT
-        </bundle>
+        <bundle>mvn:org.onlab.onos/onos-of-provider-packet/1.0.0-SNAPSHOT</bundle>
         <bundle>mvn:org.onlab.onos/onos-of-provider-flow/1.0.0-SNAPSHOT</bundle>
 
     </feature>
diff --git a/providers/lldp/pom.xml b/providers/lldp/pom.xml
new file mode 100644
index 0000000..06c18bd
--- /dev/null
+++ b/providers/lldp/pom.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+
+    <parent>
+        <groupId>org.onlab.onos</groupId>
+        <artifactId>onos-providers</artifactId>
+        <version>1.0.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>onos-lldp-provider</artifactId>
+    <packaging>bundle</packaging>
+
+    <description>ONOS LLDP Link Discovery</description>
+
+</project>
diff --git a/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/LLDPLinkProvider.java b/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/LLDPLinkProvider.java
new file mode 100644
index 0000000..023940e
--- /dev/null
+++ b/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/LLDPLinkProvider.java
@@ -0,0 +1,165 @@
+package org.onlab.onos.provider.lldp.impl;
+
+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.Device;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.device.DeviceEvent;
+import org.onlab.onos.net.device.DeviceListener;
+import org.onlab.onos.net.device.DeviceService;
+import org.onlab.onos.net.link.LinkProvider;
+import org.onlab.onos.net.link.LinkProviderRegistry;
+import org.onlab.onos.net.link.LinkProviderService;
+import org.onlab.onos.net.packet.PacketContext;
+import org.onlab.onos.net.packet.PacketProcessor;
+import org.onlab.onos.net.packet.PacketService;
+import org.onlab.onos.net.provider.AbstractProvider;
+import org.onlab.onos.net.provider.ProviderId;
+import org.slf4j.Logger;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+
+/**
+ * Provider which uses an OpenFlow controller to detect network
+ * infrastructure links.
+ */
+@Component(immediate = true)
+public class LLDPLinkProvider extends AbstractProvider implements LinkProvider {
+
+    private final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected LinkProviderRegistry providerRegistry;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetSevice;
+
+    private LinkProviderService providerService;
+
+    private final boolean useBDDP = true;
+
+
+    private final InternalLinkProvider listener = new InternalLinkProvider();
+
+    protected final Map<DeviceId, LinkDiscovery> discoverers = new ConcurrentHashMap<>();
+
+    /**
+     * Creates an OpenFlow link provider.
+     */
+    public LLDPLinkProvider() {
+        super(new ProviderId("lldp", "org.onlab.onos.provider.lldp"));
+    }
+
+    @Activate
+    public void activate() {
+        providerService = providerRegistry.register(this);
+        deviceService.addListener(listener);
+        packetSevice.addProcessor(listener, 0);
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        for (LinkDiscovery ld : discoverers.values()) {
+            ld.stop();
+        }
+        providerRegistry.unregister(this);
+        deviceService.removeListener(listener);
+        packetSevice.removeProcessor(listener);
+        providerService = null;
+
+        log.info("Stopped");
+    }
+
+
+    private class InternalLinkProvider implements PacketProcessor, DeviceListener {
+
+        @Override
+        public void event(DeviceEvent event) {
+            LinkDiscovery ld = null;
+            Device device = event.subject();
+            // it's not a switch so leave.
+            if (device.type() != Device.Type.SWITCH) {
+                return;
+            }
+            switch (event.type()) {
+                case DEVICE_ADDED:
+                    discoverers.put(event.subject().id(),
+                                    new LinkDiscovery(event.subject(), packetSevice,
+                                                      providerService, useBDDP));
+                    break;
+                case PORT_ADDED:
+                case PORT_UPDATED:
+                    if (event.port().isEnabled()) {
+                        ld = discoverers.get(event.subject().id());
+                        if (ld == null) {
+                            return;
+                        }
+                        ld.addPort(event.port());
+                    } else {
+                        ConnectPoint point = new ConnectPoint(event.subject().id(),
+                                                              event.port().number());
+                        providerService.linksVanished(point);
+                    }
+                    break;
+                case PORT_REMOVED:
+                    ConnectPoint point = new ConnectPoint(event.subject().id(),
+                                                          event.port().number());
+                    providerService.linksVanished(point);
+                    break;
+                case DEVICE_REMOVED:
+                case DEVICE_SUSPENDED:
+                    ld = discoverers.get(event.subject().id());
+                    if (ld == null) {
+                        return;
+                    }
+                    ld.stop();
+                    providerService.linksVanished(event.subject().id());
+                    break;
+                case DEVICE_AVAILABILITY_CHANGED:
+                    ld = discoverers.get(event.subject().id());
+                    if (ld == null) {
+                        return;
+                    }
+                    if (deviceService.isAvailable(event.subject().id())) {
+                        ld.start();
+                    } else {
+                        providerService.linksVanished(event.subject().id());
+                        ld.stop();
+                    }
+                    break;
+                case DEVICE_UPDATED:
+                case DEVICE_MASTERSHIP_CHANGED:
+                    break;
+                default:
+                    log.debug("Unknown event {}", event);
+            }
+        }
+
+        @Override
+        public void process(PacketContext context) {
+            LinkDiscovery ld = discoverers.get(
+                    context.inPacket().receivedFrom().deviceId());
+            if (ld == null) {
+                return;
+            }
+
+            if (ld.handleLLDP(context)) {
+                context.block();
+            }
+        }
+    }
+
+}
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
new file mode 100644
index 0000000..fc0a0f4
--- /dev/null
+++ b/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/LinkDiscovery.java
@@ -0,0 +1,349 @@
+/*******************************************************************************
+ * 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.provider.lldp.impl;
+
+
+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.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.jboss.netty.util.Timeout;
+import org.jboss.netty.util.TimerTask;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.Device;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.Link.Type;
+import org.onlab.onos.net.Port;
+import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.flow.DefaultTrafficTreatment;
+import org.onlab.onos.net.link.DefaultLinkDescription;
+import org.onlab.onos.net.link.LinkDescription;
+import org.onlab.onos.net.link.LinkProviderService;
+import org.onlab.onos.net.packet.DefaultOutboundPacket;
+import org.onlab.onos.net.packet.OutboundPacket;
+import org.onlab.onos.net.packet.PacketContext;
+import org.onlab.onos.net.packet.PacketService;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.ONOSLLDP;
+import org.onlab.util.Timer;
+import org.slf4j.Logger;
+
+
+
+/**
+ * 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
+ * fast. Every probeRate milliseconds, loop over all fast ports and send an
+ * LLDP, send an LLDP for a single slow port. Based on FlowVisor topology
+ * discovery implementation.
+ *
+ * TODO: add 'fast discovery' mode: drop LLDPs in destination switch but listen
+ * for flow_removed messages
+ */
+public class LinkDiscovery implements TimerTask {
+
+    private final Device device;
+    // send 1 probe every probeRate milliseconds
+    private final long probeRate;
+    private final Set<Long> slowPorts;
+    private final Set<Long> fastPorts;
+    // number of unacknowledged probes per port
+    private final Map<Long, AtomicInteger> portProbeCount;
+    // number of probes to send before link is removed
+    private static final short MAX_PROBE_COUNT = 3;
+    private final Logger log = getLogger(getClass());
+    private final ONOSLLDP lldpPacket;
+    private final Ethernet ethPacket;
+    private Ethernet bddpEth;
+    private final boolean useBDDP;
+    private final LinkProviderService linkProvider;
+    private final PacketService pktService;
+    private Timeout timeout;
+
+    /**
+     * Instantiates discovery manager for the given physical switch. Creates a
+     * generic LLDP packet that will be customized for the port it is sent out on.
+     * Starts the the timer for the discovery process.
+     *
+     * @param device the physical switch
+     * @param useBDDP flag to also use BDDP for discovery
+     */
+    public LinkDiscovery(Device device, PacketService pktService,
+             LinkProviderService providerService, Boolean... useBDDP) {
+        this.device = device;
+        this.probeRate = 3000;
+        this.linkProvider = providerService;
+        this.pktService = pktService;
+        this.slowPorts = Collections.synchronizedSet(new HashSet<Long>());
+        this.fastPorts = Collections.synchronizedSet(new HashSet<Long>());
+        this.portProbeCount = new HashMap<>();
+        this.lldpPacket = new ONOSLLDP();
+        this.lldpPacket.setChassisId(device.chassisId());
+        this.lldpPacket.setDevice(device.id().toString());
+
+
+        this.ethPacket = new Ethernet();
+        this.ethPacket.setEtherType(Ethernet.TYPE_LLDP);
+        this.ethPacket.setDestinationMACAddress(ONOSLLDP.LLDP_NICIRA);
+        this.ethPacket.setPayload(this.lldpPacket);
+        this.ethPacket.setPad(true);
+        this.useBDDP = useBDDP.length > 0 ? useBDDP[0] : false;
+        if (this.useBDDP) {
+            this.bddpEth = new Ethernet();
+            this.bddpEth.setPayload(this.lldpPacket);
+            this.bddpEth.setEtherType(Ethernet.TYPE_BSN);
+            this.bddpEth.setDestinationMACAddress(ONOSLLDP.BDDP_MULTICAST);
+            this.bddpEth.setPad(true);
+            log.info("Using BDDP to discover network");
+        }
+
+        start();
+        this.log.debug("Started discovery manager for switch {}",
+                       device.id());
+
+    }
+
+    /**
+     * Add physical port port to discovery process.
+     * Send out initial LLDP and label it as slow port.
+     *
+     * @param port the port
+     */
+    public void addPort(final Port port) {
+        this.log.debug("sending init probe to port {}",
+                           port.number().toLong());
+
+        sendProbes(port.number().toLong());
+
+        synchronized (this) {
+            this.slowPorts.add(port.number().toLong());
+        }
+
+
+    }
+
+    /**
+     * Removes physical port from discovery process.
+     *
+     * @param port the port
+     */
+    public void removePort(final Port port) {
+        // Ignore ports that are not on this switch
+
+        long portnum = port.number().toLong();
+        synchronized (this) {
+            if (this.slowPorts.contains(portnum)) {
+                this.slowPorts.remove(portnum);
+
+            } else if (this.fastPorts.contains(portnum)) {
+                this.fastPorts.remove(portnum);
+                this.portProbeCount.remove(portnum);
+                // no iterator to update
+            } else {
+                this.log.warn(
+                        "tried to dynamically remove non-existing port {}",
+                        portnum);
+            }
+        }
+    }
+
+    /**
+     * Method called by remote port to acknowledge receipt of LLDP sent by
+     * this port. If slow port, updates label to fast. If fast port, decrements
+     * number of unacknowledged probes.
+     *
+     * @param portNumber the port
+     */
+    public void ackProbe(final Long portNumber) {
+
+        synchronized (this) {
+            if (this.slowPorts.contains(portNumber)) {
+                this.log.debug("Setting slow port to fast: {}:{}",
+                        this.device.id(), portNumber);
+                this.slowPorts.remove(portNumber);
+                this.fastPorts.add(portNumber);
+                this.portProbeCount.put(portNumber, new AtomicInteger(0));
+            } else {
+                if (this.fastPorts.contains(portNumber)) {
+                    this.portProbeCount.get(portNumber).set(0);
+                } else {
+                    this.log.debug(
+                            "Got ackProbe for non-existing port: {}",
+                            portNumber);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Handles an incoming LLDP packet. Creates link in topology and sends ACK
+     * to port where LLDP originated.
+     */
+    public boolean handleLLDP(PacketContext context) {
+        Ethernet eth = context.inPacket().parsed();
+        ONOSLLDP onoslldp = ONOSLLDP.parseONOSLLDP(eth);
+        if (onoslldp != null) {
+            final PortNumber dstPort =
+                    context.inPacket().receivedFrom().port();
+            final PortNumber srcPort = PortNumber.portNumber(onoslldp.getPort());
+            final DeviceId srcDeviceId = DeviceId.deviceId(onoslldp.getDeviceString());
+            final DeviceId dstDeviceId = context.inPacket().receivedFrom().deviceId();
+            this.ackProbe(srcPort.toLong());
+            ConnectPoint src = new ConnectPoint(srcDeviceId, srcPort);
+            ConnectPoint dst = new ConnectPoint(dstDeviceId, dstPort);
+
+            LinkDescription ld;
+            if (eth.getEtherType() == Ethernet.TYPE_BSN) {
+                ld = new DefaultLinkDescription(src, dst, Type.INDIRECT);
+            } else {
+                ld = new DefaultLinkDescription(src, dst, Type.DIRECT);
+            }
+            linkProvider.linkDetected(ld);
+            return true;
+        }
+        return false;
+    }
+
+
+
+    /**
+     * Execute this method every t milliseconds. Loops over all ports
+     * labeled as fast and sends out an LLDP. Send out an LLDP on a single slow
+     * port.
+     *
+     * @param t timeout
+     * @throws Exception
+     */
+    @Override
+    public void run(final Timeout t) {
+        this.log.debug("sending probes");
+        synchronized (this) {
+            final Iterator<Long> fastIterator = this.fastPorts.iterator();
+            Long portNumber;
+            Integer probeCount;
+            while (fastIterator.hasNext()) {
+                portNumber = fastIterator.next();
+                probeCount = this.portProbeCount.get(portNumber)
+                        .getAndIncrement();
+
+                if (probeCount < LinkDiscovery.MAX_PROBE_COUNT) {
+                    this.log.debug("sending fast probe to port");
+                    sendProbes(portNumber);
+                } else {
+                    // Update fast and slow ports
+                    //fastIterator.remove();
+                    //this.slowPorts.add(portNumber);
+                    //this.portProbeCount.remove(portNumber);
+
+
+                    ConnectPoint cp = new ConnectPoint(
+                            device.id(),
+                            PortNumber.portNumber(portNumber));
+                    log.debug("Link down -> {}", cp);
+                    linkProvider.linksVanished(cp);
+                }
+            }
+
+            // send a probe for the next slow port
+            if (!this.slowPorts.isEmpty()) {
+                Iterator<Long> slowIterator = this.slowPorts.iterator();
+                while (slowIterator.hasNext()) {
+                    portNumber = slowIterator.next();
+                    this.log.debug("sending slow probe to port {}", portNumber);
+
+                    sendProbes(portNumber);
+
+                }
+            }
+        }
+
+        // reschedule timer
+        timeout = Timer.getTimer().newTimeout(this, this.probeRate,
+                TimeUnit.MILLISECONDS);
+    }
+
+    public void stop() {
+        timeout.cancel();
+    }
+
+    public void start() {
+        timeout = Timer.getTimer().newTimeout(this, 0,
+                                              TimeUnit.MILLISECONDS);
+    }
+
+    /**
+     * Creates packet_out LLDP for specified output port.
+     *
+     * @param port the port
+     * @return Packet_out message with LLDP data
+     */
+    private OutboundPacket createOutBoundLLDP(final Long port) {
+        if (port == null) {
+            return null;
+        }
+        this.lldpPacket.setPortId(port.intValue());
+        this.ethPacket.setSourceMACAddress("DE:AD:BE:EF:BA:11");
+
+        final byte[] lldp = this.ethPacket.serialize();
+        OutboundPacket outboundPacket = new DefaultOutboundPacket(
+                this.device.id(),
+                DefaultTrafficTreatment.builder().setOutput(
+                        PortNumber.portNumber(port)).build(),
+                ByteBuffer.wrap(lldp));
+        return outboundPacket;
+    }
+
+    /**
+     * Creates packet_out BDDP for specified output port.
+     *
+     * @param port the port
+     * @return Packet_out message with LLDP data
+     */
+    private OutboundPacket createOutBoundBDDP(final Long port) {
+        if (port == null) {
+            return null;
+        }
+        this.lldpPacket.setPortId(port.intValue());
+        this.bddpEth.setSourceMACAddress("DE:AD:BE:EF:BA:11");
+
+        final byte[] bddp = this.bddpEth.serialize();
+        OutboundPacket outboundPacket = new DefaultOutboundPacket(
+                this.device.id(),
+                DefaultTrafficTreatment.builder()
+                        .setOutput(PortNumber.portNumber(port)).build(),
+                ByteBuffer.wrap(bddp));
+        return outboundPacket;
+    }
+
+    private void sendProbes(Long portNumber) {
+        OutboundPacket pkt = this.createOutBoundLLDP(portNumber);
+        pktService.emit(pkt);
+        if (useBDDP) {
+            OutboundPacket bpkt = this.createOutBoundBDDP(portNumber);
+            pktService.emit(bpkt);
+        }
+    }
+
+}
diff --git a/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/package-info.java b/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/package-info.java
new file mode 100644
index 0000000..c7f5b61
--- /dev/null
+++ b/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Provider that uses the core as a means of infrastructure link inference.
+ */
+package org.onlab.onos.provider.lldp.impl;
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 8958fb6..88cb1ac 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.openflow.controller.OpenFlowSwitch;
 import org.onlab.onos.openflow.controller.OpenFlowSwitchListener;
 import org.onlab.onos.openflow.controller.RoleState;
+import org.onlab.packet.ChassisId;
 import org.projectfloodlight.openflow.protocol.OFPortConfig;
 import org.projectfloodlight.openflow.protocol.OFPortDesc;
 import org.projectfloodlight.openflow.protocol.OFPortState;
@@ -117,13 +118,14 @@
             }
             DeviceId did = deviceId(uri(dpid));
             OpenFlowSwitch sw = controller.getSwitch(dpid);
-
+            ChassisId cId = new ChassisId(dpid.value());
             DeviceDescription description =
                     new DefaultDeviceDescription(did.uri(), Device.Type.SWITCH,
                                                  sw.manfacturerDescription(),
                                                  sw.hardwareDescription(),
                                                  sw.softwareDescription(),
-                                                 sw.serialNumber());
+                                                 sw.serialNumber(),
+                                                cId);
             providerService.deviceConnected(did, description);
             providerService.updatePorts(did, buildPortDescriptions(sw.getPorts()));
         }
diff --git a/providers/openflow/device/src/test/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProviderTest.java b/providers/openflow/device/src/test/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProviderTest.java
index 8196cb8..2c45c5e 100644
--- a/providers/openflow/device/src/test/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProviderTest.java
+++ b/providers/openflow/device/src/test/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProviderTest.java
@@ -59,7 +59,7 @@
     private static final List<OFPortDesc> PLIST = Lists.newArrayList(PD1, PD2);
 
     private static final Device DEV1 =
-            new DefaultDevice(PID, DID1, SWITCH, "", "", "", "");
+            new DefaultDevice(PID, DID1, SWITCH, "", "", "", "", null);
 
     private static final TestOpenFlowSwitch SW1 = new TestOpenFlowSwitch();
 
diff --git a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/OpenFlowRuleProvider.java b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/OpenFlowRuleProvider.java
index ac0bb61..a815f69 100644
--- a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/OpenFlowRuleProvider.java
+++ b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/OpenFlowRuleProvider.java
@@ -1,20 +1,9 @@
 package org.onlab.onos.provider.of.flow.impl;
 
-import static org.slf4j.LoggerFactory.getLogger;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicBoolean;
-
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -68,10 +57,20 @@
 import org.projectfloodlight.openflow.types.U32;
 import org.slf4j.Logger;
 
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Multimap;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.slf4j.LoggerFactory.getLogger;
 
 /**
  * Provider which uses an OpenFlow controller to detect network
@@ -166,6 +165,9 @@
         for (FlowRuleBatchEntry fbe : batch.getOperations()) {
             FlowRule flowRule = fbe.getTarget();
             OpenFlowSwitch sw = controller.getSwitch(Dpid.dpid(flowRule.deviceId().uri()));
+            if (sw == null) {
+                log.warn("WTF {}", flowRule.deviceId());
+            }
             sws.add(new Dpid(sw.getId()));
             FlowModBuilder builder = new FlowModBuilder(flowRule, sw.factory());
             switch (fbe.getOperator()) {
@@ -322,6 +324,7 @@
 
         public void fail(OFErrorMsg msg, Dpid dpid) {
             ok.set(false);
+            removeRequirement(dpid);
             FlowEntry fe = null;
             FlowRuleBatchEntry fbe = fms.get(msg.getXid());
             FlowRule offending = fbe.getTarget();
@@ -375,10 +378,7 @@
 
         public void satisfyRequirement(Dpid dpid) {
             log.warn("Satisfaction from switch {}", dpid);
-            sws.remove(dpid);
-            countDownLatch.countDown();
-            cleanUp();
-
+            removeRequirement(dpid);
         }
 
 
@@ -395,6 +395,7 @@
 
         @Override
         public boolean cancel(boolean mayInterruptIfRunning) {
+            ok.set(false);
             this.state = BatchState.CANCELLED;
             cleanUp();
             for (FlowRuleBatchEntry fbe : fms.values()) {
@@ -431,6 +432,7 @@
                 throws InterruptedException, ExecutionException,
                 TimeoutException {
             if (countDownLatch.await(timeout, unit)) {
+
                 this.state = BatchState.FINISHED;
                 return new CompletedBatchOperation(ok.get(), offendingFlowMods);
             }
@@ -438,7 +440,7 @@
         }
 
         private void cleanUp() {
-            if (sws.isEmpty()) {
+            if (isDone() || isCancelled()) {
                 pendingFutures.remove(pendingXid);
                 for (Long xid : fms.keySet()) {
                     pendingFMs.remove(xid);
@@ -446,6 +448,12 @@
             }
         }
 
+        private void removeRequirement(Dpid dpid) {
+            countDownLatch.countDown();
+            sws.remove(dpid);
+            cleanUp();
+        }
+
     }
 
 }
diff --git a/providers/openflow/link/src/main/java/org/onlab/onos/provider/of/link/impl/LinkDiscovery.java b/providers/openflow/link/src/main/java/org/onlab/onos/provider/of/link/impl/LinkDiscovery.java
index bc45f79..3319302 100644
--- a/providers/openflow/link/src/main/java/org/onlab/onos/provider/of/link/impl/LinkDiscovery.java
+++ b/providers/openflow/link/src/main/java/org/onlab/onos/provider/of/link/impl/LinkDiscovery.java
@@ -66,6 +66,7 @@
  * TODO: add 'fast discovery' mode: drop LLDPs in destination switch but listen
  * for flow_removed messages
  */
+@Deprecated
 public class LinkDiscovery implements TimerTask {
 
     private final OpenFlowSwitch sw;
diff --git a/providers/openflow/link/src/main/java/org/onlab/onos/provider/of/link/impl/OpenFlowLinkProvider.java b/providers/openflow/link/src/main/java/org/onlab/onos/provider/of/link/impl/OpenFlowLinkProvider.java
index f1e3861..7f16eaa 100644
--- a/providers/openflow/link/src/main/java/org/onlab/onos/provider/of/link/impl/OpenFlowLinkProvider.java
+++ b/providers/openflow/link/src/main/java/org/onlab/onos/provider/of/link/impl/OpenFlowLinkProvider.java
@@ -35,6 +35,7 @@
  * infrastructure links.
  */
 @Component(immediate = true)
+@Deprecated
 public class OpenFlowLinkProvider extends AbstractProvider implements LinkProvider {
 
     private final Logger log = getLogger(getClass());
diff --git a/providers/openflow/packet/src/main/java/org/onlab/onos/provider/of/packet/impl/OpenFlowPacketProvider.java b/providers/openflow/packet/src/main/java/org/onlab/onos/provider/of/packet/impl/OpenFlowPacketProvider.java
index 94f7a33..8be1fec 100644
--- a/providers/openflow/packet/src/main/java/org/onlab/onos/provider/of/packet/impl/OpenFlowPacketProvider.java
+++ b/providers/openflow/packet/src/main/java/org/onlab/onos/provider/of/packet/impl/OpenFlowPacketProvider.java
@@ -28,7 +28,6 @@
 import org.onlab.onos.openflow.controller.OpenFlowPacketContext;
 import org.onlab.onos.openflow.controller.OpenFlowSwitch;
 import org.onlab.onos.openflow.controller.PacketListener;
-import org.onlab.packet.Ethernet;
 import org.projectfloodlight.openflow.protocol.OFPacketOut;
 import org.projectfloodlight.openflow.protocol.OFPortDesc;
 import org.projectfloodlight.openflow.protocol.action.OFAction;
@@ -96,13 +95,13 @@
             return;
         }
 
-        Ethernet eth = new Ethernet();
-        eth.deserialize(packet.data().array(), 0, packet.data().array().length);
+        //Ethernet eth = new Ethernet();
+        //eth.deserialize(packet.data().array(), 0, packet.data().array().length);
         OFPortDesc p = null;
         for (Instruction inst : packet.treatment().instructions()) {
             if (inst.type().equals(Instruction.Type.OUTPUT)) {
                 p = portDesc(((OutputInstruction) inst).port());
-                OFPacketOut po = packetOut(sw, eth, p.getPortNo());
+                OFPacketOut po = packetOut(sw, packet.data().array(), p.getPortNo());
                 sw.sendMsg(po);
             }
         }
@@ -116,7 +115,7 @@
         return builder.build();
     }
 
-    private OFPacketOut packetOut(OpenFlowSwitch sw, Ethernet eth, OFPort out) {
+    private OFPacketOut packetOut(OpenFlowSwitch sw, byte[] eth, OFPort out) {
         OFPacketOut.Builder builder = sw.factory().buildPacketOut();
         OFAction act = sw.factory().actions()
                 .buildOutput()
@@ -126,7 +125,7 @@
                 .setBufferId(OFBufferId.NO_BUFFER)
                 .setInPort(OFPort.NO_MASK)
                 .setActions(Collections.singletonList(act))
-                .setData(eth.serialize())
+                .setData(eth)
                 .build();
     }
 
diff --git a/providers/pom.xml b/providers/pom.xml
index da63b72..ff990de 100644
--- a/providers/pom.xml
+++ b/providers/pom.xml
@@ -18,6 +18,7 @@
 
     <modules>
         <module>openflow</module>
+        <module>lldp</module>
     </modules>
 
     <dependencies>
diff --git a/tools/test/cells/office b/tools/test/cells/office
index 72520a0..7c6345b 100644
--- a/tools/test/cells/office
+++ b/tools/test/cells/office
@@ -2,8 +2,8 @@
 
 export ONOS_CELL="office"
 
-export ONOS_NIC="10.128.4.*"
-export OC1="10.128.4.60"
+export ONOS_NIC="10.1.10.*"
+export OC1="10.1.10.223"
 export OCI="${OC1}"
 
 export ONOS_FEATURES="webconsole,onos-api,onos-core-trivial,onos-cli,onos-openflow,onos-app-fwd,onos-app-mobility,onos-app-tvue,onos-app-proxyarp"
diff --git a/utils/misc/src/main/java/org/onlab/packet/ChassisId.java b/utils/misc/src/main/java/org/onlab/packet/ChassisId.java
new file mode 100644
index 0000000..3029647
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/ChassisId.java
@@ -0,0 +1,74 @@
+package org.onlab.packet;
+
+/**
+ * The class representing a network device chassisId.
+ * This class is immutable.
+ */
+// TODO: Move this to a reasonable place.
+public final class ChassisId {
+
+    private static final long UNKNOWN = 0;
+    private final long value;
+
+    /**
+     * Default constructor.
+     */
+    public ChassisId() {
+        this.value = ChassisId.UNKNOWN;
+    }
+
+    /**
+     * Constructor from a long value.
+     *
+     * @param value the value to use.
+     */
+    public ChassisId(long value) {
+        this.value = value;
+    }
+
+    /**
+     * Constructor from a string.
+     *
+     * @param value the value to use.
+     */
+    public ChassisId(String value) {
+        this.value = Long.valueOf(value);
+    }
+
+    /**
+     * Get the value of the chassis id.
+     *
+     * @return the value of the chassis id.
+     */
+    public long value() {
+        return value;
+    }
+
+    /**
+     * Convert the Chassis Id value to a ':' separated hexadecimal string.
+     *
+     * @return the Chassis Id value as a ':' separated hexadecimal string.
+     */
+    @Override
+    public String toString() {
+        return Long.toHexString(this.value);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof ChassisId)) {
+            return false;
+        }
+
+        ChassisId otherChassisId = (ChassisId) other;
+
+        return value == otherChassisId.value;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 17;
+        hash += 31 * hash + (int) (value ^ value >>> 32);
+        return hash;
+    }
+}
diff --git a/utils/misc/src/main/java/org/onlab/packet/Ethernet.java b/utils/misc/src/main/java/org/onlab/packet/Ethernet.java
index 7587a54..eecdb53 100644
--- a/utils/misc/src/main/java/org/onlab/packet/Ethernet.java
+++ b/utils/misc/src/main/java/org/onlab/packet/Ethernet.java
@@ -58,6 +58,7 @@
         Ethernet.etherTypeClassMap.put(Ethernet.TYPE_RARP, ARP.class);
         Ethernet.etherTypeClassMap.put(Ethernet.TYPE_IPV4, IPv4.class);
         Ethernet.etherTypeClassMap.put(Ethernet.TYPE_LLDP, LLDP.class);
+        Ethernet.etherTypeClassMap.put(Ethernet.TYPE_BSN, LLDP.class);
     }
 
     protected MacAddress destinationMACAddress;
diff --git a/utils/misc/src/main/java/org/onlab/packet/LLDP.java b/utils/misc/src/main/java/org/onlab/packet/LLDP.java
index 105a9f3..7277cda 100644
--- a/utils/misc/src/main/java/org/onlab/packet/LLDP.java
+++ b/utils/misc/src/main/java/org/onlab/packet/LLDP.java
@@ -150,7 +150,7 @@
         final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
         LLDPTLV tlv;
         do {
-            tlv = new LLDPTLV().deserialize(bb);
+            tlv = new LLDPOrganizationalTLV().deserialize(bb);
 
             // if there was a failure to deserialize stop processing TLVs
             if (tlv == null) {
@@ -169,6 +169,7 @@
             case 0x3:
                 this.ttl = tlv;
                 break;
+
             default:
                 this.optionalTLVList.add(tlv);
                 break;
diff --git a/utils/misc/src/main/java/org/onlab/packet/LLDPOrganizationalTLV.java b/utils/misc/src/main/java/org/onlab/packet/LLDPOrganizationalTLV.java
index fb359a4..4d4e0a4 100644
--- a/utils/misc/src/main/java/org/onlab/packet/LLDPOrganizationalTLV.java
+++ b/utils/misc/src/main/java/org/onlab/packet/LLDPOrganizationalTLV.java
@@ -140,6 +140,9 @@
 
     @Override
     public byte[] serialize() {
+        if (this.type != LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE) {
+            return super.serialize();
+        }
         final int valueLength = LLDPOrganizationalTLV.OUI_LENGTH
                 + LLDPOrganizationalTLV.SUBTYPE_LENGTH + this.infoString.length;
         this.value = new byte[valueLength];
@@ -152,7 +155,11 @@
 
     @Override
     public LLDPTLV deserialize(final ByteBuffer bb) {
-        super.deserialize(bb);
+        LLDPTLV tlv = super.deserialize(bb);
+        if (tlv.getType() != LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE) {
+            return tlv;
+        }
+
         final ByteBuffer optionalField = ByteBuffer.wrap(this.value);
 
         final byte[] oui = new byte[LLDPOrganizationalTLV.OUI_LENGTH];
diff --git a/utils/misc/src/main/java/org/onlab/packet/LLDPTLV.java b/utils/misc/src/main/java/org/onlab/packet/LLDPTLV.java
index 04f89a0..16c9e31 100644
--- a/utils/misc/src/main/java/org/onlab/packet/LLDPTLV.java
+++ b/utils/misc/src/main/java/org/onlab/packet/LLDPTLV.java
@@ -111,6 +111,7 @@
         sscratch = bb.getShort();
         this.type = (byte) (sscratch >> 9 & 0x7f);
         this.length = (short) (sscratch & 0x1ff);
+
         if (this.length > 0) {
             this.value = new byte[this.length];
 
@@ -120,6 +121,7 @@
             }
             bb.get(this.value);
         }
+
         return this;
     }
 
diff --git a/utils/misc/src/main/java/org/onlab/packet/ONLabLddp.java b/utils/misc/src/main/java/org/onlab/packet/ONLabLddp.java
index 37213d0..ecfcbd8 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ONLabLddp.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ONLabLddp.java
@@ -30,6 +30,7 @@
  * Refer to IEEE Std 802.1ABTM-2009 for more information.
  *
  */
+@Deprecated
 public class ONLabLddp extends LLDP {
 
     private static final Logger LOG = LoggerFactory.getLogger(ONLabLddp.class);
diff --git a/utils/misc/src/main/java/org/onlab/packet/ONOSLLDP.java b/utils/misc/src/main/java/org/onlab/packet/ONOSLLDP.java
new file mode 100644
index 0000000..ec35de8
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/ONOSLLDP.java
@@ -0,0 +1,169 @@
+package org.onlab.packet;
+
+import com.google.common.collect.Lists;
+import org.apache.commons.lang.ArrayUtils;
+
+import java.nio.ByteBuffer;
+
+/**
+ *  ONOS LLDP containing organizational TLV for ONOS device dicovery.
+ */
+public class ONOSLLDP extends LLDP {
+
+    public static final byte[] ONLAB_OUI = {(byte) 0xa4, 0x23, 0x05};
+    public static final String DEFAULT_DEVICE = "INVALID";
+    public static final String DEFAULT_NAME = "ONOS Discovery";
+
+    public static final byte[] LLDP_NICIRA = {0x01, 0x23, 0x20, 0x00, 0x00,
+            0x01};
+    public static final byte[] LLDP_MULTICAST = {0x01, (byte) 0x80,
+            (byte) 0xc2, 0x00, 0x00, 0x0e};
+    public static final byte[] BDDP_MULTICAST = {(byte) 0xff, (byte) 0xff,
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff};
+
+    private static final byte NAME_SUBTYPE = 1;
+    private static final byte DEVICE_SUBTYPE = 2;
+    private static final short NAME_LENGTH = 4; //1 for subtype + 3 for OUI
+    private static final short DEVICE_LENGTH = 4; //1 for subtype + 3 for OUI
+    private final LLDPOrganizationalTLV nameTLV = new LLDPOrganizationalTLV();
+    private final LLDPOrganizationalTLV deviceTLV =  new LLDPOrganizationalTLV();
+
+    // TLV constants: type, size and subtype
+    // Organizationally specific TLV also have packet offset and contents of TLV
+    // header
+    private static final byte CHASSIS_TLV_TYPE = 1;
+    private static final byte CHASSIS_TLV_SIZE = 7;
+    private static final byte CHASSIS_TLV_SUBTYPE = 4;
+
+    private static final byte PORT_TLV_TYPE = 2;
+    private static final byte PORT_TLV_SIZE = 5;
+    private static final byte PORT_TLV_SUBTYPE = 2;
+
+    private static final byte TTL_TLV_TYPE = 3;
+
+
+    private final byte[] ttlValue = new byte[] {0, 0x78};
+
+    public ONOSLLDP() {
+        super();
+        setName(DEFAULT_NAME);
+        setDevice(DEFAULT_DEVICE);
+        setOptionalTLVList(Lists.<LLDPTLV>newArrayList(nameTLV, deviceTLV));
+        setTtl(new LLDPTLV().setType((byte) TTL_TLV_TYPE)
+                       .setLength((short) ttlValue.length)
+                       .setValue(ttlValue));
+
+    }
+
+    private ONOSLLDP(LLDP lldp) {
+        this.portId = lldp.getPortId();
+        this.chassisId = lldp.getChassisId();
+        this.ttl = lldp.getTtl();
+        this.optionalTLVList = lldp.getOptionalTLVList();
+    }
+
+    public void setName(String name) {
+        nameTLV.setLength((short) (name.length() + NAME_LENGTH));
+        nameTLV.setInfoString(name);
+        nameTLV.setSubType(NAME_SUBTYPE);
+        nameTLV.setOUI(ONLAB_OUI);
+    }
+
+    public void setDevice(String device) {
+        deviceTLV.setInfoString(device);
+        deviceTLV.setLength((short) (device.length() + DEVICE_LENGTH));
+        deviceTLV.setSubType(DEVICE_SUBTYPE);
+        deviceTLV.setOUI(ONLAB_OUI);
+    }
+
+    public void setChassisId(final ChassisId chassisId) {
+        MacAddress chassisMac = MacAddress.valueOf(chassisId.value());
+        byte[] chassis = ArrayUtils.addAll(new byte[] {CHASSIS_TLV_SUBTYPE},
+                                           chassisMac.getAddress());
+
+        LLDPTLV chassisTLV = new LLDPTLV();
+        chassisTLV.setLength(CHASSIS_TLV_SIZE);
+        chassisTLV.setType(CHASSIS_TLV_TYPE);
+        chassisTLV.setValue(chassis);
+        this.setChassisId(chassisTLV);
+    }
+
+    public void setPortId(final int portNumber) {
+        byte[] port = ArrayUtils.addAll(new byte[] {PORT_TLV_SUBTYPE},
+                                        ByteBuffer.allocate(4).putInt(portNumber).array());
+
+        LLDPTLV portTLV = new LLDPTLV();
+        portTLV.setLength(PORT_TLV_SIZE);
+        portTLV.setType(PORT_TLV_TYPE);
+        portTLV.setValue(port);
+        this.setPortId(portTLV);
+    }
+
+    public LLDPOrganizationalTLV getNameTLV() {
+        for (LLDPTLV tlv : this.getOptionalTLVList()) {
+            if (tlv.getType() == LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE) {
+                LLDPOrganizationalTLV orgTLV =  (LLDPOrganizationalTLV) tlv;
+                if (orgTLV.getSubType() == NAME_SUBTYPE) {
+                    return orgTLV;
+                }
+            }
+        }
+        return null;
+    }
+
+    public LLDPOrganizationalTLV getDeviceTLV() {
+        for (LLDPTLV tlv : this.getOptionalTLVList()) {
+            if (tlv.getType() == LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE) {
+                LLDPOrganizationalTLV orgTLV =  (LLDPOrganizationalTLV) tlv;
+                if (orgTLV.getSubType() == DEVICE_SUBTYPE) {
+                    return orgTLV;
+                }
+            }
+        }
+        return null;
+    }
+
+    public String getNameString() {
+        LLDPOrganizationalTLV tlv = getNameTLV();
+        if (tlv != null) {
+            return new String(tlv.getInfoString());
+        }
+        return null;
+    }
+
+    public String getDeviceString() {
+        LLDPOrganizationalTLV tlv = getDeviceTLV();
+        if (tlv != null) {
+            return new String(tlv.getInfoString());
+        }
+        return null;
+    }
+
+    public Integer getPort() {
+        ByteBuffer portBB = ByteBuffer.wrap(this.getPortId().getValue());
+        portBB.position(1);
+        return portBB.getInt();
+    }
+
+    /**
+     * Given an ethernet packet, determines if this is an LLDP from
+     * ONOS and returns the device the LLDP came from.
+     * @param eth an ethernet packet
+     * @return a the lldp packet or null
+     */
+    public static ONOSLLDP parseONOSLLDP(Ethernet eth) {
+        if (eth.getEtherType() == Ethernet.TYPE_LLDP ||
+                eth.getEtherType() == Ethernet.TYPE_BSN) {
+           ONOSLLDP onosLldp = new ONOSLLDP((LLDP) eth.getPayload()); //(ONOSLLDP) eth.getPayload();
+           if (ONOSLLDP.DEFAULT_NAME.equals(onosLldp.getNameString())) {
+               return onosLldp;
+           }
+        }
+        return null;
+    }
+
+
+
+
+
+}