diff --git a/src/main/java/net/floodlightcontroller/devicemanager/IDevice.java b/src/main/java/net/floodlightcontroller/devicemanager/IDevice.java
index 4deba4b6277c86e0252b6249120df2ba7a31cf1d..22c3c44a53a1b3fcf98c1455110b090246a15ce7 100644
--- a/src/main/java/net/floodlightcontroller/devicemanager/IDevice.java
+++ b/src/main/java/net/floodlightcontroller/devicemanager/IDevice.java
@@ -19,6 +19,9 @@ package net.floodlightcontroller.devicemanager;
 
 import java.util.Date;
 
+import net.floodlightcontroller.devicemanager.internal.Entity;
+
+
 /**
  * Represents an independent device on the network.  A device consists of a 
  * set of entities, and all the information known about a given device comes
@@ -80,10 +83,15 @@ public interface IDevice {
     public Date getLastSeen();
     
     /**
-     * Get the entity classes for the device.
-     * @return the entity classes
-     * @see IEntityClassifier
-     * @see IDeviceService#setEntityClassifier(IEntityClassifier)
+     * Get the entity class for the device.
+     * @return the entity class
+     * @see IEntityClassifierService
+     */
+    public IEntityClass getEntityClass();
+    
+    /**
+     * Get the list of entities for this device
+     * @return
      */
-    public IEntityClass[] getEntityClasses();
+    public Entity[] getEntities();
 }
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/IDeviceService.java b/src/main/java/net/floodlightcontroller/devicemanager/IDeviceService.java
index 79cb2699bab7b8da1d6fc6700a7fbbeb1445442c..5d482c48133e1c09d0945bbc2d2117472523ee54 100755
--- a/src/main/java/net/floodlightcontroller/devicemanager/IDeviceService.java
+++ b/src/main/java/net/floodlightcontroller/devicemanager/IDeviceService.java
@@ -67,27 +67,36 @@ public interface IDeviceService extends IFloodlightService {
     public IDevice getDevice(Long deviceKey);
     
     /**
-     * Search for a device using exactly matching the provided device fields.
-     * Only the key fields as defined by the {@link IEntityClassifier} will
-     * be important in this search.
+     * Search for a device exactly matching the provided device fields. This 
+     * is the same lookup process that is used for packet_in processing and 
+     * device learning. Thus, findDevice() can be used to match flow entries
+     * from switches to devices. 
+     * Only the key fields as defined by the {@link IEntityClassifierService} will
+     * be important in this search. All key fields MUST be supplied. 
+     * 
+     *{@link queryDevices()} might be more appropriate!
      * 
      * @param macAddress The MAC address
-     * @param vlan the VLAN
+     * @param vlan the VLAN. Null means no VLAN and is valid even if VLAN is a 
+     *        key field.
      * @param ipv4Address the ipv4 address
      * @param switchDPID the switch DPID
      * @param switchPort the switch port
      * @return an {@link IDevice} or null if no device is found.
-     * @see IDeviceManager#setEntityClassifier(IEntityClassifier)
+     * @see IDeviceManager#setEntityClassifier(IEntityClassifierService)
+     * @throws IllegalArgumentException if not all key fields of the
+     * current {@link IEntityClassifierService} are specified.
      */
     public IDevice findDevice(long macAddress, Short vlan,
                               Integer ipv4Address, Long switchDPID,
-                              Integer switchPort);
+                              Integer switchPort)
+                              throws IllegalArgumentException;
     
     /**
      * Get a destination device using entity fields that corresponds with
      * the given source device.  The source device is important since
      * there could be ambiguity in the destination device without the
-     * attachment point information.
+     * attachment point information. 
      * 
      * @param source the source device.  The returned destination will be
      * in the same entity class as the source.
@@ -97,10 +106,13 @@ public interface IDeviceService extends IFloodlightService {
      * @return an {@link IDevice} or null if no device is found.
      * @see IDeviceService#findDevice(long, Short, Integer, Long, 
      * Integer)
+     * @throws IllegalArgumentException if not all key fields of the
+     * source's {@link IEntityClass} are specified.
      */
     public IDevice findDestDevice(IDevice source,
                                   long macAddress, Short vlan,
-                                  Integer ipv4Address);
+                                  Integer ipv4Address)
+                                  throws IllegalArgumentException;
 
     /**
      * Get an unmodifiable collection view over all devices currently known.
@@ -177,16 +189,7 @@ public interface IDeviceService extends IFloodlightService {
      */
     public void addListener(IDeviceListener listener);
     
-    /**
-     * Set the entity classifier for the device manager to use to
-     * differentiate devices on the network.  If no classifier is set,
-     * the {@link DefaultEntityClassifer} will be used.  This should be 
-     * registered in the application initialization phase before startup.
-     * 
-     * @param classifier the classifier to set.
-     */
-    public void setEntityClassifier(IEntityClassifier classifier);
-
+    
     /**
      * Flush and/or reclassify all entities in a class
      *
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/IEntityClass.java b/src/main/java/net/floodlightcontroller/devicemanager/IEntityClass.java
index 1053a79a1b9cc54849f917576573e103a841dc50..bb077f116ce3094151fdd543af680aa8c9828c42 100644
--- a/src/main/java/net/floodlightcontroller/devicemanager/IEntityClass.java
+++ b/src/main/java/net/floodlightcontroller/devicemanager/IEntityClass.java
@@ -28,7 +28,7 @@ import net.floodlightcontroller.devicemanager.internal.Device;
  * class. A set of entities are considered to be the same device if and only 
  * if they belong to the same entity class and they match on all key fields 
  * for that entity class. A field is effectively wildcarded by not including 
- * it in the list of key fields returned by {@link IEntityClassifier} and/or 
+ * it in the list of key fields returned by {@link IEntityClassifierService} and/or 
  * {@link IEntityClass}.
  * 
  * Note that if you're not using static objects, you'll need to override
@@ -49,5 +49,11 @@ public interface IEntityClass {
      * be wildcarded.  May be null to indicate that all fields are key fields.
      */
     EnumSet<DeviceField> getKeyFields();
+    
+    /**
+     * Returns a user-friendly, unique name for this EntityClass
+     * @return the name of the entity class
+     */
+    String getName();
 }
 
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassifier.java b/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassifierService.java
similarity index 83%
rename from src/main/java/net/floodlightcontroller/devicemanager/IEntityClassifier.java
rename to src/main/java/net/floodlightcontroller/devicemanager/IEntityClassifierService.java
index 31cf4a70dfa39690adcb8dcd399a498976841164..8d693ce9220155f92ef2aef5ecfec785eee78c84 100644
--- a/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassifier.java
+++ b/src/main/java/net/floodlightcontroller/devicemanager/IEntityClassifierService.java
@@ -19,6 +19,8 @@ package net.floodlightcontroller.devicemanager;
 
 import java.util.Collection;
 import java.util.EnumSet;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
 import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
 import net.floodlightcontroller.devicemanager.internal.Entity;
 
@@ -30,32 +32,27 @@ import net.floodlightcontroller.devicemanager.internal.Entity;
  * 
  * @author readams
  */
-public interface IEntityClassifier {
+public interface IEntityClassifierService extends IFloodlightService {
     /**
-    * Classify the given entity into a set of classes.  It is important
-    * that the key fields returned by {@link IEntityClassifier#getKeyFields()}
+    * Classify the given entity into an IEntityClass.  It is important
+    * that the key fields returned by {@link IEntityClassifierService#getKeyFields()}
     * be sufficient for classifying entities.  That is, if two entities are
     * identical except for a field that is not a key field, they must be
     * assigned the same class.  Furthermore, entity classification must be
     * transitive: For all entities x, y, z, if x and y belong to a class c, and 
     * y and z belong class c, then x and z must belong to class c.
     * 
-    * <p>Note further that you must take steps to ensure you always return
-    * classes in some consistent ordering.  This could be achieved by sorting
-    * the returned collection before returning it.
-    * 
     * @param entity the entity to classify
-    * @return the IEntityClass resulting from the classification.  When
-    * iterating, must return results in a sorted order.
-    * @see IEntityClassifier#getKeyFields()
+    * @return the IEntityClass resulting from the classification.
+    * @see IEntityClassifierService#getKeyFields()
     */
-   Collection<IEntityClass> classifyEntity(Entity entity);
+   IEntityClass classifyEntity(Entity entity);
 
    /**
     * Return the most general list of fields that should be used as key 
     * fields.  If devices differ in any fields not listed here, they can
     * never be considered a different device by any {@link IEntityClass} 
-    * returned by {@link IEntityClassifier#classifyEntity}.  The key fields
+    * returned by {@link IEntityClassifierService#classifyEntity}.  The key fields
     * for an entity classifier must not change unless associated with a 
     * flush of all entity state.  The list of key fields must be the union
     * of all key fields that could be returned by
@@ -64,7 +61,7 @@ public interface IEntityClassifier {
     * @return a set containing the fields that should not be
     * wildcarded.  May be null to indicate that all fields are key fields.
     * @see {@link IEntityClass#getKeyFields()}
-    * @see {@link IEntityClassifier#classifyEntity}
+    * @see {@link IEntityClassifierService#classifyEntity}
     */
    EnumSet<DeviceField> getKeyFields();
 
@@ -83,7 +80,7 @@ public interface IEntityClassifier {
     * @param entity the entity to reclassify
     * @return the IEntityClass resulting from the classification
     */
-   Collection<IEntityClass> reclassifyEntity(IDevice curDevice,
+   IEntityClass reclassifyEntity(IDevice curDevice,
                                              Entity entity);
 
    /**
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/DefaultEntityClassifier.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/DefaultEntityClassifier.java
index 32bbeabb63eb317862d16f90f3aac5119e2742b4..353a72d1b5b8a3e382feb895d6ac24e10bc9c8e8 100644
--- a/src/main/java/net/floodlightcontroller/devicemanager/internal/DefaultEntityClassifier.java
+++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/DefaultEntityClassifier.java
@@ -17,22 +17,31 @@
 
 package net.floodlightcontroller.devicemanager.internal;
 
-import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
 
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
 import net.floodlightcontroller.devicemanager.IDevice;
 import net.floodlightcontroller.devicemanager.IDeviceService;
 import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
 import net.floodlightcontroller.devicemanager.IEntityClass;
-import net.floodlightcontroller.devicemanager.IEntityClassifier;
+import net.floodlightcontroller.devicemanager.IEntityClassifierService;
 
 /**
  * This is a default entity classifier that simply classifies all
  * entities into a fixed entity class, with key fields of MAC and VLAN.
  * @author readams
  */
-public class DefaultEntityClassifier implements IEntityClassifier {
+public class DefaultEntityClassifier implements
+        IEntityClassifierService,
+        IFloodlightModule 
+{
     /**
      * A default fixed entity class
      */
@@ -41,6 +50,11 @@ public class DefaultEntityClassifier implements IEntityClassifier {
         public EnumSet<IDeviceService.DeviceField> getKeyFields() {
             return keyFields;
         }
+
+        @Override
+        public String getName() {
+            return "DefaultEntityClass";
+        }
     }
     
     protected static EnumSet<DeviceField> keyFields;
@@ -49,20 +63,15 @@ public class DefaultEntityClassifier implements IEntityClassifier {
     }
     protected static IEntityClass entityClass = new DefaultEntityClass();
     
-    public static Collection<IEntityClass> entityClasses;
-    static {
-        entityClasses = Arrays.asList(entityClass);
-    }
-
     @Override
-    public Collection<IEntityClass> classifyEntity(Entity entity) {
-        return entityClasses;
+    public IEntityClass classifyEntity(Entity entity) {
+        return entityClass;
     }
 
     @Override
-    public Collection<IEntityClass> reclassifyEntity(IDevice curDevice,
+    public IEntityClass reclassifyEntity(IDevice curDevice,
                                                      Entity entity) {
-        return entityClasses;
+        return entityClass;
     }
 
     @Override
@@ -75,4 +84,41 @@ public class DefaultEntityClassifier implements IEntityClassifier {
     public EnumSet<DeviceField> getKeyFields() {
         return keyFields;
     }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+        Collection<Class<? extends IFloodlightService>> l = 
+                new ArrayList<Class<? extends IFloodlightService>>();
+        l.add(IEntityClassifierService.class);
+        return l;
+    }
+
+    @Override
+    public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() {
+        Map<Class<? extends IFloodlightService>,
+        IFloodlightService> m = 
+        new HashMap<Class<? extends IFloodlightService>,
+                    IFloodlightService>();
+        // We are the class that implements the service
+        m.put(IEntityClassifierService.class, this);
+        return m;
+    }
+
+    @Override
+    public Collection<Class<? extends IFloodlightService>>
+            getModuleDependencies() {
+        // No dependencies
+        return null;
+    }
+
+    @Override
+    public void init(FloodlightModuleContext context)
+                                                 throws FloodlightModuleException {
+        // no-op
+    }
+
+    @Override
+    public void startUp(FloodlightModuleContext context) {
+        // no-op
+    }
 }
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java
index 447be63f3cf0164c5ab84f62386a11dc7aca1247..b0b33794f2e0dfe30d633609951ccc5a6f6a54c3 100755
--- a/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java
+++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/Device.java
@@ -27,7 +27,6 @@ import java.util.TreeSet;
 
 import org.codehaus.jackson.map.annotate.JsonSerialize;
 import org.openflow.util.HexString;
-
 import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
 import net.floodlightcontroller.devicemanager.web.DeviceSerializer;
 import net.floodlightcontroller.devicemanager.IDevice;
@@ -46,7 +45,7 @@ public class Device implements IDevice {
     protected DeviceManagerImpl deviceManager;
 
     protected Entity[] entities;
-    protected IEntityClass[] entityClasses;
+    protected IEntityClass entityClass;
 
     protected String macAddressString;
 
@@ -59,19 +58,18 @@ public class Device implements IDevice {
      * @param deviceManager the device manager for this device
      * @param deviceKey the unique identifier for this device object
      * @param entity the initial entity for the device
-     * @param entityClasses the entity classes associated with the entity
+     * @param entityClass the entity classes associated with the entity
      */
     public Device(DeviceManagerImpl deviceManager,
                   Long deviceKey,
                   Entity entity,
-                  Collection<IEntityClass> entityClasses) {
+                  IEntityClass entityClass) {
         this.deviceManager = deviceManager;
         this.deviceKey = deviceKey;
         this.entities = new Entity[] {entity};
         this.macAddressString =
                 HexString.toHexString(entity.getMacAddress(), 6);
-        this.entityClasses =
-                entityClasses.toArray(new IEntityClass[entityClasses.size()]);
+        this.entityClass = entityClass;
         Arrays.sort(this.entities);
     }
 
@@ -80,18 +78,18 @@ public class Device implements IDevice {
      * @param deviceManager the device manager for this device
      * @param deviceKey the unique identifier for this device object
      * @param entities the initial entities for the device
-     * @param entityClasses the entity classes associated with the entity
+     * @param entityClass the entity class associated with the entities
      */
     public Device(DeviceManagerImpl deviceManager,
                   Long deviceKey,
                   Collection<Entity> entities,
-                  IEntityClass[] entityClasses) {
+                  IEntityClass entityClass) {
         this.deviceManager = deviceManager;
         this.deviceKey = deviceKey;
         this.entities = entities.toArray(new Entity[entities.size()]);
         this.macAddressString =
                 HexString.toHexString(this.entities[0].getMacAddress(), 6);
-        this.entityClasses = entityClasses;
+        this.entityClass = entityClass;
         Arrays.sort(this.entities);
     }
 
@@ -99,12 +97,11 @@ public class Device implements IDevice {
      * Construct a new device consisting of the entities from the old device
      * plus an additional entity
      * @param device the old device object
-     * @param newEntity the entity to add
-     * @param entityClasses the entity classes associated with the entities
+     * @param newEntity the entity to add. newEntity must be have the same
+     *        entity class as device
      */
     public Device(Device device,
-                  Entity newEntity,
-                  Collection<IEntityClass> entityClasses) {
+                  Entity newEntity) {
         this.deviceManager = device.deviceManager;
         this.deviceKey = device.deviceKey;
         this.entities = Arrays.<Entity>copyOf(device.entities,
@@ -115,15 +112,7 @@ public class Device implements IDevice {
         this.macAddressString =
                 HexString.toHexString(this.entities[0].getMacAddress(), 6);
 
-        if (entityClasses != null &&
-                entityClasses.size() > device.entityClasses.length) {
-            IEntityClass[] classes = new IEntityClass[entityClasses.size()];
-            this.entityClasses =
-                    entityClasses.toArray(classes);
-        } else {
-            // same actual array, not a copy
-            this.entityClasses = device.entityClasses;
-        }
+        this.entityClass = device.entityClass;
     }
 
     // *******
@@ -173,43 +162,33 @@ public class Device implements IDevice {
         // XXX - TODO we can cache this result.  Let's find out if this
         // is really a performance bottleneck first though.
 
-        if (entities.length == 1) {
-            if (entities[0].getIpv4Address() != null) {
-                return new Integer[]{ entities[0].getIpv4Address() };
-            } else {
-                return new Integer[0];
-            }
-        }
-
         TreeSet<Integer> vals = new TreeSet<Integer>();
         for (Entity e : entities) {
             if (e.getIpv4Address() == null) continue;
-
+            
             // We have an IP address only if among the devices within the class
             // we have the most recent entity with that IP.
             boolean validIP = true;
-            for (IEntityClass clazz : entityClasses) {
-                Iterator<Device> devices =
-                        deviceManager.queryClassByEntity(clazz, ipv4Fields, e);
-                while (devices.hasNext()) {
-                    Device d = devices.next();
-                    for (Entity se : d.entities) {
-                        if (se.ipv4Address != null &&
-                                se.ipv4Address.equals(e.ipv4Address) &&
-                                se.lastSeenTimestamp != null &&
-                                0 < se.lastSeenTimestamp.
-                                compareTo(e.lastSeenTimestamp)) {
-                            validIP = false;
-                            break;
-                        }
-                    }
-                    if (!validIP)
+            Iterator<Device> devices =
+                    deviceManager.queryClassByEntity(entityClass, ipv4Fields, e);
+            while (devices.hasNext()) {
+                Device d = devices.next();
+                if (deviceKey.equals(d.getDeviceKey())) 
+                    continue;
+                for (Entity se : d.entities) {
+                    if (se.getIpv4Address() != null &&
+                            se.getIpv4Address().equals(e.getIpv4Address()) &&
+                            se.getLastSeenTimestamp() != null &&
+                            0 < se.getLastSeenTimestamp().
+                            compareTo(e.getLastSeenTimestamp())) {
+                        validIP = false;
                         break;
+                    }
                 }
                 if (!validIP)
                     break;
             }
-
+            
             if (validIP)
                 vals.add(e.getIpv4Address());
         }
@@ -268,7 +247,7 @@ public class Device implements IDevice {
                     )
                 continue;
             long curCluster =
-                    topology.getL2DomainId(cur.switchDPID);
+                    topology.getL2DomainId(cur.getSwitchDPID());
             if (prevCluster != curCluster) {
                 prev = null;
                 latestLastSeen = 0;
@@ -379,10 +358,12 @@ public class Device implements IDevice {
     // Getters/Setters
     // ***************
 
-    public IEntityClass[] getEntityClasses() {
-        return entityClasses;
+    @Override
+    public IEntityClass getEntityClass() {
+        return entityClass;
     }
 
+    @Override
     public Entity[] getEntities() {
         return entities;
     }
@@ -425,6 +406,7 @@ public class Device implements IDevice {
 
     @Override
     public String toString() {
-        return "Device [entities=" + Arrays.toString(entities) + "]";
+        return "Device [entityClass=" + entityClass.getName() +
+                " entities=" + Arrays.toString(entities) + "]";
     }
 }
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIterator.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIterator.java
index 795775c15f1f5d0852aa4fb362b4dd5ced3600ec..2cbea66e5577495461171661f479c4f26335f11f 100644
--- a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIterator.java
+++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceIterator.java
@@ -67,37 +67,34 @@ public class DeviceIterator extends FilterIterator<Device> {
     protected boolean matches(Device value) {
         boolean match;
         if (entityClasses != null) {
-            IEntityClass[] classes = next.getEntityClasses();
-            if (classes == null) return false;
+            IEntityClass clazz = value.getEntityClass();
+            if (clazz == null) return false;
 
             match = false;
-            for (IEntityClass clazz : classes) {
-                for (IEntityClass entityClass : entityClasses) {
-                    if (clazz.equals(entityClass)) {
-                        match = true;
-                        break;
-                    }
+            for (IEntityClass entityClass : entityClasses) {
+                if (clazz.equals(entityClass)) {
+                    match = true;
+                    break;
                 }
-                if (match == true) break;
             }
             if (!match) return false;                
         }
         if (macAddress != null) {
-            if (macAddress.longValue() != next.getMACAddress())
+            if (macAddress.longValue() != value.getMACAddress())
                 return false;
         }
         if (vlan != null) {
-            Short[] vlans = next.getVlanId();
+            Short[] vlans = value.getVlanId();
             if (Arrays.binarySearch(vlans, vlan) < 0) 
                 return false;
         }
         if (ipv4Address != null) {
-            Integer[] ipv4Addresses = next.getIPv4Addresses();
+            Integer[] ipv4Addresses = value.getIPv4Addresses();
             if (Arrays.binarySearch(ipv4Addresses, ipv4Address) < 0) 
                 return false;
         }
         if (switchDPID != null || switchPort != null) {
-            SwitchPort[] sps = next.getAttachmentPoints();
+            SwitchPort[] sps = value.getAttachmentPoints();
             if (sps == null) return false;
             
             match = false;
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java
index ab1d8788dbfd91ff7802b0622bee662e260cc3ee..7592332f1a544344e3d9d24943a8e0e755c8ffec 100755
--- a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java
+++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java
@@ -18,7 +18,6 @@
 package net.floodlightcontroller.devicemanager.internal;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Collection;
 import java.util.Collections;
@@ -50,7 +49,7 @@ import net.floodlightcontroller.core.util.SingletonTask;
 import net.floodlightcontroller.devicemanager.IDevice;
 import net.floodlightcontroller.devicemanager.IDeviceService;
 import net.floodlightcontroller.devicemanager.IEntityClass;
-import net.floodlightcontroller.devicemanager.IEntityClassifier;
+import net.floodlightcontroller.devicemanager.IEntityClassifierService;
 import net.floodlightcontroller.devicemanager.IDeviceListener;
 import net.floodlightcontroller.devicemanager.SwitchPort;
 import net.floodlightcontroller.devicemanager.web.DeviceRoutable;
@@ -157,7 +156,7 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
     /**
      * The entity classifier currently in use
      */
-    IEntityClassifier entityClassifier;
+    protected IEntityClassifierService entityClassifier;
 
     /**
      * Used to cache state about specific entity classes
@@ -316,30 +315,38 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
     @Override
     public IDevice findDevice(long macAddress, Short vlan,
                               Integer ipv4Address, Long switchDPID,
-                              Integer switchPort) {
+                              Integer switchPort)
+                              throws IllegalArgumentException {
         if (vlan != null && vlan.shortValue() <= 0)
             vlan = null;
         if (ipv4Address != null && ipv4Address == 0)
             ipv4Address = null;
-        return findDeviceByEntity(new Entity(macAddress, vlan,
-                                             ipv4Address, switchDPID,
-                                             switchPort, null));
+        Entity e = new Entity(macAddress, vlan, ipv4Address, switchDPID,
+                              switchPort, null);
+        if (!allKeyFieldsPresent(e, entityClassifier.getKeyFields())) {
+            throw new IllegalArgumentException("Not all key fields specified."
+                      + " Required fields: " + entityClassifier.getKeyFields());
+        }
+        return findDeviceByEntity(e);
     }
 
     @Override
     public IDevice findDestDevice(IDevice source, long macAddress,
-                                  Short vlan, Integer ipv4Address) {
+                                  Short vlan, Integer ipv4Address) 
+                                  throws IllegalArgumentException {
         if (vlan != null && vlan.shortValue() <= 0)
             vlan = null;
         if (ipv4Address != null && ipv4Address == 0)
             ipv4Address = null;
-        return findDestByEntity(source,
-                                new Entity(macAddress,
-                                           vlan,
-                                           ipv4Address,
-                                           null,
-                                           null,
-                                           null));
+        Entity e = new Entity(macAddress, vlan, ipv4Address,
+                              null, null, null);
+        if (source == null || 
+                !allKeyFieldsPresent(e, source.getEntityClass().getKeyFields())) {
+            throw new IllegalArgumentException("Not all key fields and/or "
+                    + " no source device specified. Required fields: " + 
+                    entityClassifier.getKeyFields());
+        }
+        return findDestByEntity(source, e);
     }
 
     @Override
@@ -406,48 +413,46 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
                                                          Integer ipv4Address,
                                                          Long switchDPID,
                                                          Integer switchPort) {
-        IEntityClass[] entityClasses = reference.getEntityClasses();
+        IEntityClass entityClass = reference.getEntityClass();
         ArrayList<Iterator<Device>> iterators =
                 new ArrayList<Iterator<Device>>();
-        for (IEntityClass clazz : entityClasses) {
-            ClassState classState = getClassState(clazz);
-
-            DeviceIndex index = null;
-            if (classState.secondaryIndexMap.size() > 0) {
-                EnumSet<DeviceField> keys =
-                        getEntityKeys(macAddress, vlan, ipv4Address,
-                                      switchDPID, switchPort);
-                index = classState.secondaryIndexMap.get(keys);
-            }
-
-            Iterator<Device> iter;
+        ClassState classState = getClassState(entityClass);
+        
+        DeviceIndex index = null;
+        if (classState.secondaryIndexMap.size() > 0) {
+            EnumSet<DeviceField> keys =
+                    getEntityKeys(macAddress, vlan, ipv4Address,
+                                  switchDPID, switchPort);
+            index = classState.secondaryIndexMap.get(keys);
+        }
+        
+        Iterator<Device> iter;
+        if (index == null) {
+            index = classState.classIndex;
             if (index == null) {
-                index = classState.classIndex;
-                if (index == null) {
-                    // scan all devices
-                    return new DeviceIterator(deviceMap.values().iterator(),
-                                              entityClasses,
-                                              macAddress, vlan, ipv4Address,
-                                              switchDPID, switchPort);
-                } else {
-                    // scan the entire class
-                    iter = new DeviceIndexInterator(this, index.getAll());
-                }
+                // scan all devices
+                return new DeviceIterator(deviceMap.values().iterator(),
+                                          new IEntityClass[] { entityClass },
+                                          macAddress, vlan, ipv4Address,
+                                          switchDPID, switchPort);
             } else {
-                // index lookup
-                Entity entity =
-                        new Entity((macAddress == null ? 0 : macAddress),
-                                   vlan,
-                                   ipv4Address,
-                                   switchDPID,
-                                   switchPort,
-                                   null);
-                iter = new DeviceIndexInterator(this,
-                                                index.queryByEntity(entity));
+                // scan the entire class
+                iter = new DeviceIndexInterator(this, index.getAll());
             }
-            iterators.add(iter);
+        } else {
+            // index lookup
+            Entity entity =
+                    new Entity((macAddress == null ? 0 : macAddress),
+                               vlan,
+                               ipv4Address,
+                               switchDPID,
+                               switchPort,
+                               null);
+            iter = new DeviceIndexInterator(this,
+                                            index.queryByEntity(entity));
         }
-
+        iterators.add(iter);
+        
         return new MultiIterator<Device>(iterators.iterator());
     }
 
@@ -456,11 +461,6 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
         deviceListeners.add(listener);
     }
 
-    @Override
-    public void setEntityClassifier(IEntityClassifier classifier) {
-        entityClassifier = classifier;
-    }
-
     @Override
     public void flushEntityCache(IEntityClass entityClass,
                                  boolean reclassify) {
@@ -600,6 +600,7 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
         l.add(IRestApiService.class);
         l.add(IThreadPoolService.class);
         l.add(IFlowReconcileService.class);
+        l.add(IEntityClassifierService.class);
         return l;
     }
 
@@ -622,13 +623,11 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
         this.restApi = fmc.getServiceImpl(IRestApiService.class);
         this.threadPool = fmc.getServiceImpl(IThreadPoolService.class);
         this.flowReconcileMgr = fmc.getServiceImpl(IFlowReconcileService.class);
+        this.entityClassifier = fmc.getServiceImpl(IEntityClassifierService.class);
     }
 
     @Override
     public void startUp(FloodlightModuleContext fmc) {
-        if (entityClassifier == null)
-            setEntityClassifier(new DefaultEntityClassifier());
-
         primaryIndex = new DeviceUniqueIndex(entityClassifier.getKeyFields());
         secondaryIndexMap = new HashMap<EnumSet<DeviceField>, DeviceIndex>();
 
@@ -710,13 +709,21 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
         // Find the device matching the destination from the entity
         // classes of the source.
         Entity dstEntity = getDestEntityFromPacket(eth);
+        Device dstDevice = null;
         if (dstEntity != null) {
-            Device dstDevice =
+            dstDevice =
                     findDestByEntity(srcDevice, dstEntity);
             if (dstDevice != null)
                 fcStore.put(cntx, CONTEXT_DST_DEVICE, dstDevice);
         }
 
+       if (logger.isTraceEnabled()) {
+           logger.trace("Received PI: {} on switch {}, port {} *** eth={}" +
+           		     " *** srcDev={} *** dstDev={} *** ",
+           		     new Object[] { pi, sw.getStringId(), pi.getInPort(), eth,
+           		                    srcDevice, dstDevice }
+	       );
+       }
         return Command.CONTINUE;
     }
 
@@ -748,6 +755,13 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
         return true;
     }
 
+    /**
+     * Get IP address from packet if the packet is either an ARP 
+     * or a DHCP packet
+     * @param eth
+     * @param dlAddr
+     * @return
+     */
     private int getSrcNwAddr(Ethernet eth, long dlAddr) {
         if (eth.getPayload() instanceof ARP) {
             ARP arp = (ARP) eth.getPayload();
@@ -787,24 +801,13 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
         if ((dlAddrArr[0] & 0x1) != 0)
             return null;
 
-        boolean learnap = true;
-        if (!isValidAttachmentPoint(swdpid, (short)port)) {
-            // If this is an internal port or we otherwise don't want
-            // to learn on these ports.  In the future, we should
-            // handle this case by labeling flows with something that
-            // will give us the entity class.  For now, we'll do our
-            // best assuming attachment point information isn't used
-            // as a key field.
-            learnap = false;
-        }
-
         short vlan = eth.getVlanID();
         int nwSrc = getSrcNwAddr(eth, dlAddr);
         return new Entity(dlAddr,
                           ((vlan >= 0) ? vlan : null),
                           ((nwSrc != 0) ? nwSrc : null),
-                          (learnap ? swdpid : null),
-                          (learnap ? port : null),
+                          swdpid,
+                          port,
                           new Date());
     }
 
@@ -878,12 +881,33 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
                           new Date());
     }
     /**
-     * Look up a {@link Device} based on the provided {@link Entity}.
+     * Look up a {@link Device} based on the provided {@link Entity}. We first
+     * check the primary index. If we do not find an entry there we classify
+     * the device into its IEntityClass and query the classIndex. 
+     * This implies that all key field of the current IEntityClassifier must 
+     * be present in the entity for the lookup to succeed!
      * @param entity the entity to search for
      * @return The {@link Device} object if found
      */
     protected Device findDeviceByEntity(Entity entity) {
-        Long deviceKey =  primaryIndex.findByEntity(entity);
+        // Look up the fully-qualified entity to see if it already
+        // exists in the primary entity index.
+        Long deviceKey = primaryIndex.findByEntity(entity);
+        IEntityClass entityClass = null;
+
+        if (deviceKey == null) {
+            // If the entity does not exist in the primary entity index,
+            // use the entity classifier for find the classes for the
+            // entity. Look up the entity in the returned class'
+            // class entity index.
+            entityClass = entityClassifier.classifyEntity(entity);
+            ClassState classState = getClassState(entityClass);
+
+            if (classState.classIndex != null) {
+                deviceKey =
+                        classState.classIndex.findByEntity(entity);
+            }
+        }
         if (deviceKey == null) return null;
         return deviceMap.get(deviceKey);
     }
@@ -900,36 +924,30 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
      */
     protected Device findDestByEntity(IDevice source,
                                       Entity dstEntity) {
-        Device dstDevice = findDeviceByEntity(dstEntity);
-
-        //if (dstDevice == null) {
-        // This could happen because:
-        // 1) no destination known, or a broadcast destination
-        // 2) if we have attachment point key fields since
-        // attachment point information isn't available for
-        // destination devices.
-        // For the second case, we'll need to match up the
-        // destination device with the class of the source
-        // device.
-        /*
-                ArrayList<Device> candidates = new ArrayList<Device>();
-                for (IEntityClass clazz : srcDevice.getEntityClasses()) {
-                    Device c = findDeviceInClassByEntity(clazz, dstEntity);
-                    if (c != null)
-                        candidates.add(c);
-                }
-                if (candidates.size() == 1) {
-                    dstDevice = candidates.get(0);
-                } else if (candidates.size() > 1) {
-                    // ambiguous device.  A higher-order component will
-                    // need to deal with it by assigning priority
-                    // XXX - TODO
-                }
-         */
-        //}
-
-        return dstDevice;
+        
+        // Look  up the fully-qualified entity to see if it 
+        // exists in the primary entity index
+        Long deviceKey = primaryIndex.findByEntity(dstEntity);
+        
+        if (deviceKey == null) {
+            // This could happen because:
+            // 1) no destination known, or a broadcast destination
+            // 2) if we have attachment point key fields since
+            // attachment point information isn't available for
+            // destination devices.
+            // For the second case, we'll need to match up the
+            // destination device with the class of the source
+            // device.
+            ClassState classState = getClassState(source.getEntityClass());
+            if (classState.classIndex == null) {
+                return null;
+            }
+            deviceKey = classState.classIndex.findByEntity(dstEntity);
+        }
+        if (deviceKey == null) return null;
+        return deviceMap.get(deviceKey);
     }
+    
 
     /**
      * Look up a {@link Device} within a particular entity class based on
@@ -967,21 +985,19 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
             // Look up the fully-qualified entity to see if it already
             // exists in the primary entity index.
             Long deviceKey = primaryIndex.findByEntity(entity);
-            Collection<IEntityClass> classes = null;
+            IEntityClass entityClass = null;
 
             if (deviceKey == null) {
                 // If the entity does not exist in the primary entity index,
                 // use the entity classifier for find the classes for the
-                // entity. Look up the entity in each of the returned classes'
-                // class entity indexes.
-                classes = entityClassifier.classifyEntity(entity);
-                for (IEntityClass clazz : classes) {
-                    ClassState classState = getClassState(clazz);
-
-                    if (classState.classIndex != null) {
-                        deviceKey =
-                                classState.classIndex.findByEntity(entity);
-                    }
+                // entity. Look up the entity in the returned class'
+                // class entity index.
+                entityClass = entityClassifier.classifyEntity(entity);
+                ClassState classState = getClassState(entityClass);
+
+                if (classState.classIndex != null) {
+                    deviceKey =
+                            classState.classIndex.findByEntity(entity);
                 }
             }
             if (deviceKey != null) {
@@ -998,7 +1014,11 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
                 synchronized (deviceKeyLock) {
                     deviceKey = Long.valueOf(deviceKeyCounter++);
                 }
-                device = allocateDevice(deviceKey, entity, classes);
+                device = allocateDevice(deviceKey, entity, entityClass);
+                if (logger.isDebugEnabled()) {
+                    logger.debug("New device created: {} deviceKey={}", 
+                                 device, deviceKey);
+                }
 
                 // Add the new device to the primary map with a simple put
                 deviceMap.put(deviceKey, device);
@@ -1011,7 +1031,7 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
                     continue;
                 }
 
-                updateSecondaryIndices(entity, classes, deviceKey);
+                updateSecondaryIndices(entity, entityClass, deviceKey);
 
                 // generate new device update
                 deviceUpdates =
@@ -1029,7 +1049,7 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
                 device.entities[entityindex].setLastSeenTimestamp(lastSeen);
                 break;
             } else {
-                Device newDevice = allocateDevice(device, entity, classes);
+                Device newDevice = allocateDevice(device, entity);
 
                 // generate updates
                 EnumSet<DeviceField> changedFields =
@@ -1055,7 +1075,7 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
                     continue;
                 }
                 updateSecondaryIndices(entity,
-                                       device.getEntityClasses(),
+                                       device.getEntityClass(),
                                        deviceKey);
                 break;
             }
@@ -1146,6 +1166,41 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
             }
         }
     }
+    
+    /**
+     * Check if the entity e has all the keyFields set. Returns false if not
+     * @param e entity to check 
+     * @param keyFields the key fields to check e against
+     * @return
+     */
+    protected boolean allKeyFieldsPresent(Entity e, EnumSet<DeviceField> keyFields) {
+        for (DeviceField f : keyFields) {
+            switch (f) {
+                case MAC:
+                    // MAC address is always present
+                    break;
+                case IPV4:
+                    if (e.ipv4Address == null) return false;
+                    break;
+                case SWITCH:
+                    if (e.switchDPID == null) return false;
+                    break;
+                case PORT:
+                    if (e.switchPort == null) return false;
+                    break;
+                case VLAN:
+                    // FIXME: vlan==null is ambiguous: it can mean: not present
+                    // or untagged
+                    //if (e.vlan == null) return false;
+                    break;
+                default:
+                    // we should never get here. unless somebody extended 
+                    // DeviceFields
+                    throw new IllegalStateException();
+            }
+        }
+        return true;
+    }
 
     private LinkedList<DeviceUpdate>
     updateUpdates(LinkedList<DeviceUpdate> list, DeviceUpdate update) {
@@ -1178,7 +1233,7 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
 
     /**
      * Update both the primary and class indices for the provided device.
-     * If the update fails because of aEn concurrent update, will return false.
+     * If the update fails because of an concurrent update, will return false.
      * @param device the device to update
      * @param deviceKey the device key for the device
      * @return true if the update succeeded, false otherwise.
@@ -1187,52 +1242,36 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
         if (!primaryIndex.updateIndex(device, deviceKey)) {
             return false;
         }
-        for (IEntityClass clazz : device.getEntityClasses()) {
-            ClassState classState = getClassState(clazz);
+        IEntityClass entityClass = device.getEntityClass();
+        ClassState classState = getClassState(entityClass);
 
-            if (classState.classIndex != null) {
-                if (!classState.classIndex.updateIndex(device,
-                                                       deviceKey))
-                    return false;
-            }
+        if (classState.classIndex != null) {
+            if (!classState.classIndex.updateIndex(device,
+                                                   deviceKey))
+                return false;
         }
-        return true;
+    return true;
     }
 
     /**
      * Update the secondary indices for the given entity and associated
      * entity classes
      * @param entity the entity to update
-     * @param entityClasses the entity classes for the entity
+     * @param entityClass the entity class for the entity
      * @param deviceKey the device key to set up
      */
     private void updateSecondaryIndices(Entity entity,
-                                        Collection<IEntityClass> entityClasses,
+                                        IEntityClass entityClass,
                                         Long deviceKey) {
         for (DeviceIndex index : secondaryIndexMap.values()) {
             index.updateIndex(entity, deviceKey);
         }
-        for (IEntityClass clazz : entityClasses) {
-            ClassState state = getClassState(clazz);
-            for (DeviceIndex index : state.secondaryIndexMap.values()) {
-                index.updateIndex(entity, deviceKey);
-            }
+        ClassState state = getClassState(entityClass);
+        for (DeviceIndex index : state.secondaryIndexMap.values()) {
+            index.updateIndex(entity, deviceKey);
         }
     }
 
-    /**
-     * Update the secondary indices for the given entity and associated
-     * entity classes
-     * @param entity the entity to update
-     * @param entityClasses the entity classes for the entity
-     * @param deviceKey the device key to set up
-     */
-    private void updateSecondaryIndices(Entity entity,
-                                        IEntityClass[] entityClasses,
-                                        Long deviceKey) {
-        updateSecondaryIndices(entity, Arrays.asList(entityClasses), deviceKey);
-    }
-
     /**
      * Clean up expired entities/devices
      */
@@ -1269,13 +1308,13 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
                 }
 
                 for (Entity e : toRemove) {
-                    removeEntity(e, d.getEntityClasses(), d.deviceKey, toKeep);
+                    removeEntity(e, d.getEntityClass(), d.deviceKey, toKeep);
                 }
 
                 if (toKeep.size() > 0) {
                     Device newDevice = allocateDevice(d.getDeviceKey(),
                                                       toKeep,
-                                                      d.entityClasses);
+                                                      d.entityClass);
 
                     EnumSet<DeviceField> changedFields =
                             EnumSet.noneOf(DeviceField.class);
@@ -1305,29 +1344,23 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
     }
 
     private void removeEntity(Entity removed,
-                              IEntityClass[] classes,
+                              IEntityClass entityClass,
                               Long deviceKey,
                               Collection<Entity> others) {
         for (DeviceIndex index : secondaryIndexMap.values()) {
             index.removeEntityIfNeeded(removed, deviceKey, others);
         }
-        for (IEntityClass clazz : classes) {
-            ClassState classState = getClassState(clazz);
-            for (DeviceIndex index : classState.secondaryIndexMap.values()) {
-                index.removeEntityIfNeeded(removed, deviceKey, others);
-            }
+        ClassState classState = getClassState(entityClass);
+        for (DeviceIndex index : classState.secondaryIndexMap.values()) {
+            index.removeEntityIfNeeded(removed, deviceKey, others);
         }
 
         primaryIndex.removeEntityIfNeeded(removed, deviceKey, others);
 
-        for (IEntityClass clazz : classes) {
-            ClassState classState = getClassState(clazz);
-
-            if (classState.classIndex != null) {
-                classState.classIndex.removeEntityIfNeeded(removed,
-                                                           deviceKey,
-                                                           others);
-            }
+        if (classState.classIndex != null) {
+            classState.classIndex.removeEntityIfNeeded(removed,
+                                                       deviceKey,
+                                                       others);
         }
     }
 
@@ -1336,6 +1369,9 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
                                                Integer ipv4Address,
                                                Long switchDPID,
                                                Integer switchPort) {
+        // FIXME: vlan==null is a valid search. Need to handle this
+        // case correctly. Note that the code will still work correctly. 
+        // But we might do a full device search instead of using an index.
         EnumSet<DeviceField> keys = EnumSet.noneOf(DeviceField.class);
         if (macAddress != null) keys.add(DeviceField.MAC);
         if (vlan != null) keys.add(DeviceField.VLAN);
@@ -1357,20 +1393,19 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
 
     protected Device allocateDevice(Long deviceKey,
                                     Entity entity,
-                                    Collection<IEntityClass> entityClasses) {
-        return new Device(this, deviceKey, entity, entityClasses);
+                                    IEntityClass entityClass) {
+        return new Device(this, deviceKey, entity, entityClass);
     }
 
     protected Device allocateDevice(Long deviceKey,
                                     Collection<Entity> entities,
-                                    IEntityClass[] entityClasses) {
-        return new Device(this, deviceKey, entities, entityClasses);
+                                    IEntityClass entityClass) {
+        return new Device(this, deviceKey, entities, entityClass);
     }
 
     protected Device allocateDevice(Device device,
-                                    Entity entity,
-                                    Collection<IEntityClass> entityClasses) {
-        return new Device(device, entity, entityClasses);
+                                    Entity entity) {
+        return new Device(device, entity);
     }
 
     @Override
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java
index 58dfd424c408607f175332d1dede8e0ff0d73281..4ac12eba8cfeb1c3a4c1d1cf16ccb70b85f01ca0 100644
--- a/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java
+++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java
@@ -171,7 +171,7 @@ public class Entity implements Comparable<Entity> {
     public void setActiveSince(Date activeSince) {
         this.activeSince = activeSince;
     }
-
+    
     @Override
     public int hashCode() {
         if (hashCode != 0) return hashCode;
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/IndexedEntity.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/IndexedEntity.java
index ac1981b1abc20d6c4693914f7d37d276c7e975bd..7a128e6259bb07f185a1c631f61288811ec82540 100644
--- a/src/main/java/net/floodlightcontroller/devicemanager/internal/IndexedEntity.java
+++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/IndexedEntity.java
@@ -5,6 +5,7 @@ import java.util.EnumSet;
 import net.floodlightcontroller.devicemanager.IDeviceService;
 import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
 
+
 /**
  * This is a thin wrapper around {@link Entity} that allows overriding
  * the behavior of {@link Object#hashCode()} and {@link Object#equals(Object)}
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceSerializer.java b/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceSerializer.java
index fa9bcc79352539a20abde1d39a891a4c11990d63..66bdaef93255458603491463bc7d2c220cb3162c 100644
--- a/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceSerializer.java
+++ b/src/main/java/net/floodlightcontroller/devicemanager/web/DeviceSerializer.java
@@ -40,6 +40,8 @@ public class DeviceSerializer extends JsonSerializer<Device> {
             JsonProcessingException {
         jGen.writeStartObject();
         
+        jGen.writeStringField("entityClass", device.getEntityClass().getName());
+        
         jGen.writeArrayFieldStart("mac");
         jGen.writeString(HexString.toHexString(device.getMACAddress(), 6));
         jGen.writeEndArray();
@@ -50,9 +52,9 @@ public class DeviceSerializer extends JsonSerializer<Device> {
         jGen.writeEndArray();
 
         jGen.writeArrayFieldStart("vlan");
-        for (Short ip : device.getVlanId())
-            if (ip >= 0)
-                jGen.writeNumber(ip);
+        for (Short vlan : device.getVlanId())
+            if (vlan >= 0)
+                jGen.writeNumber(vlan);
         jGen.writeEndArray();
         jGen.writeArrayFieldStart("attachmentPoint");
         for (SwitchPort ap : device.getAttachmentPoints(true)) {
diff --git a/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule b/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule
index 9339ea3bcd6346a4fd1a942ad6611eff23b4a0d2..af007737c2308aaf1d7616c0cd22651623229a5d 100644
--- a/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule
+++ b/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule
@@ -18,3 +18,4 @@ net.floodlightcontroller.counter.NullCounterStore
 net.floodlightcontroller.threadpool.ThreadPool
 net.floodlightcontroller.ui.web.StaticWebRoutable
 net.floodlightcontroller.virtualnetwork.VirtualNetworkFilter
+net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier
\ No newline at end of file
diff --git a/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java b/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java
index 25ea828b06c98b510ba0d40fc36b0822a9108e19..2bafa6d32a5551aa35d9ac4163d13d5d422fe3a4 100644
--- a/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java
+++ b/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java
@@ -19,7 +19,6 @@ package net.floodlightcontroller.devicemanager.internal;
 
 import java.util.Arrays;
 import java.util.Calendar;
-import java.util.Collection;
 import java.util.Date;
 import java.util.EnumSet;
 import java.util.HashMap;
@@ -41,13 +40,13 @@ import net.floodlightcontroller.core.IOFSwitch;
 import net.floodlightcontroller.core.module.FloodlightModuleContext;
 import net.floodlightcontroller.core.test.MockFloodlightProvider;
 import net.floodlightcontroller.core.test.MockThreadPoolService;
-import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.*;
 import net.floodlightcontroller.devicemanager.IDeviceListener;
-import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
 import net.floodlightcontroller.devicemanager.IDevice;
-import net.floodlightcontroller.devicemanager.IEntityClass;
+import net.floodlightcontroller.devicemanager.IEntityClassifierService;
 import net.floodlightcontroller.devicemanager.SwitchPort;
 import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.test.MockEntityClassifier;
+import net.floodlightcontroller.devicemanager.test.MockEntityClassifierMac;
 import net.floodlightcontroller.flowcache.FlowReconcileManager;
 import net.floodlightcontroller.flowcache.IFlowReconcileService;
 import net.floodlightcontroller.packet.ARP;
@@ -108,21 +107,25 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
         mockFloodlightProvider = getMockFloodlightProvider();
         deviceManager = new DeviceManagerImpl();
         flowReconcileMgr = new FlowReconcileManager();
+        DefaultEntityClassifier entityClassifier = new DefaultEntityClassifier();
         fmc.addService(IDeviceService.class, deviceManager);
         storageSource = new MemoryStorageSource();
         fmc.addService(IStorageSourceService.class, storageSource);
         fmc.addService(IFloodlightProviderService.class, mockFloodlightProvider);
         fmc.addService(IRestApiService.class, restApi);
         fmc.addService(IFlowReconcileService.class, flowReconcileMgr);
+        fmc.addService(IEntityClassifierService.class, entityClassifier);
         tp.init(fmc);
         restApi.init(fmc);
         storageSource.init(fmc);
         deviceManager.init(fmc);
         flowReconcileMgr.init(fmc);
+        entityClassifier.init(fmc);
         storageSource.startUp(fmc);
         deviceManager.startUp(fmc);
         flowReconcileMgr.startUp(fmc);
         tp.startUp(fmc);
+        entityClassifier.startUp(fmc);
 
         IOFSwitch mockSwitch1 = makeSwitchMock(1L);
         IOFSwitch mockSwitch10 = makeSwitchMock(10L);
@@ -219,37 +222,10 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
                 .setTotalLength((short) this.testARPReplyPacket_3_Serialized.length);
     }
 
-    static EnumSet<DeviceField> testKeyFields;
-    static {
-        testKeyFields = EnumSet.of(MAC, VLAN, SWITCH, PORT);
-    }
-
-    public static class TestEntityClass implements IEntityClass {
-        @Override
-        public EnumSet<DeviceField> getKeyFields() {
-            return testKeyFields;
-        }
-    }
-
-    protected static IEntityClass testEC = new TestEntityClass();
 
-    public static class TestEntityClassifier extends DefaultEntityClassifier {
 
-        @Override
-        public Collection<IEntityClass> classifyEntity(Entity entity) {
-            if (entity.switchDPID >= 10L) {
-                return Arrays.asList(testEC);
-            }
-            return DefaultEntityClassifier.entityClasses;
-        }
-
-        @Override
-        public EnumSet<IDeviceService.DeviceField> getKeyFields() {
-            return testKeyFields;
-        }
-
-    }
 
+    
     @Test
     public void testLastSeen() throws Exception {
         Calendar c = Calendar.getInstance();
@@ -276,7 +252,7 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
                 createStrictMock(IDeviceListener.class);
 
         deviceManager.addListener(mockListener);
-        deviceManager.setEntityClassifier(new TestEntityClassifier());
+        deviceManager.entityClassifier= new MockEntityClassifier();
         deviceManager.startUp(null);
 
         ITopologyService mockTopology = createMock(ITopologyService.class);
@@ -314,8 +290,8 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
         Device d1 = deviceManager.learnDeviceByEntity(entity1);
         assertSame(d1, deviceManager.learnDeviceByEntity(entity1));
         assertSame(d1, deviceManager.findDeviceByEntity(entity1));
-        assertArrayEquals(new IEntityClass[]{ DefaultEntityClassifier.entityClass },
-                          d1.entityClasses);
+        assertEquals(DefaultEntityClassifier.entityClass ,
+                          d1.entityClass);
         assertArrayEquals(new Short[] { -1 }, d1.getVlanId());
         assertArrayEquals(new Integer[] { }, d1.getIPv4Addresses());
 
@@ -329,8 +305,8 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
         Device d2 = deviceManager.learnDeviceByEntity(entity2);
         assertFalse(d1.equals(d2));
         assertNotSame(d1, d2);
-        assertArrayEquals(new IEntityClass[]{ testEC },
-                          d2.entityClasses);
+        assertNotSame(d1.getDeviceKey(), d2.getDeviceKey());
+        assertEquals(MockEntityClassifier.testEC, d2.entityClass);
         assertArrayEquals(new Short[] { -1 }, d2.getVlanId());
         assertArrayEquals(new Integer[] { }, d2.getIPv4Addresses());
 
@@ -343,8 +319,8 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
 
         Device d3 = deviceManager.learnDeviceByEntity(entity3);
         assertNotSame(d2, d3);
-        assertArrayEquals(new IEntityClass[]{ testEC },
-                          d3.entityClasses);
+        assertEquals(d2.getDeviceKey(), d3.getDeviceKey());
+        assertEquals(MockEntityClassifier.testEC, d3.entityClass);
         assertArrayEquals(new Integer[] { 1 },
                           d3.getIPv4Addresses());
         assertArrayEquals(new SwitchPort[] { new SwitchPort(10L, 1) },
@@ -363,8 +339,8 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
 
         Device d4 = deviceManager.learnDeviceByEntity(entity4);
         assertNotSame(d1, d4);
-        assertArrayEquals(new IEntityClass[]{ DefaultEntityClassifier.entityClass },
-                          d4.entityClasses);
+        assertEquals(d1.getDeviceKey(), d4.getDeviceKey());
+        assertEquals(DefaultEntityClassifier.entityClass, d4.entityClass);
         assertArrayEquals(new Integer[] { 1 },
                           d4.getIPv4Addresses());
         assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) },
@@ -406,6 +382,8 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
         replay(mockListener);
 
         Device d7 = deviceManager.learnDeviceByEntity(entity7);
+        assertNotSame(d6, d7);
+        assertEquals(d6.getDeviceKey(), d7.getDeviceKey());
         assertArrayEquals(new SwitchPort[] { new SwitchPort(50L, 3) },
                           d7.getAttachmentPoints());
         assertArrayEquals(new Short[] { (short) 4 },
@@ -699,7 +677,7 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
                                       1L,
                                       1,
                                       currentDate),
-                                      DefaultEntityClassifier.entityClasses);
+                                      DefaultEntityClassifier.entityClass);
 
         expect(mockTopology.isAllowed(EasyMock.anyLong(),
                                       EasyMock.anyShort())).
@@ -738,8 +716,7 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
                                       ipaddr,
                                       5L,
                                       2,
-                                      currentDate),
-                                      DefaultEntityClassifier.entityClasses);
+                                      currentDate));
 
         reset(mockTopology);
         expect(mockTopology.isAttachmentPointPort(anyLong(),
@@ -1185,4 +1162,204 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
     public void testDeviceClassQuery() throws Exception {
         doTestDeviceClassQuery();
     }
+    
+    @Test
+    public void testFindDevice() {
+        boolean exceptionCaught;
+        deviceManager.entityClassifier= new MockEntityClassifierMac();
+        deviceManager.startUp(null);
+        
+        Entity entity1 = new Entity(1L, (short)1, 1, 1L, 1, new Date());
+        Entity entity2 = new Entity(2L, (short)2, 2, 1L, 2, new Date());
+        Entity entity2b = new Entity(22L, (short)2, 2, 1L, 2, new Date());
+        
+        Entity entity3 = new Entity(3L, (short)1, 3, 2L, 1, new Date());
+        Entity entity4 = new Entity(4L, (short)2, 4, 2L, 2, new Date());
+        
+        Entity entity5 = new Entity(5L, (short)1, 5, 3L, 1, new Date());
+
+        IDevice d1 = deviceManager.learnDeviceByEntity(entity1);
+        IDevice d2 = deviceManager.learnDeviceByEntity(entity2);
+        IDevice d3 = deviceManager.learnDeviceByEntity(entity3);
+        IDevice d4 = deviceManager.learnDeviceByEntity(entity4);
+        IDevice d5 = deviceManager.learnDeviceByEntity(entity5);
+        
+        // Make sure the entity classifier worked as expected
+        assertEquals(MockEntityClassifierMac.testECMac1, d1.getEntityClass());
+        assertEquals(MockEntityClassifierMac.testECMac1, d2.getEntityClass());
+        assertEquals(MockEntityClassifierMac.testECMac2, d3.getEntityClass());
+        assertEquals(MockEntityClassifierMac.testECMac2, d4.getEntityClass());
+        assertEquals(DefaultEntityClassifier.entityClass,
+                     d5.getEntityClass());
+        
+        // Look up the device using findDevice() which uses only the primary
+        // index
+        assertEquals(d1, deviceManager.findDevice(entity1.getMacAddress(), 
+                                                  entity1.getVlan(),
+                                                  entity1.getIpv4Address(),
+                                                  entity1.getSwitchDPID(),
+                                                  entity1.getSwitchPort()));
+        // port changed. Device will be found through class index
+        assertEquals(d1, deviceManager.findDevice(entity1.getMacAddress(), 
+                                                  entity1.getVlan(),
+                                                  entity1.getIpv4Address(),
+                                                  entity1.getSwitchDPID(),
+                                                  entity1.getSwitchPort()+1));
+        // VLAN changed. No device matches
+        assertEquals(null, deviceManager.findDevice(entity1.getMacAddress(), 
+                                                  (short)42,
+                                                  entity1.getIpv4Address(),
+                                                  entity1.getSwitchDPID(),
+                                                  entity1.getSwitchPort()));
+        assertEquals(null, deviceManager.findDevice(entity1.getMacAddress(), 
+                                                  null,
+                                                  entity1.getIpv4Address(),
+                                                  entity1.getSwitchDPID(),
+                                                  entity1.getSwitchPort()));
+        assertEquals(d2, deviceManager.findDeviceByEntity(entity2));
+        assertEquals(null, deviceManager.findDeviceByEntity(entity2b));
+        assertEquals(d3, deviceManager.findDevice(entity3.getMacAddress(), 
+                                                  entity3.getVlan(),
+                                                  entity3.getIpv4Address(),
+                                                  entity3.getSwitchDPID(),
+                                                  entity3.getSwitchPort()));
+        // switch and port not set. throws exception
+        exceptionCaught = false;
+        try {
+            assertEquals(null, deviceManager.findDevice(entity3.getMacAddress(), 
+                                                        entity3.getVlan(),
+                                                        entity3.getIpv4Address(),
+                                                        null,
+                                                        null));
+        } 
+        catch (IllegalArgumentException e) {
+            exceptionCaught = true;
+        }
+        if (!exceptionCaught)
+            fail("findDevice() did not throw IllegalArgumentException");
+        assertEquals(d4, deviceManager.findDeviceByEntity(entity4));
+        assertEquals(d5, deviceManager.findDevice(entity5.getMacAddress(), 
+                                                  entity5.getVlan(),
+                                                  entity5.getIpv4Address(),
+                                                  entity5.getSwitchDPID(),
+                                                  entity5.getSwitchPort()));
+        // switch and port not set. throws exception (swith/port are key 
+        // fields of IEntityClassifier but not d5.entityClass
+        exceptionCaught = false;
+        try {
+            assertEquals(d5, deviceManager.findDevice(entity5.getMacAddress(), 
+                                                      entity5.getVlan(),
+                                                      entity5.getIpv4Address(),
+                                                      null,
+                                                      null));
+        } 
+        catch (IllegalArgumentException e) {
+            exceptionCaught = true;
+        }
+        if (!exceptionCaught)
+            fail("findDevice() did not throw IllegalArgumentException");
+        
+        // Now look up destination devices
+        assertEquals(d1, deviceManager.findDestDevice(d2, 
+                                                  entity1.getMacAddress(), 
+                                                  entity1.getVlan(),
+                                                  entity1.getIpv4Address()));
+        assertEquals(d1, deviceManager.findDestDevice(d2, 
+                                                  entity1.getMacAddress(), 
+                                                  entity1.getVlan(),
+                                                  null));
+        assertEquals(null, deviceManager.findDestDevice(d2, 
+                                                  entity1.getMacAddress(), 
+                                                  (short) -1,
+                                                  0));
+    }
+    
+    @Test
+    public void testGetIPv4Addresses() {
+        // Looks like Date is only 1s granularity
+        
+        Entity e1 = new Entity(1L, (short)1, null, null, null, new Date(2000));
+        Device d1 = deviceManager.learnDeviceByEntity(e1);
+        assertArrayEquals(new Integer[0], d1.getIPv4Addresses());
+        
+        
+        Entity e2 = new Entity(2L, (short)2, 2, null, null, new Date(2000));
+        Device d2 = deviceManager.learnDeviceByEntity(e2);
+        d2 = deviceManager.learnDeviceByEntity(e2);
+        assertArrayEquals(new Integer[] { 2 }, d2.getIPv4Addresses());
+        // More than one entity
+        Entity e2b = new Entity(2L, (short)2, null, 2L, 2, new Date(3000));
+        d2 = deviceManager.learnDeviceByEntity(e2b);
+        assertEquals(2, d2.entities.length);
+        assertArrayEquals(new Integer[] { 2 }, d2.getIPv4Addresses());
+        // and now add an entity with an IP
+        Entity e2c = new Entity(2L, (short)2, 2, 2L, 3, new Date(3000));
+        d2 = deviceManager.learnDeviceByEntity(e2c);
+        assertArrayEquals(new Integer[] { 2 }, d2.getIPv4Addresses());
+        assertEquals(3, d2.entities.length);
+        
+        // Other devices with different IPs shouldn't interfere
+        Entity e3 = new Entity(3L, (short)3, 3, null, null, new Date(4000));
+        Entity e3b = new Entity(3L, (short)3, 3, 3L, 3, new Date(4400));
+        Device d3 = deviceManager.learnDeviceByEntity(e3);
+        d3 = deviceManager.learnDeviceByEntity(e3b);
+        assertArrayEquals(new Integer[] { 2 }, d2.getIPv4Addresses());
+        assertArrayEquals(new Integer[] { 3 }, d3.getIPv4Addresses());
+        
+        // Add another IP to d3
+        Entity e3c = new Entity(3L, (short)3, 33, 3L, 3, new Date(4400));
+        d3 = deviceManager.learnDeviceByEntity(e3c);
+        Integer[] ips = d3.getIPv4Addresses();
+        Arrays.sort(ips);
+        assertArrayEquals(new Integer[] { 3, 33 }, ips);
+        
+        // Add another device that also claims IP2 but is older than e2
+        Entity e4 = new Entity(4L, (short)4, 2, null, null, new Date(1000));
+        Entity e4b = new Entity(4L, (short)4, null, 4L, 4, new Date(1000));
+        Device d4 = deviceManager.learnDeviceByEntity(e4);
+        assertArrayEquals(new Integer[] { 2 }, d2.getIPv4Addresses());
+        assertArrayEquals(new Integer[0],  d4.getIPv4Addresses());
+        // add another entity to d4
+        d4 = deviceManager.learnDeviceByEntity(e4b);
+        assertArrayEquals(new Integer[0], d4.getIPv4Addresses());
+        
+        // Make e4 and e4a newer
+        Entity e4c = new Entity(4L, (short)4, 2, null, null, new Date(5000));
+        Entity e4d = new Entity(4L, (short)4, null, 4L, 5, new Date(5000));
+        d4 = deviceManager.learnDeviceByEntity(e4c);
+        d4 = deviceManager.learnDeviceByEntity(e4d);
+        assertArrayEquals(new Integer[0], d2.getIPv4Addresses());
+        // FIXME: d4 should not return IP4
+        assertArrayEquals(new Integer[] { 2 }, d4.getIPv4Addresses());
+        
+        // Add another newer entity to d2 but with different IP
+        Entity e2d = new Entity(2L, (short)2, 22, 4L, 6, new Date(6000));
+        d2 = deviceManager.learnDeviceByEntity(e2d);
+        assertArrayEquals(new Integer[] { 22 }, d2.getIPv4Addresses());
+        assertArrayEquals(new Integer[] { 2 }, d4.getIPv4Addresses());
+
+        // new IP for d2,d4 but with same timestamp. Both devices get the IP
+        Entity e2e = new Entity(2L, (short)2, 42, 2L, 4, new Date(7000));
+        d2 = deviceManager.learnDeviceByEntity(e2e);
+        ips= d2.getIPv4Addresses();
+        Arrays.sort(ips);
+        assertArrayEquals(new Integer[] { 22, 42 }, ips);
+        Entity e4e = new Entity(4L, (short)4, 42, 4L, 7, new Date(7000));
+        d4 = deviceManager.learnDeviceByEntity(e4e);
+        ips= d4.getIPv4Addresses();
+        Arrays.sort(ips);
+        assertArrayEquals(new Integer[] { 2, 42 }, ips);
+        
+        // add a couple more IPs
+        Entity e2f = new Entity(2L, (short)2, 4242, 2L, 5, new Date(8000));
+        d2 = deviceManager.learnDeviceByEntity(e2f);
+        ips= d2.getIPv4Addresses();
+        Arrays.sort(ips);
+        assertArrayEquals(new Integer[] { 22, 42, 4242 }, ips);
+        Entity e4f = new Entity(4L, (short)4, 4242, 4L, 8, new Date(9000));
+        d4 = deviceManager.learnDeviceByEntity(e4f);
+        ips= d4.getIPv4Addresses();
+        Arrays.sort(ips);
+        assertArrayEquals(new Integer[] { 2, 42, 4242 }, ips);
+    }
 }
diff --git a/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceUniqueIndexTest.java b/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceUniqueIndexTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..c6c9c7329f3ea95204c744a4bd8ec16a2340ad19
--- /dev/null
+++ b/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceUniqueIndexTest.java
@@ -0,0 +1,167 @@
+/**
+*    Copyright 2012 Big Switch Networks, Inc. 
+* 
+*    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 net.floodlightcontroller.devicemanager.internal;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Iterator;
+
+import org.junit.Test;
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+import junit.framework.TestCase;
+
+/**
+ * 
+ * @author gregor
+ *
+ */
+public class DeviceUniqueIndexTest extends TestCase {
+    protected Entity e1a;
+    protected Entity e1b;
+    protected Device d1;
+    protected Entity e2;
+    protected Entity e2alt;
+    protected Entity e3;
+    protected Entity e4;
+    
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        e1a = new Entity(1L, (short)1, 1, 1L, 1, new Date());
+        e1b = new Entity(1L, (short)2, 1, 1L, 1, new Date());
+        List<Entity> d1Entities = new ArrayList<Entity>(2);
+        d1Entities.add(e1a);
+        d1Entities.add(e1b);
+        d1 = new Device(null, Long.valueOf(1), d1Entities, null);
+        
+        // e2 and e2 alt match in MAC and VLAN
+        e2 = new Entity(2L, (short)2, 2, 2L, 2, new Date());
+        e2alt = new Entity(2, (short)2, null, null, null, null);
+        
+        // IP is null
+        e3 = new Entity(3L, (short)3, null, 3L, 3, new Date());
+        
+        // IP and switch and port are null
+        e4 = new Entity(4L, (short)4, null, null, null, new Date());
+    }
+    
+    /*
+     * Checks that the iterator it returns the elements in the Set expected
+     * Doesn't check how often an element is returned as long it's at least
+     * once
+     */
+    protected void verifyIterator(Set<Long> expected, Iterator<Long> it) {
+        HashSet<Long> actual = new HashSet<Long>();
+        while (it.hasNext()) {
+            actual.add(it.next());
+        }
+        assertEquals(expected, actual);
+    }
+    
+    @Test
+    public void testDeviceUniqueIndex() {
+        DeviceUniqueIndex idx1 = new DeviceUniqueIndex(
+                                             EnumSet.of(DeviceField.MAC, 
+                                                        DeviceField.VLAN));
+        
+        idx1.updateIndex(d1, d1.getDeviceKey());
+        idx1.updateIndex(e2, 2L);
+        
+        //-------------
+        // Test findByEntity lookups
+        assertEquals(Long.valueOf(1L), idx1.findByEntity(e1a));
+        assertEquals(Long.valueOf(1L), idx1.findByEntity(e1b));
+        assertEquals(Long.valueOf(2L), idx1.findByEntity(e2));
+        // we didn't add e2alt but since they key fields are the same we 
+        // should find it 
+        assertEquals(Long.valueOf(2L), idx1.findByEntity(e2alt));
+        assertEquals(null, idx1.findByEntity(e3));
+        assertEquals(null, idx1.findByEntity(e4));
+        
+        //-------------
+        // Test getAll()
+        HashSet<Long> expectedKeys = new HashSet<Long>();
+        expectedKeys.add(1L);
+        expectedKeys.add(2L);
+        verifyIterator(expectedKeys, idx1.getAll());
+        
+                
+        //-------------
+        // Test queryByEntity()
+        verifyIterator(Collections.<Long>singleton(1L), 
+                       idx1.queryByEntity(e1a));
+        verifyIterator(Collections.<Long>singleton(1L), 
+                       idx1.queryByEntity(e1b));
+        verifyIterator(Collections.<Long>singleton(2L), 
+                       idx1.queryByEntity(e2));
+        verifyIterator(Collections.<Long>singleton(2L),
+                       idx1.queryByEntity(e2alt));
+        assertEquals(false, idx1.queryByEntity(e3).hasNext());
+        assertEquals(false, idx1.queryByEntity(e3).hasNext());
+        
+        
+        //-------------
+        // Test removal
+        idx1.removeEntity(e1a, 42L); // No-op. e1a isn't mapped to this key
+        assertEquals(Long.valueOf(1L), idx1.findByEntity(e1a));
+        idx1.removeEntity(e1a, 1L); 
+        assertEquals(null, idx1.findByEntity(e1a));
+        assertEquals(Long.valueOf(1L), idx1.findByEntity(e1b));
+        assertEquals(Long.valueOf(2L), idx1.findByEntity(e2));
+        idx1.removeEntity(e2);  
+        assertEquals(null, idx1.findByEntity(e2));
+        assertEquals(Long.valueOf(1L), idx1.findByEntity(e1b));
+        
+        
+        //-------------
+        // Test null keys
+        DeviceUniqueIndex idx2 = new DeviceUniqueIndex(
+                                             EnumSet.of(DeviceField.IPV4,
+                                                        DeviceField.SWITCH));
+        // only one key field is null
+        idx2.updateIndex(e3, 3L);
+        assertEquals(Long.valueOf(3L), idx2.findByEntity(e3));
+        e3.ipv4Address = 3;
+        assertEquals(null, idx2.findByEntity(e3));
+        // all key fields are null
+        idx2.updateIndex(e4, 4L);
+        assertEquals(null, idx2.findByEntity(e4));
+        Device d4 = new Device(null, 4L, Collections.<Entity>singleton(e4), null);
+        idx2.updateIndex(d4, 4L);
+        assertEquals(null, idx2.findByEntity(e4));
+        
+        
+
+        //-------------
+        // entity already exists with different deviceKey
+        DeviceUniqueIndex idx3 = new DeviceUniqueIndex(
+                                             EnumSet.of(DeviceField.MAC, 
+                                                        DeviceField.VLAN));
+        idx3.updateIndex(e1a, 42L);
+        assertEquals(false, idx3.updateIndex(d1, 1L));
+        // TODO: shouldn't this fail as well so that the behavior
+        // is consistent?
+        idx3.updateIndex(e1a, 1L);
+        // anyways. We can now add d1 ;-)
+        assertEquals(true, idx3.updateIndex(d1, 1L));
+    }
+}
diff --git a/src/test/java/net/floodlightcontroller/devicemanager/test/MockDevice.java b/src/test/java/net/floodlightcontroller/devicemanager/test/MockDevice.java
index 47c39d176e885845b614090a1fb83bc6d0340ebe..6e91f314f5dfac482ad435209b4bea4746b07f33 100644
--- a/src/test/java/net/floodlightcontroller/devicemanager/test/MockDevice.java
+++ b/src/test/java/net/floodlightcontroller/devicemanager/test/MockDevice.java
@@ -36,19 +36,18 @@ public class MockDevice extends Device {
     public MockDevice(DeviceManagerImpl deviceManager,
                       Long deviceKey,
                       Entity entity, 
-                      Collection<IEntityClass> entityClasses)  {
-        super(deviceManager, deviceKey, entity, entityClasses);
+                      IEntityClass entityClass)  {
+        super(deviceManager, deviceKey, entity, entityClass);
     }
 
-    public MockDevice(Device device, Entity newEntity,
-                      Collection<IEntityClass> entityClasses) {
-        super(device, newEntity, entityClasses);
+    public MockDevice(Device device, Entity newEntity) {
+        super(device, newEntity);
     }
     
     public MockDevice(DeviceManagerImpl deviceManager, Long deviceKey,
                       Collection<Entity> entities,
-                      IEntityClass[] entityClasses) {
-        super(deviceManager, deviceKey, entities, entityClasses);
+                      IEntityClass entityClass) {
+        super(deviceManager, deviceKey, entities, entityClass);
     }
 
     @Override
diff --git a/src/test/java/net/floodlightcontroller/devicemanager/test/MockDeviceManager.java b/src/test/java/net/floodlightcontroller/devicemanager/test/MockDeviceManager.java
index 2502f7c04580a97a45172314aa05b7e497d2b82a..cd84a6a3c157ad3403c0d869ae25bac908a13a9f 100644
--- a/src/test/java/net/floodlightcontroller/devicemanager/test/MockDeviceManager.java
+++ b/src/test/java/net/floodlightcontroller/devicemanager/test/MockDeviceManager.java
@@ -7,6 +7,7 @@ import java.util.Set;
 import net.floodlightcontroller.devicemanager.IDevice;
 import net.floodlightcontroller.devicemanager.IDeviceListener;
 import net.floodlightcontroller.devicemanager.IEntityClass;
+import net.floodlightcontroller.devicemanager.IEntityClassifierService;
 import net.floodlightcontroller.devicemanager.internal.Device;
 import net.floodlightcontroller.devicemanager.internal.DeviceManagerImpl;
 import net.floodlightcontroller.devicemanager.internal.Entity;
@@ -16,6 +17,18 @@ import net.floodlightcontroller.devicemanager.internal.Entity;
  * @author readams
  */
 public class MockDeviceManager extends DeviceManagerImpl {
+    /**
+     * Set a new IEntityClassifier
+     * Use this as a quick way to use a particular entity classifier in a 
+     * single test without having to setup the full FloodlightModuleContext
+     * again.
+     * @param ecs 
+     */
+    public void setEntityClassifier(IEntityClassifierService ecs) {
+        this.entityClassifier = ecs;
+        this.startUp(null);
+    }
+    
     /**
      * Learn a device using the given characteristics. 
      * @param macAddress the MAC
@@ -66,21 +79,20 @@ public class MockDeviceManager extends DeviceManagerImpl {
     @Override
     protected Device allocateDevice(Long deviceKey,
                                     Entity entity, 
-                                    Collection<IEntityClass> entityClasses) {
-        return new MockDevice(this, deviceKey, entity, entityClasses);
+                                    IEntityClass entityClass) {
+        return new MockDevice(this, deviceKey, entity, entityClass);
     }
     
     @Override
     protected Device allocateDevice(Long deviceKey,
                                     Collection<Entity> entities, 
-                                    IEntityClass[] entityClasses) {
-        return new MockDevice(this, deviceKey, entities, entityClasses);
+                                    IEntityClass entityClass) {
+        return new MockDevice(this, deviceKey, entities, entityClass);
     }
     
     @Override
     protected Device allocateDevice(Device device,
-                                    Entity entity, 
-                                    Collection<IEntityClass> entityClasses) {
-        return new MockDevice(device, entity, entityClasses);
+                                    Entity entity) {
+        return new MockDevice(device, entity);
     }
 }
diff --git a/src/test/java/net/floodlightcontroller/devicemanager/test/MockEntityClassifier.java b/src/test/java/net/floodlightcontroller/devicemanager/test/MockEntityClassifier.java
new file mode 100644
index 0000000000000000000000000000000000000000..679dc9a4eb7c922329738479bea251a1bdc873c5
--- /dev/null
+++ b/src/test/java/net/floodlightcontroller/devicemanager/test/MockEntityClassifier.java
@@ -0,0 +1,47 @@
+package net.floodlightcontroller.devicemanager.test;
+
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.MAC;
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.PORT;
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.SWITCH;
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.VLAN;
+
+import java.util.EnumSet;
+
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.IEntityClass;
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+import net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier;
+import net.floodlightcontroller.devicemanager.internal.Entity;
+
+/** A simple IEntityClassifier. Useful for tests that need IEntityClassifiers 
+ * and IEntityClass'es with switch and/or port key fields 
+ */
+public class MockEntityClassifier extends DefaultEntityClassifier {
+    public static class TestEntityClass implements IEntityClass {
+        @Override
+        public EnumSet<DeviceField> getKeyFields() {
+            return EnumSet.of(MAC, VLAN, SWITCH, PORT);
+        }
+
+        @Override
+        public String getName() {
+            return "TestEntityClass";
+        }
+    }
+    public static IEntityClass testEC = 
+            new MockEntityClassifier.TestEntityClass();
+    
+    @Override
+    public IEntityClass classifyEntity(Entity entity) {
+        if (entity.getSwitchDPID() >= 10L) {
+            return testEC;
+        }
+        return DefaultEntityClassifier.entityClass;
+    }
+
+    @Override
+    public EnumSet<IDeviceService.DeviceField> getKeyFields() {
+        return EnumSet.of(MAC, VLAN, SWITCH, PORT);
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/java/net/floodlightcontroller/devicemanager/test/MockEntityClassifierMac.java b/src/test/java/net/floodlightcontroller/devicemanager/test/MockEntityClassifierMac.java
new file mode 100644
index 0000000000000000000000000000000000000000..768cc503e6795095df69923d95b995c2b49fdfd0
--- /dev/null
+++ b/src/test/java/net/floodlightcontroller/devicemanager/test/MockEntityClassifierMac.java
@@ -0,0 +1,55 @@
+package net.floodlightcontroller.devicemanager.test;
+
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.MAC;
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.PORT;
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.SWITCH;
+import static net.floodlightcontroller.devicemanager.IDeviceService.DeviceField.VLAN;
+
+import java.util.EnumSet;
+
+import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.IEntityClass;
+import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
+import net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier;
+import net.floodlightcontroller.devicemanager.internal.Entity;
+
+/** A simple IEntityClassifier. Useful for tests that need an IEntityClassifier
+ * with switch/port as key fields. 
+ */
+public class MockEntityClassifierMac extends DefaultEntityClassifier {
+    public static class TestEntityClassMac implements IEntityClass {
+        protected String name;
+        public TestEntityClassMac(String name) {
+            this.name = name;
+        }
+        
+        @Override
+        public EnumSet<DeviceField> getKeyFields() {
+            return EnumSet.of(MAC, VLAN);
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+    }
+    public static IEntityClass testECMac1 = 
+            new MockEntityClassifierMac.TestEntityClassMac("testECMac1");
+    public static IEntityClass testECMac2 = 
+            new MockEntityClassifierMac.TestEntityClassMac("testECMac2");
+    
+    @Override
+    public IEntityClass classifyEntity(Entity entity) {
+        if (entity.getSwitchDPID() == 1L) {
+            return testECMac1;
+        } else if (entity.getSwitchDPID() == 2L) {
+            return testECMac2;
+        }
+        return DefaultEntityClassifier.entityClass;
+    }
+
+    @Override
+    public EnumSet<IDeviceService.DeviceField> getKeyFields() {
+        return EnumSet.of(MAC, VLAN, SWITCH, PORT);
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/net/floodlightcontroller/forwarding/ForwardingTest.java b/src/test/java/net/floodlightcontroller/forwarding/ForwardingTest.java
index 96f1a2bc3b45ae0c348762e0da30a68bdd4daf41..0d850a789fccbb818ffbe65c9a587e163c321198 100644
--- a/src/test/java/net/floodlightcontroller/forwarding/ForwardingTest.java
+++ b/src/test/java/net/floodlightcontroller/forwarding/ForwardingTest.java
@@ -38,11 +38,13 @@ import net.floodlightcontroller.core.IOFSwitch;
 import net.floodlightcontroller.core.module.FloodlightModuleContext;
 import net.floodlightcontroller.core.test.MockFloodlightProvider;
 import net.floodlightcontroller.core.test.MockThreadPoolService;
+import net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier;
 import net.floodlightcontroller.devicemanager.test.MockDeviceManager;
 import net.floodlightcontroller.counter.CounterStore;
 import net.floodlightcontroller.counter.ICounterStoreService;
 import net.floodlightcontroller.devicemanager.IDevice;
 import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.IEntityClassifierService;
 import net.floodlightcontroller.packet.Data;
 import net.floodlightcontroller.packet.Ethernet;
 import net.floodlightcontroller.packet.IPacket;
@@ -103,6 +105,7 @@ public class ForwardingTest extends FloodlightTestCase {
         flowReconcileMgr = new FlowReconcileManager();
         routingEngine = createMock(IRoutingService.class);
         topology = createMock(ITopologyService.class);
+        DefaultEntityClassifier entityClassifier = new DefaultEntityClassifier();
         
 
         FloodlightModuleContext fmc = new FloodlightModuleContext();
@@ -114,15 +117,18 @@ public class ForwardingTest extends FloodlightTestCase {
         fmc.addService(ICounterStoreService.class, new CounterStore());
         fmc.addService(IDeviceService.class, deviceManager);
         fmc.addService(IFlowReconcileService.class, flowReconcileMgr);
+        fmc.addService(IEntityClassifierService.class, entityClassifier);
 
         threadPool.init(fmc);
         forwarding.init(fmc);
         deviceManager.init(fmc);
         flowReconcileMgr.init(fmc);
+        entityClassifier.init(fmc);
         threadPool.startUp(fmc);
         deviceManager.startUp(fmc);
         forwarding.startUp(fmc);
         flowReconcileMgr.startUp(fmc);
+        entityClassifier.startUp(fmc);
         
         // Mock switches
         sw1 = EasyMock.createNiceMock(IOFSwitch.class);
diff --git a/src/test/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkFilterTest.java b/src/test/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkFilterTest.java
index bcf0ac2be74d8a392ef4d363ce4f1d9e486e81a5..824fe03375b25df9d6335b28e4cb287e106ecec2 100644
--- a/src/test/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkFilterTest.java
+++ b/src/test/java/net/floodlightcontroller/virtualnetwork/VirtualNetworkFilterTest.java
@@ -22,6 +22,8 @@ import net.floodlightcontroller.core.module.FloodlightModuleContext;
 import net.floodlightcontroller.core.test.MockThreadPoolService;
 import net.floodlightcontroller.core.test.PacketFactory;
 import net.floodlightcontroller.devicemanager.IDeviceService;
+import net.floodlightcontroller.devicemanager.IEntityClassifierService;
+import net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier;
 import net.floodlightcontroller.devicemanager.test.MockDeviceManager;
 import net.floodlightcontroller.flowcache.FlowReconcileManager;
 import net.floodlightcontroller.flowcache.IFlowReconcileService;
@@ -88,16 +90,19 @@ public class VirtualNetworkFilterTest extends FloodlightTestCase {
         FlowReconcileManager frm = new FlowReconcileManager();
         MockThreadPoolService tps = new MockThreadPoolService();
         vns = new VirtualNetworkFilter();
+        DefaultEntityClassifier entityClassifier = new DefaultEntityClassifier();
         fmc.addService(IRestApiService.class, restApi);
         fmc.addService(IFloodlightProviderService.class, getMockFloodlightProvider());
         fmc.addService(IDeviceService.class, deviceService);
         fmc.addService(IFlowReconcileService.class, frm);
         fmc.addService(IThreadPoolService.class, tps);
+        fmc.addService(IEntityClassifierService.class, entityClassifier);
         tps.init(fmc);
         frm.init(fmc);
         deviceService.init(fmc);
         restApi.init(fmc);
         getMockFloodlightProvider().init(fmc);
+        entityClassifier.init(fmc);
         tps.startUp(fmc);
         vns.init(fmc);
         frm.startUp(fmc);
@@ -105,6 +110,7 @@ public class VirtualNetworkFilterTest extends FloodlightTestCase {
         restApi.startUp(fmc);
         getMockFloodlightProvider().startUp(fmc);
         vns.startUp(fmc);
+        entityClassifier.startUp(fmc);
         
         // Mock switches
         //fastWilcards mocked as this constant