Merge "HostTracker Bundle Separation"
authorGiovanni Meo <gmeo@cisco.com>
Thu, 13 Jun 2013 16:33:55 +0000 (16:33 +0000)
committerGerrit Code Review <gerrit@opendaylight.org>
Thu, 13 Jun 2013 16:33:55 +0000 (16:33 +0000)
54 files changed:
opendaylight/distribution/opendaylight/pom.xml
opendaylight/distribution/opendaylight/src/assemble/bin.xml
opendaylight/hosttracker/api/src/main/java/org/opendaylight/controller/hosttracker/IfHostListener.java
opendaylight/hosttracker/api/src/main/java/org/opendaylight/controller/hosttracker/IfIptoHost.java
opendaylight/hosttracker/api/src/main/java/org/opendaylight/controller/hosttracker/IfNewHostNotify.java
opendaylight/hosttracker/api/src/main/java/org/opendaylight/controller/hosttracker/hostAware/HostNodeConnector.java
opendaylight/hosttracker/api/src/main/java/org/opendaylight/controller/hosttracker/hostAware/IHostFinder.java
opendaylight/hosttracker/api/src/test/java/org/opendaylight/controller/hosttracker/hostAware/HostNodeConnectorTest.java
opendaylight/hosttracker/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/Activator.java
opendaylight/hosttracker/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/HostTracker.java
opendaylight/hosttracker/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/HostTrackerCallable.java
opendaylight/hosttracker/implementation/src/test/java/org/opendaylight/controller/hosttracker/internal/HostTrackerTest.java
opendaylight/hosttracker/integrationtest/src/test/java/org/opendaylight/controller/hosttracker/internal/HostTrackerIT.java
opendaylight/hosttracker_new/api/pom.xml [new file with mode: 0644]
opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/Entity.java [new file with mode: 0644]
opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IDevice.java [new file with mode: 0644]
opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IDeviceListener.java [new file with mode: 0644]
opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IDeviceService.java [new file with mode: 0755]
opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IEntityClass.java [new file with mode: 0644]
opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IEntityClassListener.java [new file with mode: 0644]
opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IEntityClassifierService.java [new file with mode: 0644]
opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IfHostListener.java [new file with mode: 0644]
opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IfIptoHost.java [new file with mode: 0644]
opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IfNewHostNotify.java [new file with mode: 0644]
opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/SwitchPort.java [new file with mode: 0644]
opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/hostAware/HostNodeConnector.java [new file with mode: 0644]
opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/hostAware/IHostFinder.java [new file with mode: 0644]
opendaylight/hosttracker_new/api/src/test/java/org/opendaylight/controller/hosttracker/hostAware/HostNodeConnectorTest.java [new file with mode: 0644]
opendaylight/hosttracker_new/implementation/pom.xml [new file with mode: 0644]
opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/Activator.java [new file with mode: 0644]
opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/AttachmentPoint.java [new file with mode: 0644]
opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/DefaultEntityClassifier.java [new file with mode: 0644]
opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/Device.java [new file with mode: 0755]
opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/DeviceIndex.java [new file with mode: 0644]
opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/DeviceIndexInterator.java [new file with mode: 0644]
opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/DeviceIterator.java [new file with mode: 0644]
opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/DeviceManagerImpl.java [new file with mode: 0755]
opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/DeviceMultiIndex.java [new file with mode: 0644]
opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/DeviceUniqueIndex.java [new file with mode: 0644]
opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/IndexedEntity.java [new file with mode: 0644]
opendaylight/hosttracker_new/implementation/src/test/java/org/opendaylight/controller/hosttracker/internal/DeviceManagerImplTest.java [new file with mode: 0644]
opendaylight/hosttracker_new/implementation/src/test/java/org/opendaylight/controller/hosttracker/internal/DeviceUniqueIndexTest.java [new file with mode: 0644]
opendaylight/hosttracker_new/implementation/src/test/java/org/opendaylight/controller/hosttracker/test/MockDevice.java [new file with mode: 0644]
opendaylight/hosttracker_new/implementation/src/test/java/org/opendaylight/controller/hosttracker/test/MockDeviceManager.java [new file with mode: 0644]
opendaylight/hosttracker_new/implementation/src/test/java/org/opendaylight/controller/hosttracker/test/MockEntityClassifier.java [new file with mode: 0644]
opendaylight/hosttracker_new/implementation/src/test/java/org/opendaylight/controller/hosttracker/test/MockEntityClassifierMac.java [new file with mode: 0644]
opendaylight/hosttracker_new/implementation/src/test/java/org/opendaylight/controller/hosttracker/test/MockFlexEntityClassifier.java [new file with mode: 0644]
opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/packet/ARP.java
opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/utils/FilterIterator.java [new file with mode: 0644]
opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/utils/IListener.java [new file with mode: 0644]
opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/utils/IterableIterator.java [new file with mode: 0644]
opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/utils/ListenerDispatcher.java [new file with mode: 0644]
opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/utils/MultiIterator.java [new file with mode: 0644]
opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/utils/SingletonTask.java [new file with mode: 0644]

index b30241daa8144194d4e072fd45a964c192cbc376..765dea5337bb65c0ccb218892a788d389363ea95 100644 (file)
@@ -46,6 +46,8 @@
     <module>../../hosttracker/api</module>
     <module>../../hosttracker/implementation</module>
     <module>../../hosttracker/integrationtest</module>
+    <module>../../hosttracker_new/api</module>
+    <module>../../hosttracker_new/implementation</module>
     <module>../../containermanager/api</module>
     <module>../../containermanager/implementation</module>
     <module>../../switchmanager/api</module>
index 8c00d7f0fcf8b4b9553e3ea0a1e0b93e83158f3a..8be21f6450735266c4092ffa054ef3e18c021d66 100644 (file)
@@ -15,6 +15,8 @@
                <exclude>org.opendaylight.controller:logging.bridge</exclude>
                <exclude>org.opendaylight.controller:protocol_plugins.stub</exclude>
                <exclude>org.opendaylight.controller:*.integrationtest</exclude>
+        <exclude>org.opendaylight.controller:hosttracker_new</exclude>
+        <exclude>org.opendaylight.controller:hosttracker_new.implementation</exclude>
       </excludes>
       <binaries>
         <outputDirectory>opendaylight/plugins</outputDirectory>
index d7c6fdbb3d7fead1d8d0d8c24ecee16ea907d493..51d68d87b43ab2ca19769bbd31455c141a4736fe 100644 (file)
@@ -1,4 +1,3 @@
-
 /*
  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
  *
@@ -12,21 +11,21 @@ package org.opendaylight.controller.hosttracker;
 import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
 
 /**
- * This interface defines the method to notify detected Host on the
- * network. The information includes Host's IP address, MAC address,
- * switch ID, port, and VLAN.
+ * This interface defines the method to notify detected Host on the network. The
+ * information includes Host's IP address, MAC address, switch ID, port, and
+ * VLAN.
  *
  */
 
 public interface IfHostListener {
     /**
-     * Learns  new Hosts. Called by ArpHandler and implemented in
-     * HostTracker.java. If a Host is learned for the first time then
-     * adds it to the local database and informs other applications
-     * of coming up a new Host. For the hosts which it has already
-     * learned, it refreshes them.
+     * Learns new Hosts. Called by ArpHandler and implemented in
+     * HostTracker.java. If a Host is learned for the first time then adds it to
+     * the local database and informs other applications of coming up a new
+     * Host. For the hosts which it has already learned, it refreshes them.
      *
-     * @param host      Host info encapsulated in HostNodeConnector class
+     * @param host
+     *            Host info encapsulated in HostNodeConnector class
      */
     public void hostListener(HostNodeConnector host);
 }
index fdb1e72b3f4ce9c7c1ccc147872409dd5b399060..995ee575152f35f4f93bfe4983df771245af7d08 100644 (file)
@@ -1,4 +1,3 @@
-
 /*
  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
  *
@@ -13,29 +12,31 @@ import java.net.InetAddress;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.Future;
-import org.opendaylight.controller.sal.core.NodeConnector;
+
 import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
+import org.opendaylight.controller.sal.core.NodeConnector;
 import org.opendaylight.controller.sal.utils.Status;
 
 /**
- * This interface defines the methods to retrieve information about
- * learned Hosts. Also provides methods to statically add/remove
- * Hosts from the local database.
+ * This interface defines the methods to retrieve information about learned
+ * Hosts. Also provides methods to statically add/remove Hosts from the local
+ * database.
  *
  */
 
 public interface IfIptoHost {
     /**
-     * Applications call this interface methods to determine IP address to MAC binding and its
-     * connectivity to an OpenFlow switch in term of Node, Port, and VLAN. These
-     * bindings are learned dynamically as well as can be added statically through
-     * Northbound APIs. If a binding is unknown, then an ARP request is initiated
-     * immediately to discover the host.
+     * Applications call this interface methods to determine IP address to MAC
+     * binding and its connectivity to an OpenFlow switch in term of Node, Port,
+     * and VLAN. These bindings are learned dynamically as well as can be added
+     * statically through Northbound APIs. If a binding is unknown, then an ARP
+     * request is initiated immediately to discover the host.
      *
-     * @param networkAddress    IP Address of the Host encapsulated in class InetAddress
-     * @return                  {@link org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector}
-     *                                                  Class that contains the Host info such as its MAC address,
-     *                                                  Switch ID, port, VLAN. If Host is not found, returns NULL
+     * @param networkAddress
+     *            IP Address of the Host encapsulated in class InetAddress
+     * @return {@link org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector}
+     *         Class that contains the Host info such as its MAC address, Switch
+     *         ID, port, VLAN. If Host is not found, returns NULL
      */
     public HostNodeConnector hostFind(InetAddress networkAddress);
 
@@ -43,10 +44,11 @@ public interface IfIptoHost {
      * Checks the local Host Database to see if a Host has been learned for a
      * given IP address.
      *
-     * @param networkAddress    IP Address of the Host encapsulated in class InetAddress
-     * @return                  {@link org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector}
-     *                                                  Class that contains the Host info such as its MAC address,
-     *                                                  Switch ID, port, VLAN. If Host is not found, returns NULL
+     * @param networkAddress
+     *            IP Address of the Host encapsulated in class InetAddress
+     * @return {@link org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector}
+     *         Class that contains the Host info such as its MAC address, Switch
+     *         ID, port, VLAN. If Host is not found, returns NULL
      *
      */
     public HostNodeConnector hostQuery(InetAddress networkAddress);
@@ -55,73 +57,82 @@ public interface IfIptoHost {
      * Initiates an immediate discovery of the Host for a given IP address. This
      * provides for the calling applications to block on the host discovery.
      *
-     * @param networkAddress            IP address encapsulated in InetAddress class
-     * @return                      Future {@link org.opendaylight.controller.hosttracker.HostTrackerCallable}
+     * @param networkAddress
+     *            IP address encapsulated in InetAddress class
+     * @return Future
+     *         {@link org.opendaylight.controller.hosttracker.HostTrackerCallable}
      */
     public Future<HostNodeConnector> discoverHost(InetAddress networkAddress);
 
     /**
-     * Returns the Network Hierarchy for a given Host. This API is typically used by
-     * applications like Hadoop for Rack Awareness functionality.
+     * Returns the Network Hierarchy for a given Host. This API is typically
+     * used by applications like Hadoop for Rack Awareness functionality.
      *
-     * @param                                   IP address of the Host encapsulated in InetAddress class
-     * @return                  List of String ArrayList containing the Hierarchies.
+     * @param IP
+     *            address of the Host encapsulated in InetAddress class
+     * @return List of String ArrayList containing the Hierarchies.
      */
     public List<List<String>> getHostNetworkHierarchy(InetAddress hostAddress);
 
     /**
-     * Returns all the the Hosts either learned dynamically or added statically via
-     * Northbound APIs.
+     * Returns all the the Hosts either learned dynamically or added statically
+     * via Northbound APIs.
      *
-     * @return                  Set of {@link org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector}.
-     *                                                  Class that contains the Host info such as its MAC address,
-     *                                                  Switch ID, port, VLAN.
+     * @return Set of
+     *         {@link org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector}
+     *         . Class that contains the Host info such as its MAC address,
+     *         Switch ID, port, VLAN.
      */
     public Set<HostNodeConnector> getAllHosts();
 
     /**
-     * Returns all the "Active Hosts" learned "Statically" via Northbound APIs. These Hosts
-     * are categorized as "Active" because the Switch and Port they are connected to, are in
-     * up state.
+     * Returns all the "Active Hosts" learned "Statically" via Northbound APIs.
+     * These Hosts are categorized as "Active" because the Switch and Port they
+     * are connected to, are in up state.
      *
-     * @return                  Set of {@link org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector}.
-     *                                                  Class that contains the Host info such as MAC address,
-     *                                                  Switch ID, port, VLAN. If Host is not found, returns NULL
+     * @return Set of
+     *         {@link org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector}
+     *         . Class that contains the Host info such as MAC address, Switch
+     *         ID, port, VLAN. If Host is not found, returns NULL
      */
     public Set<HostNodeConnector> getActiveStaticHosts();
 
     /**
-     * Returns all the "Inactive Hosts" learned "Statically" via Northbound APIs. These Hosts
-     * are categorized as "Inactive" because either the Switch or the Port they are connected
-     * to, is in down state.
+     * Returns all the "Inactive Hosts" learned "Statically" via Northbound
+     * APIs. These Hosts are categorized as "Inactive" because either the Switch
+     * or the Port they are connected to, is in down state.
      *
-     * @return                  Set of HostNodeConnector {@link org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector}.
-     *                                                  HostNodeConnector is Class that
-     *                                                  contains the Host info such as its MAC address, OpenFlowNode
-     *                                                  ID, port, VLAN.
+     * @return Set of HostNodeConnector
+     *         {@link org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector}
+     *         . HostNodeConnector is Class that contains the Host info such as
+     *         its MAC address, OpenFlowNode ID, port, VLAN.
      */
     public Set<HostNodeConnector> getInactiveStaticHosts();
 
     /**
-     * Hosts can be learned dynamically or added statically. This method allows the addition
-     * of a Host to the local database statically.
+     * Hosts can be learned dynamically or added statically. This method allows
+     * the addition of a Host to the local database statically.
      *
-     * @param networkAddress        IP Address of the Host
-     * @param dataLayerAddress      MAC Address of the Host
-     * @param nc                                    NodeConnector to which the host is attached
-     * @param vlan                  VLAN the host belongs to
-     * @return                      The status object as described in {@code Status}
-     *                                                          indicating the result of this action.
+     * @param networkAddress
+     *            IP Address of the Host
+     * @param dataLayerAddress
+     *            MAC Address of the Host
+     * @param nc
+     *            NodeConnector to which the host is attached
+     * @param vlan
+     *            VLAN the host belongs to
+     * @return The status object as described in {@code Status} indicating the
+     *         result of this action.
      */
     public Status addStaticHost(String networkAddress, String dataLayerAddress,
-                                NodeConnector nc, String vlan);
+            NodeConnector nc, String vlan);
 
     /**
      * Allows the deletion of statically learned Host
      *
      * @param networkAddress
-     * @return                      The status object as described in {@code Status}
-     *                                                          indicating the result of this action.
+     * @return The status object as described in {@code Status} indicating the
+     *         result of this action.
      */
     public Status removeStaticHost(String networkAddress);
 }
index 561f78f5f5f17e5c26f6fdc0085906d6aaf695ed..ef900e475ecc68b580e1a6946ef09470f36054fd 100644 (file)
@@ -1,4 +1,3 @@
-
 /*
  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
  *
@@ -12,25 +11,27 @@ package org.opendaylight.controller.hosttracker;
 import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
 
 /**
- * This Interface defines the methods for client applications of
- * Host Tracker to get notifications when a new host is learned or
- * existing host is removed from the network.
+ * This Interface defines the methods for client applications of Host Tracker to
+ * get notifications when a new host is learned or existing host is removed from
+ * the network.
  *
  */
 public interface IfNewHostNotify {
     /**
      * Notifies the HostTracker Clients that a new Host has been learned
      *
-     * @param host      Host Info encapsulated in HostNodeConnector class
+     * @param host
+     *            Host Info encapsulated in HostNodeConnector class
      */
     public void notifyHTClient(HostNodeConnector host);
 
     /**
-     * Notifies the HostTracker Clients that a Host which was learned in
-     * the past has been removed either due to switch/port down event or
-     * due to ARP Aging
+     * Notifies the HostTracker Clients that a Host which was learned in the
+     * past has been removed either due to switch/port down event or due to ARP
+     * Aging
      *
-     * @param host      Host Info encapsulated in HostNodeConnector class
+     * @param host
+     *            Host Info encapsulated in HostNodeConnector class
      */
     public void notifyHTClientHostRemoved(HostNodeConnector host);
 }
index 243da05869a5edd4ec7d41eedccf61b2b5e1079f..5d9a5e180310bf8bb98e042201dd45e59b442dc5 100644 (file)
@@ -1,4 +1,3 @@
-
 /*
  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
  *
@@ -26,7 +25,7 @@ import org.opendaylight.controller.sal.core.Node;
 import org.opendaylight.controller.sal.core.NodeConnector;
 import org.opendaylight.controller.sal.packet.address.EthernetAddress;
 
-@XmlRootElement(name="host")
+@XmlRootElement(name = "host")
 @XmlAccessorType(XmlAccessType.NONE)
 public class HostNodeConnector extends Host {
     private static final long serialVersionUID = 1L;
@@ -173,7 +172,9 @@ public class HostNodeConnector extends Host {
         return !Arrays.equals(emptyArray, macaddr);
     }
 
-    /* (non-Javadoc)
+    /*
+     * (non-Javadoc)
+     *
      * @see java.lang.Object#toString()
      */
     @Override
index 533e7599bddfb01f386ad71f78329da024157d81..abe75180cf676bdeab08d7fe0f5166d6bfa57c97 100644 (file)
@@ -1,4 +1,3 @@
-
 /*
  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
  *
 package org.opendaylight.controller.hosttracker.hostAware;
 
 import java.net.InetAddress;
-import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
-
 
 /**
- * This Interface  defines the methods to trigger the discovery of
- * a Host and to probe if a learned Host is still in the network.
+ * This Interface defines the methods to trigger the discovery of a Host and to
+ * probe if a learned Host is still in the network.
  *
  *
  *
  */
 public interface IHostFinder {
     /**
-     * This method initiates the discovery of a host based on its IP address. This is triggered
-     * by query of an application to the HostTracker. The requested IP address
-     * doesn't exist in the local database at this point.
+     * This method initiates the discovery of a host based on its IP address.
+     * This is triggered by query of an application to the HostTracker. The
+     * requested IP address doesn't exist in the local database at this point.
      *
-     * @param networkAddress    IP Address encapsulated in InetAddress class
+     * @param networkAddress
+     *            IP Address encapsulated in InetAddress class
      *
      */
     public void find(InetAddress networkAddress);
 
     /**
-     * This method is called by HostTracker to see if a learned Host is still in the network.
-     * Used mostly for ARP Aging.
+     * This method is called by HostTracker to see if a learned Host is still in
+     * the network. Used mostly for ARP Aging.
      *
-     * @param host          The Host that needs to be probed
+     * @param host
+     *            The Host that needs to be probed
      */
     public void probe(HostNodeConnector host);
 }
index 2c2c49bfe18fb348302eb0cae929f9983ccac243..bdccc99c9c52d29801bf43c7a7ebff602a207c65 100644 (file)
@@ -1,4 +1,3 @@
-\r
 /*\r
  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.\r
  *\r
@@ -26,64 +25,73 @@ import org.opendaylight.controller.sal.utils.NodeConnectorCreator;
 import org.opendaylight.controller.sal.core.NodeConnector;\r
 import org.opendaylight.controller.sal.utils.NodeCreator;\r
 \r
-\r
 public class HostNodeConnectorTest extends TestCase {\r
 \r
-        @Test\r
-        public void testHostNodeConnector() throws UnknownHostException {\r
-                HostNodeConnector hostnodeconnector_1, hostnodeconnector_2, hostnodeconnector_3;\r
-                InetAddress hostIP_1 = InetAddress.getByName("192.168.0.8");\r
-                InetAddress hostIP_2 = InetAddress.getByName("2001:420:281:1004:e123:e688:d655:a1b0");\r
-                InetAddress hostIP_3 = InetAddress.getByName("192.168.0.28");\r
-                byte[] hostMAC_2 = new byte[]{(byte)0x11,(byte)0x22,(byte)0x33,(byte)0x22,(byte)0x22,(byte)0x22};\r
-                byte[] hostMAC_3 = new byte[]{(byte)0x11,(byte)0x22,(byte)0x33,(byte)0x33,(byte)0x33,(byte)0x33};\r
-\r
-                Node node  = NodeCreator.createOFNode(1L);\r
-                NodeConnector nc1 = NodeConnectorCreator.createOFNodeConnector((short) 2, node);\r
-                NodeConnector nc2 = NodeConnectorCreator.createOFNodeConnector((short) 1, node);\r
-\r
-                try {\r
-                        hostnodeconnector_1 = new HostNodeConnector(hostIP_1);\r
-                        Assert.assertTrue(hostnodeconnector_1.equalsByIP(hostIP_1));\r
-                        Assert.assertTrue(hostnodeconnector_1.isV4Host());\r
-                        Assert.assertTrue(hostnodeconnector_1.equalsByIP(hostIP_1));\r
-                } catch (ConstructionException e) {\r
-                        Assert.assertTrue(false);\r
-                }\r
-\r
-                try {\r
-                        hostnodeconnector_2 = new HostNodeConnector(\r
-                                hostMAC_2, hostIP_2, nc1, (short)2);\r
-                        Assert.assertTrue(hostnodeconnector_2.isV6Host());\r
-                        Assert.assertTrue(hostnodeconnector_2.getnodeConnector().equals(nc1));\r
-                        Assert.assertTrue(hostnodeconnector_2.getnodeconnectorNode().equals(node));\r
-                        Assert.assertTrue(node.equals(hostnodeconnector_2.getnodeconnectorNode()));\r
-                } catch (ConstructionException e) {\r
-                        Assert.assertTrue(false);\r
-                }\r
-\r
-                try {\r
-                        hostnodeconnector_3 = new HostNodeConnector(\r
-                                        new EthernetAddress(hostMAC_3), hostIP_3, nc2, (short)3);\r
-                        byte[] hostMAC_3_rb = hostnodeconnector_3.getDataLayerAddressBytes();\r
-                        HostNodeConnector  hostnodeconnector_3rb = new HostNodeConnector(\r
-                                        new EthernetAddress(hostMAC_3_rb), hostIP_3, nc2, (short)3);\r
-                        Assert.assertTrue(hostnodeconnector_3.equals(hostnodeconnector_3rb));\r
-\r
-                        Assert.assertTrue(hostnodeconnector_3.getVlan() == (short)3);\r
-\r
-                        hostnodeconnector_3.setStaticHost(true);\r
-                        Assert.assertTrue(hostnodeconnector_3.isStaticHost());\r
-\r
-                        Assert.assertTrue(hostnodeconnector_3.isRewriteEnabled());\r
-\r
-                        hostnodeconnector_3.initArpSendCountDown().setArpSendCountDown((short) 10);\r
-                        Assert.assertTrue(hostnodeconnector_3.getArpSendCountDown() == (short)10);\r
-\r
-                } catch (ConstructionException e) {\r
-                        Assert.assertTrue(false);\r
-                }\r
+    @Test\r
+    public void testHostNodeConnector() throws UnknownHostException {\r
+        HostNodeConnector hostnodeconnector_1, hostnodeconnector_2, hostnodeconnector_3;\r
+        InetAddress hostIP_1 = InetAddress.getByName("192.168.0.8");\r
+        InetAddress hostIP_2 = InetAddress\r
+                .getByName("2001:420:281:1004:e123:e688:d655:a1b0");\r
+        InetAddress hostIP_3 = InetAddress.getByName("192.168.0.28");\r
+        byte[] hostMAC_2 = new byte[] { (byte) 0x11, (byte) 0x22, (byte) 0x33,\r
+                (byte) 0x22, (byte) 0x22, (byte) 0x22 };\r
+        byte[] hostMAC_3 = new byte[] { (byte) 0x11, (byte) 0x22, (byte) 0x33,\r
+                (byte) 0x33, (byte) 0x33, (byte) 0x33 };\r
+\r
+        Node node = NodeCreator.createOFNode(1L);\r
+        NodeConnector nc1 = NodeConnectorCreator.createOFNodeConnector(\r
+                (short) 2, node);\r
+        NodeConnector nc2 = NodeConnectorCreator.createOFNodeConnector(\r
+                (short) 1, node);\r
+\r
+        try {\r
+            hostnodeconnector_1 = new HostNodeConnector(hostIP_1);\r
+            Assert.assertTrue(hostnodeconnector_1.equalsByIP(hostIP_1));\r
+            Assert.assertTrue(hostnodeconnector_1.isV4Host());\r
+            Assert.assertTrue(hostnodeconnector_1.equalsByIP(hostIP_1));\r
+        } catch (ConstructionException e) {\r
+            Assert.assertTrue(false);\r
+        }\r
 \r
+        try {\r
+            hostnodeconnector_2 = new HostNodeConnector(hostMAC_2, hostIP_2,\r
+                    nc1, (short) 2);\r
+            Assert.assertTrue(hostnodeconnector_2.isV6Host());\r
+            Assert.assertTrue(hostnodeconnector_2.getnodeConnector()\r
+                    .equals(nc1));\r
+            Assert.assertTrue(hostnodeconnector_2.getnodeconnectorNode()\r
+                    .equals(node));\r
+            Assert.assertTrue(node.equals(hostnodeconnector_2\r
+                    .getnodeconnectorNode()));\r
+        } catch (ConstructionException e) {\r
+            Assert.assertTrue(false);\r
         }\r
 \r
+        try {\r
+            hostnodeconnector_3 = new HostNodeConnector(new EthernetAddress(\r
+                    hostMAC_3), hostIP_3, nc2, (short) 3);\r
+            byte[] hostMAC_3_rb = hostnodeconnector_3\r
+                    .getDataLayerAddressBytes();\r
+            HostNodeConnector hostnodeconnector_3rb = new HostNodeConnector(\r
+                    new EthernetAddress(hostMAC_3_rb), hostIP_3, nc2, (short) 3);\r
+            Assert.assertTrue(hostnodeconnector_3.equals(hostnodeconnector_3rb));\r
+\r
+            Assert.assertTrue(hostnodeconnector_3.getVlan() == (short) 3);\r
+\r
+            hostnodeconnector_3.setStaticHost(true);\r
+            Assert.assertTrue(hostnodeconnector_3.isStaticHost());\r
+\r
+            Assert.assertTrue(hostnodeconnector_3.isRewriteEnabled());\r
+\r
+            hostnodeconnector_3.initArpSendCountDown().setArpSendCountDown(\r
+                    (short) 10);\r
+            Assert.assertTrue(hostnodeconnector_3.getArpSendCountDown() == (short) 10);\r
+\r
+        } catch (ConstructionException e) {\r
+            Assert.assertTrue(false);\r
+        }\r
+\r
+    }\r
+\r
 }\r
index ef88fa2e1660a2cf4f495c43f6a8c2fa8b0d86b7..65cb8225c856ef9d5c0a693284e3266e72c0a2ae 100644 (file)
@@ -1,4 +1,3 @@
-
 /*
  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
  *
 package org.opendaylight.controller.hosttracker.internal;
 
 import org.apache.felix.dm.Component;
-import org.opendaylight.controller.hosttracker.internal.HostTracker;
+import org.opendaylight.controller.clustering.services.IClusterContainerServices;
 import org.opendaylight.controller.hosttracker.IfHostListener;
 import org.opendaylight.controller.hosttracker.IfIptoHost;
 import org.opendaylight.controller.hosttracker.IfNewHostNotify;
 import org.opendaylight.controller.hosttracker.hostAware.IHostFinder;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import org.opendaylight.controller.clustering.services.IClusterContainerServices;
 import org.opendaylight.controller.sal.core.ComponentActivatorAbstractBase;
 import org.opendaylight.controller.switchmanager.IInventoryListener;
 import org.opendaylight.controller.switchmanager.ISwitchManager;
 import org.opendaylight.controller.switchmanager.ISwitchManagerAware;
 import org.opendaylight.controller.topologymanager.ITopologyManager;
 import org.opendaylight.controller.topologymanager.ITopologyManagerAware;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class Activator extends ComponentActivatorAbstractBase {
     protected static final Logger logger = LoggerFactory
             .getLogger(Activator.class);
 
     /**
-     * Function called when the activator starts just after some
-     * initializations are done by the
-     * ComponentActivatorAbstractBase.
+     * Function called when the activator starts just after some initializations
+     * are done by the ComponentActivatorAbstractBase.
      *
      */
     public void init() {
     }
 
     /**
-     * Function called when the activator stops just before the
-     * cleanup done by ComponentActivatorAbstractBase
+     * Function called when the activator stops just before the cleanup done by
+     * ComponentActivatorAbstractBase
      *
      */
     public void destroy() {
     }
 
     /**
-     * Function that is used to communicate to dependency manager the
-     * list of known implementations for services inside a container
+     * Function that is used to communicate to dependency manager the list of
+     * known implementations for services inside a container
      *
      *
      * @return An array containing all the CLASS objects that will be
-     * instantiated in order to get an fully working implementation
-     * Object
+     *         instantiated in order to get an fully working implementation
+     *         Object
      */
     public Object[] getImplementations() {
         Object[] res = { HostTracker.class };
@@ -62,57 +58,64 @@ public class Activator extends ComponentActivatorAbstractBase {
     }
 
     /**
-     * Function that is called when configuration of the dependencies
-     * is required.
+     * Function that is called when configuration of the dependencies is
+     * required.
      *
-     * @param c dependency manager Component object, used for
-     * configuring the dependencies exported and imported
-     * @param imp Implementation class that is being configured,
-     * needed as long as the same routine can configure multiple
-     * implementations
-     * @param containerName The containerName being configured, this allow
-     * also optional per-container different behavior if needed, usually
-     * should not be the case though.
+     * @param c
+     *            dependency manager Component object, used for configuring the
+     *            dependencies exported and imported
+     * @param imp
+     *            Implementation class that is being configured, needed as long
+     *            as the same routine can configure multiple implementations
+     * @param containerName
+     *            The containerName being configured, this allow also optional
+     *            per-container different behavior if needed, usually should not
+     *            be the case though.
      */
     public void configureInstance(Component c, Object imp, String containerName) {
         if (imp.equals(HostTracker.class)) {
             // export the service
-            c.setInterface(new String[] { ISwitchManagerAware.class.getName(),
-                    IInventoryListener.class.getName(),
-                    IfIptoHost.class.getName(), IfHostListener.class.getName(),
-                    ITopologyManagerAware.class.getName() }, null);
+            c.setInterface(
+                    new String[] { ISwitchManagerAware.class.getName(),
+                            IInventoryListener.class.getName(),
+                            IfIptoHost.class.getName(),
+                            IfHostListener.class.getName(),
+                            ITopologyManagerAware.class.getName() }, null);
 
-            c.add(createContainerServiceDependency(containerName).setService(
-                    ISwitchManager.class).setCallbacks("setSwitchManager",
-                    "unsetSwitchManager").setRequired(false));
-            c.add(createContainerServiceDependency(containerName).setService(
-                    IClusterContainerServices.class).setCallbacks(
-                    "setClusterContainerService",
-                    "unsetClusterContainerService").setRequired(true));
-            c.add(createContainerServiceDependency(containerName).setService(
-                    IHostFinder.class).setCallbacks("setArpHandler",
-                    "unsetArpHandler").setRequired(false));
-            c.add(createContainerServiceDependency(containerName).setService(
-                    ITopologyManager.class).setCallbacks("setTopologyManager",
-                    "unsetTopologyManager").setRequired(false));
-            c.add(createContainerServiceDependency(containerName).setService(
-                    IfNewHostNotify.class).setCallbacks("setnewHostNotify",
-                    "unsetnewHostNotify").setRequired(false));
+            c.add(createContainerServiceDependency(containerName)
+                    .setService(ISwitchManager.class)
+                    .setCallbacks("setSwitchManager", "unsetSwitchManager")
+                    .setRequired(false));
+            c.add(createContainerServiceDependency(containerName)
+                    .setService(IClusterContainerServices.class)
+                    .setCallbacks("setClusterContainerService",
+                            "unsetClusterContainerService").setRequired(true));
+            c.add(createContainerServiceDependency(containerName)
+                    .setService(IHostFinder.class)
+                    .setCallbacks("setArpHandler", "unsetArpHandler")
+                    .setRequired(false));
+            c.add(createContainerServiceDependency(containerName)
+                    .setService(ITopologyManager.class)
+                    .setCallbacks("setTopologyManager", "unsetTopologyManager")
+                    .setRequired(false));
+            c.add(createContainerServiceDependency(containerName)
+                    .setService(IfNewHostNotify.class)
+                    .setCallbacks("setnewHostNotify", "unsetnewHostNotify")
+                    .setRequired(false));
         }
     }
 
     /**
-     * Method which tells how many Global implementations are
-     * supported by the bundle. This way we can tune the number of
-     * components created. This components will be created ONLY at the
-     * time of bundle startup and will be destroyed only at time of
-     * bundle destruction, this is the major difference with the
-     * implementation retrieved via getImplementations where all of
-     * them are assumed to be in a container !
+     * Method which tells how many Global implementations are supported by the
+     * bundle. This way we can tune the number of components created. This
+     * components will be created ONLY at the time of bundle startup and will be
+     * destroyed only at time of bundle destruction, this is the major
+     * difference with the implementation retrieved via getImplementations where
+     * all of them are assumed to be in a container !
      *
      *
-     * @return The list of implementations the bundle will support,
-     * in Global version
+     * @return The list of implementations the bundle will support, in Global
+     *         version
      */
     protected Object[] getGlobalImplementations() {
         return null;
@@ -121,10 +124,13 @@ public class Activator extends ComponentActivatorAbstractBase {
     /**
      * Configure the dependency for a given instance Global
      *
-     * @param c Component assigned for this instance, this will be
-     * what will be used for configuration
-     * @param imp implementation to be configured
-     * @param containerName container on which the configuration happens
+     * @param c
+     *            Component assigned for this instance, this will be what will
+     *            be used for configuration
+     * @param imp
+     *            implementation to be configured
+     * @param containerName
+     *            container on which the configuration happens
      */
     protected void configureGlobalInstance(Component c, Object imp) {
         if (imp.equals(HostTracker.class)) {
index f8534e98101702b6ce1039714eb9a647392cf85f..707a7761389c1b0dcf3d6c6cc4706c95c944f96f 100644 (file)
@@ -33,11 +33,11 @@ import org.opendaylight.controller.clustering.services.CacheConfigException;
 import org.opendaylight.controller.clustering.services.CacheExistException;
 import org.opendaylight.controller.clustering.services.IClusterContainerServices;
 import org.opendaylight.controller.clustering.services.IClusterServices;
-import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
-import org.opendaylight.controller.hosttracker.hostAware.IHostFinder;
 import org.opendaylight.controller.hosttracker.IfHostListener;
 import org.opendaylight.controller.hosttracker.IfIptoHost;
 import org.opendaylight.controller.hosttracker.IfNewHostNotify;
+import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
+import org.opendaylight.controller.hosttracker.hostAware.IHostFinder;
 import org.opendaylight.controller.sal.core.ConstructionException;
 import org.opendaylight.controller.sal.core.Edge;
 import org.opendaylight.controller.sal.core.Host;
@@ -433,7 +433,7 @@ public class HostTracker implements IfIptoHost, IfHostListener,
                  */
                 removePendingARPFromList(i);
                 logger.debug("Host Removed from ARPPending List, IP: {}",
-                          networkAddr);
+                        networkAddr);
                 return;
             }
         }
@@ -991,13 +991,14 @@ public class HostTracker implements IfIptoHost, IfHostListener,
                      * there
                      */
                     if (logger.isTraceEnabled()) {
-                      logger.trace(
-                              "ARP Probing ({}) for {}({})",
-                              new Object[] {
-                                      arp_cntdown,
-                                      host.getNetworkAddress().getHostAddress(),
-                                      HexEncode.bytesToHexString(host
-                                              .getDataLayerAddressBytes()) });
+                        logger.trace(
+                                "ARP Probing ({}) for {}({})",
+                                new Object[] {
+                                        arp_cntdown,
+                                        host.getNetworkAddress()
+                                                .getHostAddress(),
+                                        HexEncode.bytesToHexString(host
+                                                .getDataLayerAddressBytes()) });
                     }
                     host.setArpSendCountDown(arp_cntdown);
                     hostFinder.probe(host);
@@ -1260,7 +1261,9 @@ public class HostTracker implements IfIptoHost, IfHostListener,
         for (Entry<InetAddress, HostNodeConnector> entry : hostsDB.entrySet()) {
             HostNodeConnector host = entry.getValue();
             if (host.getnodeConnector().equals(nodeConnector)) {
-                logger.debug(" NodeConnector: {} is down, remove from Hosts_DB", nodeConnector);
+                logger.debug(
+                        " NodeConnector: {} is down, remove from Hosts_DB",
+                        nodeConnector);
                 removeKnownHost(entry.getKey());
                 notifyHostLearnedOrRemoved(host, false);
             }
index a99100b2956762fcefd4e92d891d25585b5bf3e0..25de544f52e8d0dd79da9afff83e2eda1e28a145 100644 (file)
@@ -1,4 +1,3 @@
-\r
 /*\r
  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.\r
  *\r
@@ -9,51 +8,48 @@
 \r
 package org.opendaylight.controller.hosttracker.internal;\r
 \r
-\r
 import java.net.InetAddress;\r
 import java.net.UnknownHostException;\r
 import java.util.concurrent.Future;\r
 \r
+import junit.framework.TestCase;\r
+\r
 import org.junit.Assert;\r
 import org.junit.Test;\r
-\r
-import junit.framework.TestCase;\r
 import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;\r
 \r
 public class HostTrackerTest extends TestCase {\r
 \r
-        @Test\r
-        public void testHostTrackerCallable() throws UnknownHostException {\r
-\r
-                HostTracker hostTracker = null;\r
-                hostTracker = new HostTracker();\r
-                Assert.assertFalse(hostTracker== null);\r
-\r
-                InetAddress hostIP = InetAddress.getByName("192.168.0.8");\r
-\r
-                HostTrackerCallable htCallable = new HostTrackerCallable (hostTracker, hostIP);\r
-                Assert.assertTrue(htCallable.trackedHost.equals(hostIP));\r
-                Assert.assertTrue(htCallable.hostTracker.equals(hostTracker));\r
-\r
-                long count = htCallable.latch.getCount();\r
-                htCallable.wakeup();\r
-                Assert.assertTrue(htCallable.latch.getCount() == --count );\r
-        }\r
-\r
-\r
-\r
-        @Test\r
-        public void testHostTracker() throws UnknownHostException {\r
-                HostTracker hostTracker = null;\r
-                hostTracker = new HostTracker();\r
-                Assert.assertFalse(hostTracker== null);\r
-\r
-                InetAddress hostIP_1 = InetAddress.getByName("192.168.0.8");\r
-                InetAddress hostIP_2 = InetAddress.getByName("192.168.0.18");\r
-                Future<HostNodeConnector> dschost = hostTracker.discoverHost(hostIP_1);\r
-                dschost = hostTracker.discoverHost(hostIP_2);\r
-                hostTracker.nonClusterObjectCreate();\r
-        }\r
-\r
+    @Test\r
+    public void testHostTrackerCallable() throws UnknownHostException {\r
+\r
+        HostTracker hostTracker = null;\r
+        hostTracker = new HostTracker();\r
+        Assert.assertFalse(hostTracker == null);\r
+\r
+        InetAddress hostIP = InetAddress.getByName("192.168.0.8");\r
+\r
+        HostTrackerCallable htCallable = new HostTrackerCallable(hostTracker,\r
+                hostIP);\r
+        Assert.assertTrue(htCallable.trackedHost.equals(hostIP));\r
+        Assert.assertTrue(htCallable.hostTracker.equals(hostTracker));\r
+\r
+        long count = htCallable.latch.getCount();\r
+        htCallable.wakeup();\r
+        Assert.assertTrue(htCallable.latch.getCount() == --count);\r
+    }\r
+\r
+    @Test\r
+    public void testHostTracker() throws UnknownHostException {\r
+        HostTracker hostTracker = null;\r
+        hostTracker = new HostTracker();\r
+        Assert.assertFalse(hostTracker == null);\r
+\r
+        InetAddress hostIP_1 = InetAddress.getByName("192.168.0.8");\r
+        InetAddress hostIP_2 = InetAddress.getByName("192.168.0.18");\r
+        Future<HostNodeConnector> dschost = hostTracker.discoverHost(hostIP_1);\r
+        dschost = hostTracker.discoverHost(hostIP_2);\r
+        hostTracker.nonClusterObjectCreate();\r
+    }\r
 \r
 }\r
index 1cf320b4df37ad61a7539c4a67643f9dfda2afaa..04219502f6eff56cc6f80d545dc38d5dca1e4d25 100644 (file)
@@ -59,8 +59,7 @@ import org.ops4j.pax.exam.spi.reactors.PerClass;
 \r
 @RunWith(PaxExam.class)\r
 public class HostTrackerIT {\r
-    private Logger log = LoggerFactory\r
-            .getLogger(HostTrackerIT.class);\r
+    private Logger log = LoggerFactory.getLogger(HostTrackerIT.class);\r
     // get the OSGI bundle context\r
     @Inject\r
     private BundleContext bc;\r
diff --git a/opendaylight/hosttracker_new/api/pom.xml b/opendaylight/hosttracker_new/api/pom.xml
new file mode 100644 (file)
index 0000000..a4c2f2e
--- /dev/null
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">\r
+  <modelVersion>4.0.0</modelVersion>\r
+  <parent>\r
+    <groupId>org.opendaylight.controller</groupId>\r
+    <artifactId>commons.opendaylight</artifactId>\r
+    <version>1.4.0-SNAPSHOT</version>\r
+    <relativePath>../../commons/opendaylight</relativePath>\r
+  </parent>\r
+  <artifactId>hosttracker_new</artifactId>\r
+  <version>0.4.0-SNAPSHOT</version>\r
+  <packaging>bundle</packaging>\r
+\r
+  <build>\r
+    <plugins>\r
+      <plugin>\r
+        <groupId>org.apache.felix</groupId>\r
+        <artifactId>maven-bundle-plugin</artifactId>\r
+        <version>2.3.6</version>\r
+        <extensions>true</extensions>\r
+        <configuration>\r
+          <instructions>\r
+            <Export-Package>\r
+              org.opendaylight.controller.hosttracker_new,\r
+              org.opendaylight.controller.hosttracker_new.hostAware\r
+            </Export-Package>\r
+            <Import-Package>\r
+              org.opendaylight.controller.sal.core,\r
+              org.opendaylight.controller.sal.utils,\r
+              org.opendaylight.controller.topologymanager,\r
+              org.opendaylight.controller.sal.packet.address,\r
+              org.opendaylight.controller.switchmanager,\r
+              org.opendaylight.controller.clustering.services,\r
+              javax.xml.bind.annotation,\r
+              javax.xml.bind,\r
+              org.apache.felix.dm,\r
+              org.apache.commons.lang3.builder,\r
+              org.osgi.service.component,\r
+              org.slf4j,\r
+              org.eclipse.osgi.framework.console,\r
+              org.osgi.framework\r
+            </Import-Package>\r
+          </instructions>\r
+          <manifestLocation>${project.basedir}/META-INF</manifestLocation>\r
+        </configuration>\r
+      </plugin>\r
+    </plugins>\r
+  </build>\r
+  <dependencies>\r
+    <dependency>\r
+      <groupId>org.opendaylight.controller</groupId>\r
+      <artifactId>topologymanager</artifactId>\r
+      <version>0.4.0-SNAPSHOT</version>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.opendaylight.controller</groupId>\r
+      <artifactId>switchmanager</artifactId>\r
+      <version>0.4.0-SNAPSHOT</version>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.opendaylight.controller</groupId>\r
+      <artifactId>clustering.services</artifactId>\r
+      <version>0.4.0-SNAPSHOT</version>\r
+    </dependency>\r
+    <dependency>\r
+      <groupId>org.opendaylight.controller</groupId>\r
+      <artifactId>sal</artifactId>\r
+      <version>0.5.0-SNAPSHOT</version>\r
+    </dependency>\r
+  </dependencies>\r
+</project>\r
diff --git a/opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/Entity.java b/opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/Entity.java
new file mode 100644 (file)
index 0000000..7c98e95
--- /dev/null
@@ -0,0 +1,252 @@
+/*
+ * Copyright (c) 2011,2012 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * 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.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the
+ *    License. You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an "AS
+ *    IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ *    express or implied. See the License for the specific language
+ *    governing permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.hosttracker;
+
+import java.util.Date;
+
+import org.opendaylight.controller.sal.core.NodeConnector;
+
+/**
+ * An entity on the network is a visible trace of a device that corresponds to a
+ * packet received from a particular interface on the edge of a network, with a
+ * particular VLAN tag, and a particular MAC address, along with any other
+ * packet characteristics we might want to consider as helpful for
+ * disambiguating devices.
+ *
+ * Entities are the most basic element of devices; devices consist of one or
+ * more entities. Entities are immutable once created, except for the last seen
+ * timestamp.
+ *
+ * @author readams
+ *
+ */
+public class Entity implements Comparable<Entity> {
+    /**
+     * Timeout for computing {@link Entity#activeSince}.
+     *
+     * @see {@link Entity#activeSince}
+     */
+    protected static int ACTIVITY_TIMEOUT = 30000;
+
+    /**
+     * The MAC address associated with this entity
+     */
+    protected long macAddress;
+
+    /**
+     * The IP address associated with this entity, or null if no IP learned from
+     * the network observation associated with this entity
+     */
+    protected Integer ipv4Address;
+
+    /**
+     * The VLAN tag on this entity, or null if untagged
+     */
+    protected Short vlan;
+
+    /**
+     * The attachment point for this entity
+     */
+    NodeConnector port;
+
+    /**
+     * The last time we observed this entity on the network
+     */
+    protected Date lastSeenTimestamp;
+
+    /**
+     * The time between {@link Entity#activeSince} and
+     * {@link Entity#lastSeenTimestamp} is a period of activity for this entity
+     * where it was observed repeatedly. If, when the entity is observed, the is
+     * longer ago than the activity timeout, {@link Entity#lastSeenTimestamp}
+     * and {@link Entity#activeSince} will be set to the current time.
+     */
+    protected Date activeSince;
+
+    private int hashCode = 0;
+
+    // ************
+    // Constructors
+    // ************
+
+    /**
+     * Create a new entity
+     *
+     * @param macAddress
+     * @param vlan
+     * @param ipv4Address
+     * @param switchDPID
+     * @param switchPort
+     * @param lastSeenTimestamp
+     */
+    public Entity(long macAddress, Short vlan, Integer ipv4Address,
+            NodeConnector port, Date lastSeenTimestamp) {
+        this.macAddress = macAddress;
+        this.ipv4Address = ipv4Address;
+        this.vlan = vlan;
+        this.port = port;
+        this.lastSeenTimestamp = lastSeenTimestamp;
+        this.activeSince = lastSeenTimestamp;
+    }
+
+    // ***************
+    // Getters/Setters
+    // ***************
+
+    // @JsonSerialize(using=MACSerializer.class)
+    public long getMacAddress() {
+        return macAddress;
+    }
+
+    // @JsonSerialize(using=IPv4Serializer.class)
+    public Integer getIpv4Address() {
+        return ipv4Address;
+    }
+
+    public Short getVlan() {
+        return vlan;
+    }
+
+    public NodeConnector getPort() {
+        return port;
+    }
+
+    // @JsonIgnore
+    public boolean hasSwitchPort() {
+        return port != null;
+    }
+
+    public Date getLastSeenTimestamp() {
+        return lastSeenTimestamp;
+    }
+
+    /**
+     * Set the last seen timestamp and also update {@link Entity#activeSince} if
+     * appropriate
+     *
+     * @param lastSeenTimestamp
+     *            the new last seen timestamp
+     * @see {@link Entity#activeSince}
+     */
+    public void setLastSeenTimestamp(Date lastSeenTimestamp) {
+        if (activeSince == null
+                || (activeSince.getTime() + ACTIVITY_TIMEOUT) < lastSeenTimestamp
+                        .getTime())
+            this.activeSince = lastSeenTimestamp;
+        this.lastSeenTimestamp = lastSeenTimestamp;
+    }
+
+    public Date getActiveSince() {
+        return activeSince;
+    }
+
+    public void setActiveSince(Date activeSince) {
+        this.activeSince = activeSince;
+    }
+
+    @Override
+    public int hashCode() {
+        if (hashCode != 0)
+            return hashCode;
+        final int prime = 31;
+        hashCode = 1;
+        hashCode = prime * hashCode
+                + ((ipv4Address == null) ? 0 : ipv4Address.hashCode());
+        hashCode = prime * hashCode + (int) (macAddress ^ (macAddress >>> 32));
+        hashCode = prime * hashCode + ((port == null) ? 0 : port.hashCode());
+        hashCode = prime * hashCode + ((vlan == null) ? 0 : vlan.hashCode());
+        return hashCode;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        Entity other = (Entity) obj;
+        if (ipv4Address == null) {
+            if (other.ipv4Address != null)
+                return false;
+        } else if (!ipv4Address.equals(other.ipv4Address))
+            return false;
+        if (macAddress != other.macAddress)
+            return false;
+        if (port == null) {
+            if (other.port != null)
+                return false;
+        } else if (!port.equals(other.port))
+            return false;
+        if (vlan == null) {
+            if (other.vlan != null)
+                return false;
+        } else if (!vlan.equals(other.vlan))
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "Entity [macAddress=" + macAddress + ", ipv4Address="
+                + ipv4Address + ", vlan=" + vlan + ", port=" + port + "]";
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Override
+    public int compareTo(Entity o) {
+        int r;
+        if (port == null)
+            r = o.port == null ? 0 : -1;
+        else if (o.port == null)
+            r = 1;
+        else {
+            // XXX - the node id is only defined as an object rather
+            // than something useful. We're just going to have to
+            // blindly cast to Comparable and hope it works.
+            Comparable switchId = (Comparable) port.getNode().getID();
+            Comparable oswitchId = (Comparable) o.port.getNode().getID();
+            r = switchId.compareTo(oswitchId);
+            if (r != 0)
+                return r;
+
+            Comparable portId = (Comparable) port.getID();
+            Comparable oportId = (Comparable) o.port.getID();
+            r = portId.compareTo(oportId);
+        }
+        return r;
+    }
+
+}
diff --git a/opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IDevice.java b/opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IDevice.java
new file mode 100644 (file)
index 0000000..d9cd3f1
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2011,2012 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * 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.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the
+ *    License. You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an "AS
+ *    IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ *    express or implied. See the License for the specific language
+ *    governing permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.hosttracker;
+
+import java.util.Date;
+
+/**
+ * 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 only
+ * from merging all associated entities for that device.
+ *
+ * @author readams
+ */
+public interface IDevice {
+    /**
+     * Get the primary key for this device.
+     *
+     * @return the primary key
+     */
+    public Long getDeviceKey();
+
+    /**
+     * Get the MAC address of the device as a Long value.
+     *
+     * @return the MAC address for the device
+     */
+    public long getMACAddress();
+
+    /**
+     * Get the MAC address of the device as a String value.
+     *
+     * @return the MAC address for the device
+     */
+    public String getMACAddressString();
+
+    /**
+     * Get all unique VLAN IDs for the device. If the device has untagged
+     * entities, then the value -1 will be returned.
+     *
+     * @return an array containing all unique VLAN IDs for the device.
+     */
+    public Short[] getVlanId();
+
+    /**
+     * Get all unique IPv4 addresses associated with the device.
+     *
+     * @return an array containing the unique IPv4 addresses for the device.
+     */
+    public Integer[] getIPv4Addresses();
+
+    /**
+     * Get all unique attachment points associated with the device. This will
+     * not include any blocked attachment points.
+     *
+     * @return an array containing all unique attachment points for the device
+     */
+    public SwitchPort[] getAttachmentPoints();
+
+    /**
+     * Get all old attachment points associated with the device. this is used in
+     * host movement scenario.
+     *
+     * @return an array containing all unique old attachment points for the
+     *         device
+     */
+    public SwitchPort[] getOldAP();
+
+    /**
+     * Get all unique attachment points associated with the device.
+     *
+     * @param includeError
+     *            whether to include blocked attachment points. Blocked
+     *            attachment points should not be used for forwarding, but could
+     *            be useful to show to a user
+     * @return an array containing all unique attachment points for the device
+     */
+    public SwitchPort[] getAttachmentPoints(boolean includeError);
+
+    /**
+     * Returns all unique VLAN IDs for the device that were observed on the
+     * given switch port
+     *
+     * @param swp
+     *            the switch port to query
+     * @return an array containing the unique VLAN IDs
+     */
+    public Short[] getSwitchPortVlanIds(SwitchPort swp);
+
+    /**
+     * Get the most recent timestamp for this device
+     *
+     * @return the last seen timestamp
+     */
+    public Date getLastSeen();
+
+    /**
+     * Get the entity class for the device.
+     *
+     * @return the entity class
+     * @see IEntityClassifierService
+     */
+    public IEntityClass getEntityClass();
+
+}
diff --git a/opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IDeviceListener.java b/opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IDeviceListener.java
new file mode 100644 (file)
index 0000000..5861915
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2011 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * 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.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the
+ *    License. You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an "AS
+ *    IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ *    express or implied. See the License for the specific language
+ *    governing permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.hosttracker;
+
+import org.opendaylight.controller.sal.utils.IListener;
+
+/**
+ * Implementors of this interface can receive updates from DeviceManager about
+ * the state of devices under its control.
+ *
+ * @author David Erickson (daviderickson@cs.stanford.edu)
+ */
+public interface IDeviceListener extends IListener<String> {
+    /**
+     * Called when a new Device is found
+     *
+     * @param device
+     *            the device that changed
+     */
+    public void deviceAdded(IDevice device);
+
+    /**
+     * Called when a Device is removed, this typically occurs when the port the
+     * Device is attached to goes down, or the switch it is attached to is
+     * removed.
+     *
+     * @param device
+     *            the device that changed
+     */
+    public void deviceRemoved(IDevice device);
+
+    /**
+     * Called when a Device has moved to a new location on the network. Note
+     * that either the switch or the port or both has changed.
+     *
+     * @param device
+     *            the device that changed
+     */
+    public void deviceMoved(IDevice device);
+
+    /**
+     * Called when a network address has been added or remove from a device
+     *
+     * @param device
+     *            the device that changed
+     */
+    public void deviceIPV4AddrChanged(IDevice device);
+
+    /**
+     * Called when a VLAN tag for the device has been added or removed
+     *
+     * @param device
+     *            the device that changed
+     */
+    public void deviceVlanChanged(IDevice device);
+}
diff --git a/opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IDeviceService.java b/opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IDeviceService.java
new file mode 100755 (executable)
index 0000000..9199f5a
--- /dev/null
@@ -0,0 +1,241 @@
+/*
+ * Copyright (c) 2011,2012 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * 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.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the
+ *    License. You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an "AS
+ *    IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ *    express or implied. See the License for the specific language
+ *    governing permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.hosttracker;
+
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.osgi.service.device.Device;
+
+/**
+ * Device manager allows interacting with devices on the network. Note that
+ * under normal circumstances, {@link Device} objects should be retrieved from
+ * the {@link FloodlightContext} rather than from {@link IDeviceManager}.
+ */
+public interface IDeviceService {
+
+    /**
+     * Fields used in devices for indexes and querying
+     *
+     * @see IDeviceService#addIndex
+     */
+    enum DeviceField {
+        MAC, IPV4, VLAN, SWITCHPORT
+    }
+
+    /**
+     * The source device for the current packet-in, if applicable.
+     */
+    // public static final String CONTEXT_SRC_DEVICE =
+    // "net.floodlightcontroller.devicemanager.srcDevice";
+
+    /**
+     * The destination device for the current packet-in, if applicable.
+     */
+    // public static final String CONTEXT_DST_DEVICE =
+    // / "net.floodlightcontroller.devicemanager.dstDevice";
+
+    /**
+     * The original destination device for the current packet-in
+     */
+    // public static final String CONTEXT_ORIG_DST_DEVICE =
+    // "net.floodlightcontroller.devicemanager.origDstDevice";
+
+    /**
+     * A FloodlightContextStore object that can be used to interact with the
+     * FloodlightContext information created by BVS manager.
+     */
+    // public static final FloodlightContextStore<IDevice> fcStore =
+    // new FloodlightContextStore<IDevice>();
+
+    /**
+     * Get the device with the given device key.
+     *
+     * @param deviceKey
+     *            the key to search for
+     * @return the device associated with the key, or null if no such device
+     * @see IDevice#getDeviceKey()
+     */
+    public IDevice getDevice(Long deviceKey);
+
+    /**
+     * 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. Null means no VLAN and is valid even if VLAN is a
+     *            key field.
+     * @param ipv4Address
+     *            the ipv4 address
+     * @param port
+     *            the node connector
+     * @return an {@link IDevice} or null if no device is found.
+     * @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,
+            NodeConnector port) 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. Search for a device in a given entity class. This is the
+     * same as the lookup process for destination devices.
+     *
+     * Only the key fields as defined by the reference entity class will be
+     * important in this search. All key fields MUST be supplied.
+     *
+     * @param entityClass
+     *            The entity class in which to perform the lookup.
+     * @param macAddress
+     *            The MAC address for the destination
+     * @param vlan
+     *            the VLAN if available
+     * @param ipv4Address
+     *            The IP address if available.
+     * @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 findClassDevice(IEntityClass entityClass, long macAddress,
+            Short vlan, Integer ipv4Address) throws IllegalArgumentException;
+
+    /**
+     * Get an unmodifiable collection view over all devices currently known.
+     *
+     * @return the collection of all devices
+     */
+    public Collection<? extends IDevice> getAllDevices();
+
+    /**
+     * Create an index over a set of fields. This allows efficient lookup of
+     * devices when querying using the indexed set of specified fields. The
+     * index must be registered before any device learning takes place, or it
+     * may be incomplete. It's OK if this is called multiple times with the same
+     * fields; only one index will be created for each unique set of fields.
+     *
+     * @param perClass
+     *            set to true if the index should be maintained for each entity
+     *            class separately.
+     * @param keyFields
+     *            the set of fields on which to index
+     */
+    public void addIndex(boolean perClass, EnumSet<DeviceField> keyFields);
+
+    /**
+     * Find devices that match the provided query. Any fields that are null will
+     * not be included in the query. If there is an index for the query, then it
+     * will be performed efficiently using the index. Otherwise, there will be a
+     * full scan of the device list.
+     *
+     * @param macAddress
+     *            The MAC address
+     * @param vlan
+     *            the VLAN
+     * @param ipv4Address
+     *            the ipv4 address
+     * @param port
+     *            the switch port
+     * @return an iterator over a set of devices matching the query
+     * @see IDeviceService#queryClassDevices(IEntityClass, Long, Short, Integer,
+     *      Long, Integer)
+     */
+    public Iterator<? extends IDevice> queryDevices(Long macAddress,
+            Short vlan, Integer ipv4Address, NodeConnector port);
+
+    /**
+     * Find devices that match the provided query. Only the index for the
+     * specified class will be searched. Any fields that are null will not be
+     * included in the query. If there is an index for the query, then it will
+     * be performed efficiently using the index. Otherwise, there will be a full
+     * scan of the device list.
+     *
+     * @param entityClass
+     *            The entity class in which to perform the query
+     * @param macAddress
+     *            The MAC address
+     * @param vlan
+     *            the VLAN
+     * @param ipv4Address
+     *            the ipv4 address
+     * @param port
+     *            the switch port
+     * @return an iterator over a set of devices matching the query
+     * @see IDeviceService#queryClassDevices(Long, Short, Integer, Long,
+     *      Integer)
+     */
+    public Iterator<? extends IDevice> queryClassDevices(
+            IEntityClass entityClass, Long macAddress, Short vlan,
+            Integer ipv4Address, NodeConnector port);
+
+    /**
+     * Adds a listener to listen for IDeviceManagerServices notifications
+     *
+     * @param listener
+     *            The listener that wants the notifications
+     * @param type
+     *            The type of the listener
+     */
+    public void addListener(IDeviceListener listener);
+
+    /**
+     * Specify points in the network where attachment points are not to be
+     * learned.
+     *
+     * @param sw
+     * @param port
+     */
+    public void addSuppressAPs(NodeConnector port);
+
+    public void removeSuppressAPs(NodeConnector port);
+
+    public Set<SwitchPort> getSuppressAPs();
+
+}
diff --git a/opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IEntityClass.java b/opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IEntityClass.java
new file mode 100644 (file)
index 0000000..7c1c100
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2011,2012 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * 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.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the
+ *    License. You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an "AS
+ *    IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ *    express or implied. See the License for the specific language
+ *    governing permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.hosttracker;
+
+import java.util.EnumSet;
+
+import org.opendaylight.controller.hosttracker.IDeviceService.DeviceField;
+import org.osgi.service.device.Device;
+
+/**
+ * Entities within an entity class are grouped into {@link Device} objects based
+ * on the {@link IEntityClass}, and the key fields specified by the entity
+ * 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 IEntityClassifierService} and/or
+ * {@link IEntityClass}.
+ *
+ * Note that if you're not using static objects, you'll need to override
+ * {@link Object#equals(Object)} and {@link Object#hashCode()}.
+ *
+ * @author readams
+ *
+ */
+public interface IEntityClass {
+    /**
+     * Return the set of key fields for this entity class. Entities belonging to
+     * this class that differ in fields not included in this collection will be
+     * considered the same device. The key fields for an entity class must not
+     * change unless associated with a flush of that entity class.
+     *
+     * @return a set containing the fields that should not 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/opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IEntityClassListener.java b/opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IEntityClassListener.java
new file mode 100644 (file)
index 0000000..be5f258
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2011 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * 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.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the
+ *    License. You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an "AS
+ *    IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ *    express or implied. See the License for the specific language
+ *    governing permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.hosttracker;
+
+import java.util.Set;
+
+/**
+ * Implementors of this interface can receive updates from the Entity Classifier
+ * about the changes to entity Classes.
+ *
+ * @author Ananth Suryanarayana (Ananth.Suryanarayana@bigswitch.com)
+ */
+public interface IEntityClassListener {
+
+    /**
+     * Process entity classes change event.
+     *
+     * @param entityClassNames
+     *            Set of entity classes changed
+     */
+    public void entityClassChanged(Set<String> entityClassNames);
+}
diff --git a/opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IEntityClassifierService.java b/opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IEntityClassifierService.java
new file mode 100644 (file)
index 0000000..f6071e1
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2011,2012 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * 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.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the
+ *    License. You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an "AS
+ *    IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ *    express or implied. See the License for the specific language
+ *    governing permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.hosttracker;
+
+import java.util.Collection;
+import java.util.EnumSet;
+
+import org.opendaylight.controller.hosttracker.IDeviceService.DeviceField;
+
+/**
+ * A component that wishes to participate in entity classification needs to
+ * implement the IEntityClassifier interface, and register with the Device
+ * Manager as an entity classifier. An entity is classified by the classifier
+ * into an {@link IEntityClass}
+ *
+ * @author readams
+ */
+public interface IEntityClassifierService {
+    /**
+     * 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.
+     *
+     * @param entity
+     *            the entity to classify
+     * @return the IEntityClass resulting from the classification.
+     * @see IEntityClassifierService#getKeyFields()
+     */
+    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 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 {@link IEntityClass#getKeyFields()}.
+     *
+     * @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 IEntityClassifierService#classifyEntity}
+     */
+    EnumSet<DeviceField> getKeyFields();
+
+    /**
+     * Reclassify the given entity into a class. When reclassifying entities, it
+     * can be helpful to take into account the current classification either as
+     * an optimization or to allow flushing any cached state tied to the key for
+     * that device. The entity will be assigned to a new device with a new
+     * object if the entity class returned is different from the entity class
+     * for curDevice.
+     *
+     * <p>
+     * Note that you must take steps to ensure you always return classes in some
+     * consistent ordering.
+     *
+     * @param curDevice
+     *            the device currently associated with the entity
+     * @param entity
+     *            the entity to reclassify
+     * @return the IEntityClass resulting from the classification
+     */
+    IEntityClass reclassifyEntity(IDevice curDevice, Entity entity);
+
+    /**
+     * Once reclassification is complete for a device, this method will be
+     * called. If any entities within the device changed their classification,
+     * it will split into one or more new devices for each of the entities. If
+     * two devices are merged because of a reclassification, then this will be
+     * called on each of the devices, with the same device in the newDevices
+     * collection.
+     *
+     * @param oldDevice
+     *            the original device object
+     * @param newDevices
+     *            all the new devices derived from the entities of the old
+     *            device. If null, the old device was unchanged.
+     */
+    void deviceUpdate(IDevice oldDevice,
+            Collection<? extends IDevice> newDevices);
+
+    /**
+     * Adds a listener to listen for IEntityClassifierServices notifications
+     *
+     * @param listener
+     *            The listener that wants the notifications
+     */
+    public void addListener(IEntityClassListener listener);
+}
diff --git a/opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IfHostListener.java b/opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IfHostListener.java
new file mode 100644 (file)
index 0000000..51d68d8
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.hosttracker;
+
+import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
+
+/**
+ * This interface defines the method to notify detected Host on the network. The
+ * information includes Host's IP address, MAC address, switch ID, port, and
+ * VLAN.
+ *
+ */
+
+public interface IfHostListener {
+    /**
+     * Learns new Hosts. Called by ArpHandler and implemented in
+     * HostTracker.java. If a Host is learned for the first time then adds it to
+     * the local database and informs other applications of coming up a new
+     * Host. For the hosts which it has already learned, it refreshes them.
+     *
+     * @param host
+     *            Host info encapsulated in HostNodeConnector class
+     */
+    public void hostListener(HostNodeConnector host);
+}
diff --git a/opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IfIptoHost.java b/opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IfIptoHost.java
new file mode 100644 (file)
index 0000000..995ee57
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.hosttracker;
+
+import java.net.InetAddress;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Future;
+
+import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.utils.Status;
+
+/**
+ * This interface defines the methods to retrieve information about learned
+ * Hosts. Also provides methods to statically add/remove Hosts from the local
+ * database.
+ *
+ */
+
+public interface IfIptoHost {
+    /**
+     * Applications call this interface methods to determine IP address to MAC
+     * binding and its connectivity to an OpenFlow switch in term of Node, Port,
+     * and VLAN. These bindings are learned dynamically as well as can be added
+     * statically through Northbound APIs. If a binding is unknown, then an ARP
+     * request is initiated immediately to discover the host.
+     *
+     * @param networkAddress
+     *            IP Address of the Host encapsulated in class InetAddress
+     * @return {@link org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector}
+     *         Class that contains the Host info such as its MAC address, Switch
+     *         ID, port, VLAN. If Host is not found, returns NULL
+     */
+    public HostNodeConnector hostFind(InetAddress networkAddress);
+
+    /**
+     * Checks the local Host Database to see if a Host has been learned for a
+     * given IP address.
+     *
+     * @param networkAddress
+     *            IP Address of the Host encapsulated in class InetAddress
+     * @return {@link org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector}
+     *         Class that contains the Host info such as its MAC address, Switch
+     *         ID, port, VLAN. If Host is not found, returns NULL
+     *
+     */
+    public HostNodeConnector hostQuery(InetAddress networkAddress);
+
+    /**
+     * Initiates an immediate discovery of the Host for a given IP address. This
+     * provides for the calling applications to block on the host discovery.
+     *
+     * @param networkAddress
+     *            IP address encapsulated in InetAddress class
+     * @return Future
+     *         {@link org.opendaylight.controller.hosttracker.HostTrackerCallable}
+     */
+    public Future<HostNodeConnector> discoverHost(InetAddress networkAddress);
+
+    /**
+     * Returns the Network Hierarchy for a given Host. This API is typically
+     * used by applications like Hadoop for Rack Awareness functionality.
+     *
+     * @param IP
+     *            address of the Host encapsulated in InetAddress class
+     * @return List of String ArrayList containing the Hierarchies.
+     */
+    public List<List<String>> getHostNetworkHierarchy(InetAddress hostAddress);
+
+    /**
+     * Returns all the the Hosts either learned dynamically or added statically
+     * via Northbound APIs.
+     *
+     * @return Set of
+     *         {@link org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector}
+     *         . Class that contains the Host info such as its MAC address,
+     *         Switch ID, port, VLAN.
+     */
+    public Set<HostNodeConnector> getAllHosts();
+
+    /**
+     * Returns all the "Active Hosts" learned "Statically" via Northbound APIs.
+     * These Hosts are categorized as "Active" because the Switch and Port they
+     * are connected to, are in up state.
+     *
+     * @return Set of
+     *         {@link org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector}
+     *         . Class that contains the Host info such as MAC address, Switch
+     *         ID, port, VLAN. If Host is not found, returns NULL
+     */
+    public Set<HostNodeConnector> getActiveStaticHosts();
+
+    /**
+     * Returns all the "Inactive Hosts" learned "Statically" via Northbound
+     * APIs. These Hosts are categorized as "Inactive" because either the Switch
+     * or the Port they are connected to, is in down state.
+     *
+     * @return Set of HostNodeConnector
+     *         {@link org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector}
+     *         . HostNodeConnector is Class that contains the Host info such as
+     *         its MAC address, OpenFlowNode ID, port, VLAN.
+     */
+    public Set<HostNodeConnector> getInactiveStaticHosts();
+
+    /**
+     * Hosts can be learned dynamically or added statically. This method allows
+     * the addition of a Host to the local database statically.
+     *
+     * @param networkAddress
+     *            IP Address of the Host
+     * @param dataLayerAddress
+     *            MAC Address of the Host
+     * @param nc
+     *            NodeConnector to which the host is attached
+     * @param vlan
+     *            VLAN the host belongs to
+     * @return The status object as described in {@code Status} indicating the
+     *         result of this action.
+     */
+    public Status addStaticHost(String networkAddress, String dataLayerAddress,
+            NodeConnector nc, String vlan);
+
+    /**
+     * Allows the deletion of statically learned Host
+     *
+     * @param networkAddress
+     * @return The status object as described in {@code Status} indicating the
+     *         result of this action.
+     */
+    public Status removeStaticHost(String networkAddress);
+}
diff --git a/opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IfNewHostNotify.java b/opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/IfNewHostNotify.java
new file mode 100644 (file)
index 0000000..ef900e4
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.hosttracker;
+
+import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
+
+/**
+ * This Interface defines the methods for client applications of Host Tracker to
+ * get notifications when a new host is learned or existing host is removed from
+ * the network.
+ *
+ */
+public interface IfNewHostNotify {
+    /**
+     * Notifies the HostTracker Clients that a new Host has been learned
+     *
+     * @param host
+     *            Host Info encapsulated in HostNodeConnector class
+     */
+    public void notifyHTClient(HostNodeConnector host);
+
+    /**
+     * Notifies the HostTracker Clients that a Host which was learned in the
+     * past has been removed either due to switch/port down event or due to ARP
+     * Aging
+     *
+     * @param host
+     *            Host Info encapsulated in HostNodeConnector class
+     */
+    public void notifyHTClientHostRemoved(HostNodeConnector host);
+}
diff --git a/opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/SwitchPort.java b/opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/SwitchPort.java
new file mode 100644 (file)
index 0000000..e60f8b4
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2012 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * 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.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the
+ *    License. You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an "AS
+ *    IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ *    express or implied. See the License for the specific language
+ *    governing permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.hosttracker;
+
+import org.opendaylight.controller.sal.core.NodeConnector;
+
+/**
+ * A simple switch DPID/port pair This class is immutable
+ *
+ * @author readams
+ *
+ */
+public class SwitchPort {
+    public enum ErrorStatus {
+        DUPLICATE_DEVICE("duplicate-device");
+
+        private String value;
+
+        ErrorStatus(String v) {
+            value = v;
+        }
+
+        @Override
+        public String toString() {
+            return value;
+        }
+
+        public static ErrorStatus fromString(String str) {
+            for (ErrorStatus m : ErrorStatus.values()) {
+                if (m.value.equals(str)) {
+                    return m;
+                }
+            }
+            return null;
+        }
+    }
+
+    private final NodeConnector port;
+    private final ErrorStatus errorStatus;
+
+    /**
+     * Simple constructor
+     *
+     * @param switchDPID
+     *            the dpid
+     * @param port
+     *            the port
+     * @param errorStatus
+     *            any error status for the switch port
+     */
+    public SwitchPort(NodeConnector port, ErrorStatus errorStatus) {
+        super();
+        this.port = port;
+        this.errorStatus = errorStatus;
+    }
+
+    /**
+     * Simple constructor
+     *
+     * @param switchDPID
+     *            the dpid
+     * @param port
+     *            the port
+     */
+    public SwitchPort(NodeConnector port) {
+        super();
+        this.port = port;
+        this.errorStatus = null;
+    }
+
+    // ***************
+    // Getters/Setters
+    // ***************
+
+    public NodeConnector getPort() {
+        return port;
+    }
+
+    public ErrorStatus getErrorStatus() {
+        return errorStatus;
+    }
+
+    // ******
+    // Object
+    // ******
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                + ((errorStatus == null) ? 0 : errorStatus.hashCode());
+        result = prime * result + ((port == null) ? 0 : port.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        SwitchPort other = (SwitchPort) obj;
+        if (errorStatus != other.errorStatus)
+            return false;
+        if (port == null) {
+            if (other.port != null)
+                return false;
+        } else if (!port.equals(other.port))
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "SwitchPort [port=" + port + ", errorStatus=" + errorStatus
+                + "]";
+    }
+}
\ No newline at end of file
diff --git a/opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/hostAware/HostNodeConnector.java b/opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/hostAware/HostNodeConnector.java
new file mode 100644 (file)
index 0000000..fe396ba
--- /dev/null
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.hosttracker.hostAware;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.util.Arrays;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
+import org.opendaylight.controller.sal.core.ConstructionException;
+import org.opendaylight.controller.sal.core.Host;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.packet.address.EthernetAddress;
+
+@XmlRootElement(name = "host")
+@XmlAccessorType(XmlAccessType.NONE)
+public class HostNodeConnector extends Host {
+    private static final long serialVersionUID = 1L;
+    @XmlElement
+    private NodeConnector nodeConnector;
+    @XmlElement
+    private short vlan;
+    @XmlElement
+    private boolean staticHost;
+    private transient short arpSendCountDown;
+
+    /**
+     * Private constructor used for JAXB mapping
+     */
+    @SuppressWarnings("unused")
+    private HostNodeConnector() {
+    }
+
+    public HostNodeConnector(InetAddress ip) throws ConstructionException {
+        this(ip, null);
+    }
+
+    public HostNodeConnector(InetAddress ip, NodeConnector nc)
+            throws ConstructionException {
+        this(new EthernetAddress(new byte[] { (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }), ip, nc,
+                (short) 0);
+    }
+
+    public HostNodeConnector(byte[] mac, InetAddress ip, NodeConnector nc,
+            short vlan) throws ConstructionException {
+        this(new EthernetAddress(mac.clone()), ip, nc, vlan);
+    }
+
+    public HostNodeConnector(EthernetAddress eaddr, InetAddress naddr,
+            NodeConnector nc, short vlan) throws ConstructionException {
+        super(eaddr, naddr);
+        this.nodeConnector = nc;
+        this.vlan = vlan;
+    }
+
+    /**
+     * @return the NodeConnector
+     */
+    public NodeConnector getnodeConnector() {
+        return this.nodeConnector;
+    }
+
+    /**
+     * @return the Node
+     */
+    public Node getnodeconnectorNode() {
+        return this.nodeConnector.getNode();
+    }
+
+    /**
+     * @return the NodeId
+     */
+    public Long getnodeconnectornodeId() {
+        return (Long) this.nodeConnector.getNode().getID();
+    }
+
+    /**
+     * @return the port
+     */
+    public Short getnodeconnectorportId() {
+        return (Short) this.nodeConnector.getID();
+    }
+
+    /**
+     * @return the DataLayerAddress
+     */
+    public byte[] getDataLayerAddressBytes() {
+        byte[] macaddr = null;
+        if (getDataLayerAddress() instanceof EthernetAddress) {
+            EthernetAddress e = (EthernetAddress) getDataLayerAddress();
+            macaddr = e.getValue();
+        }
+        return macaddr;
+    }
+
+    /**
+     * @return the vlan
+     */
+    public short getVlan() {
+        return this.vlan;
+    }
+
+    public boolean isStaticHost() {
+        return this.staticHost;
+    }
+
+    public HostNodeConnector setStaticHost(boolean statically_learned) {
+        this.staticHost = statically_learned;
+        return this;
+    }
+
+    public HostNodeConnector initArpSendCountDown() {
+        this.arpSendCountDown = 24;
+        return this;
+    }
+
+    public short getArpSendCountDown() {
+        return (this.arpSendCountDown);
+    }
+
+    public HostNodeConnector setArpSendCountDown(short cntdown) {
+        this.arpSendCountDown = cntdown;
+        return this;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = super.hashCode();
+        result = prime * result
+                + ((nodeConnector == null) ? 0 : nodeConnector.hashCode());
+        result = prime * result + (staticHost ? 1231 : 1237);
+        result = prime * result + vlan;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (!super.equals(obj))
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        HostNodeConnector other = (HostNodeConnector) obj;
+        if (nodeConnector == null) {
+            if (other.nodeConnector != null)
+                return false;
+        } else if (!nodeConnector.equals(other.nodeConnector))
+            return false;
+        if (staticHost != other.staticHost)
+            return false;
+        if (vlan != other.vlan)
+            return false;
+        return true;
+    }
+
+    public boolean equalsByIP(InetAddress networkAddress) {
+        return (this.getNetworkAddress().equals(networkAddress));
+    }
+
+    public boolean isRewriteEnabled() {
+        byte[] emptyArray = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00 };
+        byte[] macaddr = null;
+        if (getDataLayerAddress() instanceof EthernetAddress) {
+            EthernetAddress e = (EthernetAddress) getDataLayerAddress();
+            macaddr = e.getValue();
+        }
+        if (macaddr == null)
+            return false;
+        return !Arrays.equals(emptyArray, macaddr);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return "HostNodeConnector[" + ReflectionToStringBuilder.toString(this)
+                + "]";
+    }
+
+    public boolean isV4Host() {
+        return (getNetworkAddress() instanceof Inet4Address);
+    }
+
+    public boolean isV6Host() {
+        return (getNetworkAddress() instanceof Inet6Address);
+    }
+
+    public String toJson() {
+        return "{\"host\":\"" + super.toString() + "\", " + "\"vlan\":\""
+                + String.valueOf(vlan) + "\",\"NodeConnector\":\""
+                + nodeConnector.toString() + "\"," + "\"static\":\""
+                + String.valueOf(isStaticHost()) + "\"}";
+    }
+
+}
diff --git a/opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/hostAware/IHostFinder.java b/opendaylight/hosttracker_new/api/src/main/java/org/opendaylight/controller/hosttracker/hostAware/IHostFinder.java
new file mode 100644 (file)
index 0000000..abe7518
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.hosttracker.hostAware;
+
+import java.net.InetAddress;
+
+/**
+ * This Interface defines the methods to trigger the discovery of a Host and to
+ * probe if a learned Host is still in the network.
+ *
+ *
+ *
+ */
+public interface IHostFinder {
+    /**
+     * This method initiates the discovery of a host based on its IP address.
+     * This is triggered by query of an application to the HostTracker. The
+     * requested IP address doesn't exist in the local database at this point.
+     *
+     * @param networkAddress
+     *            IP Address encapsulated in InetAddress class
+     *
+     */
+    public void find(InetAddress networkAddress);
+
+    /**
+     * This method is called by HostTracker to see if a learned Host is still in
+     * the network. Used mostly for ARP Aging.
+     *
+     * @param host
+     *            The Host that needs to be probed
+     */
+    public void probe(HostNodeConnector host);
+}
diff --git a/opendaylight/hosttracker_new/api/src/test/java/org/opendaylight/controller/hosttracker/hostAware/HostNodeConnectorTest.java b/opendaylight/hosttracker_new/api/src/test/java/org/opendaylight/controller/hosttracker/hostAware/HostNodeConnectorTest.java
new file mode 100644 (file)
index 0000000..0c3ca2f
--- /dev/null
@@ -0,0 +1,100 @@
+/*\r
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.\r
+ *\r
+ * This program and the accompanying materials are made available under the\r
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,\r
+ * and is available at http://www.eclipse.org/legal/epl-v10.html\r
+ */\r
+\r
+package org.opendaylight.controller.hosttracker.hostAware;\r
+\r
+import java.net.InetAddress;\r
+import java.net.UnknownHostException;\r
+\r
+import org.junit.Assert;\r
+import org.junit.Test;\r
+\r
+import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;\r
+import org.opendaylight.controller.sal.core.ConstructionException;\r
+import org.opendaylight.controller.sal.core.Node;\r
+\r
+import junit.framework.TestCase;\r
+\r
+import org.opendaylight.controller.sal.packet.address.EthernetAddress;\r
+\r
+import org.opendaylight.controller.sal.utils.NodeConnectorCreator;\r
+import org.opendaylight.controller.sal.core.NodeConnector;\r
+import org.opendaylight.controller.sal.utils.NodeCreator;\r
+\r
+public class HostNodeConnectorTest extends TestCase {\r
+\r
+    @Test\r
+    public void testHostNodeConnector() throws UnknownHostException {\r
+        HostNodeConnector hostnodeconnector_1, hostnodeconnector_2, hostnodeconnector_3;\r
+        InetAddress hostIP_1 = InetAddress.getByName("192.168.0.8");\r
+        InetAddress hostIP_2 = InetAddress\r
+                .getByName("2001:420:281:1004:e123:e688:d655:a1b0");\r
+        InetAddress hostIP_3 = InetAddress.getByName("192.168.0.28");\r
+        byte[] hostMAC_2 = new byte[] { (byte) 0x11, (byte) 0x22, (byte) 0x33,\r
+                (byte) 0x22, (byte) 0x22, (byte) 0x22 };\r
+        byte[] hostMAC_3 = new byte[] { (byte) 0x11, (byte) 0x22, (byte) 0x33,\r
+                (byte) 0x33, (byte) 0x33, (byte) 0x33 };\r
+\r
+        Node node = NodeCreator.createOFNode(1L);\r
+        NodeConnector nc1 = NodeConnectorCreator.createOFNodeConnector(\r
+                (short) 2, node);\r
+        NodeConnector nc2 = NodeConnectorCreator.createOFNodeConnector(\r
+                (short) 1, node);\r
+\r
+        try {\r
+            hostnodeconnector_1 = new HostNodeConnector(hostIP_1);\r
+            Assert.assertTrue(hostnodeconnector_1.equalsByIP(hostIP_1));\r
+            Assert.assertTrue(hostnodeconnector_1.isV4Host());\r
+            Assert.assertTrue(hostnodeconnector_1.equalsByIP(hostIP_1));\r
+        } catch (ConstructionException e) {\r
+            Assert.assertTrue(false);\r
+        }\r
+\r
+        try {\r
+            hostnodeconnector_2 = new HostNodeConnector(hostMAC_2, hostIP_2,\r
+                    nc1, (short) 2);\r
+            Assert.assertTrue(hostnodeconnector_2.isV6Host());\r
+            Assert.assertTrue(hostnodeconnector_2.getnodeConnector()\r
+                    .equals(nc1));\r
+            Assert.assertTrue(hostnodeconnector_2.getnodeconnectorNode()\r
+                    .equals(node));\r
+            Assert.assertTrue(node.getID().equals(\r
+                    hostnodeconnector_2.getnodeconnectornodeId()));\r
+            Assert.assertTrue(hostnodeconnector_2.getnodeconnectorportId()\r
+                    .equals((short) 2));\r
+        } catch (ConstructionException e) {\r
+            Assert.assertTrue(false);\r
+        }\r
+\r
+        try {\r
+            hostnodeconnector_3 = new HostNodeConnector(new EthernetAddress(\r
+                    hostMAC_3), hostIP_3, nc2, (short) 3);\r
+            byte[] hostMAC_3_rb = hostnodeconnector_3\r
+                    .getDataLayerAddressBytes();\r
+            HostNodeConnector hostnodeconnector_3rb = new HostNodeConnector(\r
+                    new EthernetAddress(hostMAC_3_rb), hostIP_3, nc2, (short) 3);\r
+            Assert.assertTrue(hostnodeconnector_3.equals(hostnodeconnector_3rb));\r
+\r
+            Assert.assertTrue(hostnodeconnector_3.getVlan() == (short) 3);\r
+\r
+            hostnodeconnector_3.setStaticHost(true);\r
+            Assert.assertTrue(hostnodeconnector_3.isStaticHost());\r
+\r
+            Assert.assertTrue(hostnodeconnector_3.isRewriteEnabled());\r
+\r
+            hostnodeconnector_3.initArpSendCountDown().setArpSendCountDown(\r
+                    (short) 10);\r
+            Assert.assertTrue(hostnodeconnector_3.getArpSendCountDown() == (short) 10);\r
+\r
+        } catch (ConstructionException e) {\r
+            Assert.assertTrue(false);\r
+        }\r
+\r
+    }\r
+\r
+}\r
diff --git a/opendaylight/hosttracker_new/implementation/pom.xml b/opendaylight/hosttracker_new/implementation/pom.xml
new file mode 100644 (file)
index 0000000..0201fd8
--- /dev/null
@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+  xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.opendaylight</artifactId>
+    <version>1.4.0-SNAPSHOT</version>
+    <relativePath>../../commons/opendaylight</relativePath>
+  </parent>
+  <artifactId>hosttracker_new.implementation</artifactId>
+  <version>0.4.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+
+  <properties>
+    <!-- Sonar properties using jacoco to retrieve integration test results -->
+    <sonar.java.coveragePlugin>jacoco</sonar.java.coveragePlugin>
+    <sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis>
+    <sonar.jacoco.Reportpath>target/jacoco.exec</sonar.jacoco.Reportpath>
+    <sonar.jacoco.itReportPath>target/jacoco-it.exec</sonar.jacoco.itReportPath>
+    <sonar.language>java</sonar.language>
+  </properties>
+
+  <build>
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <groupId>org.jacoco</groupId>
+          <artifactId>jacoco-maven-plugin</artifactId>
+          <version>0.5.3.201107060350</version>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.3.6</version>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Export-Package>
+            </Export-Package>
+            <Import-Package>
+              org.opendaylight.controller.sal.core,
+              org.opendaylight.controller.sal.utils,
+              org.opendaylight.controller.sal.topology,
+              org.opendaylight.controller.sal.packet,
+              org.opendaylight.controller.hosttracker,
+              org.opendaylight.controller.topologymanager,
+              org.opendaylight.controller.sal.packet.address,
+              org.opendaylight.controller.switchmanager,
+              org.opendaylight.controller.clustering.services,
+              org.opendaylight.controller.hosttracker_new.hostAware,
+              javax.xml.bind.annotation,
+              javax.xml.bind,
+              org.apache.felix.dm,
+              org.apache.commons.lang3.builder,
+              org.osgi.service.component,
+              org.slf4j,
+              org.eclipse.osgi.framework.console,
+              org.osgi.framework
+            </Import-Package>
+            <Bundle-Activator>
+              org.opendaylight.controller.hosttracker.internal.Activator
+            </Bundle-Activator>
+            <Service-Component>
+            </Service-Component>
+          </instructions>
+          <manifestLocation>${project.basedir}/META-INF</manifestLocation>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.jacoco</groupId>
+        <artifactId>jacoco-maven-plugin</artifactId>
+        <configuration>
+          <includes>org.opendaylight.controller.*</includes>
+        </configuration>
+        <executions>
+          <execution>
+            <id>pre-test</id>
+            <goals>
+              <goal>prepare-agent</goal>
+            </goals>
+          </execution>
+          <execution>
+            <id>post-test</id>
+            <phase>test</phase>
+            <goals>
+              <goal>report</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>topologymanager</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>switchmanager</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>clustering.services</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal</artifactId>
+      <version>0.5.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>hosttracker_new</artifactId>
+      <version>0.4.0-SNAPSHOT</version>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/Activator.java b/opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/Activator.java
new file mode 100644 (file)
index 0000000..631a65a
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.hosttracker.internal;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.felix.dm.Component;
+import org.opendaylight.controller.hosttracker.IDeviceService;
+import org.opendaylight.controller.sal.core.ComponentActivatorAbstractBase;
+import org.opendaylight.controller.sal.packet.IDataPacketService;
+import org.opendaylight.controller.sal.packet.IListenDataPacket;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+import org.opendaylight.controller.topologymanager.ITopologyManager;
+import org.opendaylight.controller.topologymanager.ITopologyManagerAware;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Activator extends ComponentActivatorAbstractBase {
+    protected static final Logger logger = LoggerFactory
+            .getLogger(Activator.class);
+
+    @Override
+    protected void init() {
+
+    }
+
+    @Override
+    protected void destroy() {
+
+    }
+
+    /**
+     * Function that is used to communicate to dependency manager the list of
+     * known implementations for services inside a container
+     *
+     *
+     * @return An array containing all the CLASS objects that will be
+     *         instantiated in order to get an fully working implementation
+     *         Object
+     */
+    @Override
+    public Object[] getImplementations() {
+        Object[] res = { DeviceManagerImpl.class };
+        return res;
+    }
+
+    /**
+     * Function that is called when configuration of the dependencies is
+     * required.
+     *
+     * @param c
+     *            dependency manager Component object, used for configuring the
+     *            dependencies exported and imported
+     * @param imp
+     *            Implementation class that is being configured, needed as long
+     *            as the same routine can configure multiple implementations
+     * @param containerName
+     *            The containerName being configured, this allow also optional
+     *            per-container different behavior if needed, usually should not
+     *            be the case though.
+     */
+    @Override
+    public void configureInstance(Component c, Object imp, String containerName) {
+        if (imp.equals(DeviceManagerImpl.class)) {
+            // export the service
+            // XXX - TODO merge with existing APIs
+            Dictionary<String, String> props = new Hashtable<String, String>();
+            props.put("salListenerName", "devicemanager");
+
+            c.setInterface(new String[] { IDeviceService.class.getName(),
+                    IListenDataPacket.class.getName(),
+                    ITopologyManagerAware.class.getName() }, props);
+
+            c.add(createContainerServiceDependency(containerName)
+                    .setService(ISwitchManager.class)
+                    .setCallbacks("setSwitchManager", "unsetSwitchManager")
+                    .setRequired(false));
+
+            c.add(createContainerServiceDependency(containerName)
+                    .setService(IDataPacketService.class)
+                    .setCallbacks("setDataPacketService",
+                            "unsetDataPacketService").setRequired(true));
+
+            // c.add(createContainerServiceDependency(containerName).setService(
+            // IClusterContainerServices.class).setCallbacks(
+            // "setClusterContainerService",
+            // "unsetClusterContainerService").setRequired(true));
+            c.add(createContainerServiceDependency(containerName)
+                    .setService(ITopologyManager.class)
+                    .setCallbacks("setTopologyManager", "unsetTopologyManager")
+                    .setRequired(false));
+
+            c.add(createContainerServiceDependency(containerName)
+                    .setService(IDataPacketService.class)
+                    .setCallbacks("setDataPacketService",
+                            "unsetDataPacketService").setRequired(true));
+        }
+    }
+
+    /**
+     * Method which tells how many Global implementations are supported by the
+     * bundle. This way we can tune the number of components created. This
+     * components will be created ONLY at the time of bundle startup and will be
+     * destroyed only at time of bundle destruction, this is the major
+     * difference with the implementation retrieved via getImplementations where
+     * all of them are assumed to be in a container !
+     *
+     *
+     * @return The list of implementations the bundle will support, in Global
+     *         version
+     */
+    @Override
+    protected Object[] getGlobalImplementations() {
+        return null;
+    }
+
+    /**
+     * Configure the dependency for a given instance Global
+     *
+     * @param c
+     *            Component assigned for this instance, this will be what will
+     *            be used for configuration
+     * @param imp
+     *            implementation to be configured
+     * @param containerName
+     *            container on which the configuration happens
+     */
+    @Override
+    protected void configureGlobalInstance(Component c, Object imp) {
+
+    }
+
+}
diff --git a/opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/AttachmentPoint.java b/opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/AttachmentPoint.java
new file mode 100644 (file)
index 0000000..ba2c64a
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2011,2012 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * 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.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    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.
+ */
+
+/**
+ * @author Srini
+ */
+
+package org.opendaylight.controller.hosttracker.internal;
+
+import org.opendaylight.controller.sal.core.NodeConnector;
+
+public class AttachmentPoint {
+    NodeConnector port;
+    long activeSince;
+    long lastSeen;
+
+    // Timeout for moving attachment points from OF/broadcast
+    // domain to another.
+    public static final long INACTIVITY_INTERVAL = 30000; // 30 seconds
+    public static final long EXTERNAL_TO_EXTERNAL_TIMEOUT = 5000; // 5 seconds
+    public static final long OPENFLOW_TO_EXTERNAL_TIMEOUT = 30000; // 30 seconds
+    public static final long CONSISTENT_TIMEOUT = 30000; // 30 seconds
+
+    public AttachmentPoint(NodeConnector port, long activeSince, long lastSeen) {
+        this.port = port;
+        this.activeSince = activeSince;
+        this.lastSeen = lastSeen;
+    }
+
+    public AttachmentPoint(NodeConnector port, long lastSeen) {
+        this.port = port;
+        this.lastSeen = lastSeen;
+        this.activeSince = lastSeen;
+    }
+
+    public AttachmentPoint(AttachmentPoint ap) {
+        this.port = ap.port;
+        this.activeSince = ap.activeSince;
+        this.lastSeen = ap.lastSeen;
+    }
+
+    public NodeConnector getPort() {
+        return port;
+    }
+
+    public void setPort(NodeConnector port) {
+        this.port = port;
+    }
+
+    public long getActiveSince() {
+        return activeSince;
+    }
+
+    public void setActiveSince(long activeSince) {
+        this.activeSince = activeSince;
+    }
+
+    public long getLastSeen() {
+        return lastSeen;
+    }
+
+    public void setLastSeen(long lastSeen) {
+        if (this.lastSeen + INACTIVITY_INTERVAL < lastSeen)
+            this.activeSince = lastSeen;
+        if (this.lastSeen < lastSeen)
+            this.lastSeen = lastSeen;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((port == null) ? 0 : port.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        AttachmentPoint other = (AttachmentPoint) obj;
+        if (port == null) {
+            if (other.port != null)
+                return false;
+        } else if (!port.equals(other.port))
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "AttachmentPoint [port=" + port + ", activeSince=" + activeSince
+                + ", lastSeen=" + lastSeen + "]";
+    }
+}
diff --git a/opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/DefaultEntityClassifier.java b/opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/DefaultEntityClassifier.java
new file mode 100644 (file)
index 0000000..39c322f
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2011,2012 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * 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.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the
+ *    License. You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an "AS
+ *    IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ *    express or implied. See the License for the specific language
+ *    governing permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.hosttracker.internal;
+
+import java.util.Collection;
+import java.util.EnumSet;
+
+import org.opendaylight.controller.hosttracker.Entity;
+import org.opendaylight.controller.hosttracker.IDevice;
+import org.opendaylight.controller.hosttracker.IDeviceService;
+import org.opendaylight.controller.hosttracker.IDeviceService.DeviceField;
+import org.opendaylight.controller.hosttracker.IEntityClass;
+import org.opendaylight.controller.hosttracker.IEntityClassListener;
+import org.opendaylight.controller.hosttracker.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 IEntityClassifierService {
+    /**
+     * A default fixed entity class
+     */
+    protected static class DefaultEntityClass implements IEntityClass {
+        String name;
+
+        public DefaultEntityClass(String name) {
+            this.name = name;
+        }
+
+        @Override
+        public EnumSet<IDeviceService.DeviceField> getKeyFields() {
+            return keyFields;
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+    }
+
+    protected static EnumSet<DeviceField> keyFields;
+    static {
+        keyFields = EnumSet.of(DeviceField.MAC, DeviceField.VLAN);
+    }
+    protected static DefaultEntityClass entityClass = new DefaultEntityClass(
+            "DefaultEntityClass");
+
+    @Override
+    public IEntityClass classifyEntity(Entity entity) {
+        return entityClass;
+    }
+
+    @Override
+    public IEntityClass reclassifyEntity(IDevice curDevice, Entity entity) {
+        return entityClass;
+    }
+
+    @Override
+    public void deviceUpdate(IDevice oldDevice,
+            Collection<? extends IDevice> newDevices) {
+        // no-op
+    }
+
+    @Override
+    public EnumSet<DeviceField> getKeyFields() {
+        return keyFields;
+    }
+
+    @Override
+    public void addListener(IEntityClassListener listener) {
+        // no-op
+
+    }
+}
diff --git a/opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/Device.java b/opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/Device.java
new file mode 100755 (executable)
index 0000000..90911fe
--- /dev/null
@@ -0,0 +1,805 @@
+/*
+ * Copyright (c) 2011,2012 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * 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.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the
+ *    License. You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an "AS
+ *    IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ *    express or implied. See the License for the specific language
+ *    governing permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.hosttracker.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeSet;
+
+import org.opendaylight.controller.hosttracker.Entity;
+import org.opendaylight.controller.hosttracker.IDevice;
+import org.opendaylight.controller.hosttracker.IDeviceService.DeviceField;
+import org.opendaylight.controller.hosttracker.IEntityClass;
+import org.opendaylight.controller.hosttracker.SwitchPort;
+import org.opendaylight.controller.hosttracker.SwitchPort.ErrorStatus;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.utils.HexEncode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Concrete implementation of {@link IDevice}
+ *
+ * @author readams
+ */
+public class Device implements IDevice {
+    protected static Logger log = LoggerFactory.getLogger(Device.class);
+    public static final short VLAN_UNTAGGED = (short) 0xffff;
+
+    private final Long deviceKey;
+    protected final DeviceManagerImpl deviceManager;
+
+    protected final Entity[] entities;
+    private final IEntityClass entityClass;
+
+    protected final String macAddressString;
+    // the vlan Ids from the entities of this device
+    protected final Short[] vlanIds;
+    protected volatile String dhcpClientName;
+
+    /**
+     * These are the old attachment points for the device that were valid no
+     * more than INACTIVITY_TIME ago.
+     */
+    protected volatile List<AttachmentPoint> oldAPs;
+    /**
+     * The current attachment points for the device.
+     */
+    protected volatile List<AttachmentPoint> attachmentPoints;
+
+    // ************
+    // Constructors
+    // ************
+
+    /**
+     * Create a device from an entities
+     *
+     * @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 entityClass
+     *            the entity classes associated with the entity
+     */
+    public Device(DeviceManagerImpl deviceManager, Long deviceKey,
+            Entity entity, IEntityClass entityClass) {
+        this.deviceManager = deviceManager;
+        this.deviceKey = deviceKey;
+        this.entities = new Entity[] { entity };
+        this.macAddressString = HexEncode.longToHexString(entity
+                .getMacAddress());
+        this.entityClass = entityClass;
+        Arrays.sort(this.entities);
+
+        this.dhcpClientName = null;
+        this.oldAPs = null;
+        this.attachmentPoints = null;
+
+        if (entity.getPort() != null) {
+            NodeConnector port = entity.getPort();
+
+            if (deviceManager.isValidAttachmentPoint(port)) {
+                AttachmentPoint ap;
+                ap = new AttachmentPoint(port, entity.getLastSeenTimestamp()
+                        .getTime());
+
+                this.attachmentPoints = new ArrayList<AttachmentPoint>();
+                this.attachmentPoints.add(ap);
+            }
+        }
+        vlanIds = computeVlandIds();
+    }
+
+    /**
+     * Create a device from a set of entities
+     *
+     * @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 entityClass
+     *            the entity class associated with the entities
+     */
+    public Device(DeviceManagerImpl deviceManager, Long deviceKey,
+            String dhcpClientName, Collection<AttachmentPoint> oldAPs,
+            Collection<AttachmentPoint> attachmentPoints,
+            Collection<Entity> entities, IEntityClass entityClass) {
+        this.deviceManager = deviceManager;
+        this.deviceKey = deviceKey;
+        this.dhcpClientName = dhcpClientName;
+        this.entities = entities.toArray(new Entity[entities.size()]);
+        this.oldAPs = null;
+        this.attachmentPoints = null;
+        if (oldAPs != null) {
+            this.oldAPs = new ArrayList<AttachmentPoint>(oldAPs);
+        }
+        if (attachmentPoints != null) {
+            this.attachmentPoints = new ArrayList<AttachmentPoint>(
+                    attachmentPoints);
+        }
+        this.macAddressString = HexEncode.longToHexString(this.entities[0]
+                .getMacAddress());
+        this.entityClass = entityClass;
+        Arrays.sort(this.entities);
+        vlanIds = computeVlandIds();
+    }
+
+    /**
+     * Construct a new device consisting of the entities from the old device
+     * plus an additional entity. The caller needs to ensure that the additional
+     * entity is not already present in the array
+     *
+     * @param device
+     *            the old device object
+     * @param newEntity
+     *            the entity to add. newEntity must be have the same entity
+     *            class as device
+     * @param if positive indicates the index in the entities array were the new
+     *        entity should be inserted. If negative we will compute the correct
+     *        insertion point
+     */
+    public Device(Device device, Entity newEntity, int insertionpoint) {
+        this.deviceManager = device.deviceManager;
+        this.deviceKey = device.deviceKey;
+        this.dhcpClientName = device.dhcpClientName;
+
+        this.entities = new Entity[device.entities.length + 1];
+        if (insertionpoint < 0) {
+            insertionpoint = -(Arrays.binarySearch(device.entities, newEntity) + 1);
+        }
+        if (insertionpoint > 0) {
+            // insertion point is not the beginning:
+            // copy up to insertion point
+            System.arraycopy(device.entities, 0, this.entities, 0,
+                    insertionpoint);
+        }
+        if (insertionpoint < device.entities.length) {
+            // insertion point is not the end
+            // copy from insertion point
+            System.arraycopy(device.entities, insertionpoint, this.entities,
+                    insertionpoint + 1, device.entities.length - insertionpoint);
+        }
+        this.entities[insertionpoint] = newEntity;
+        /*
+         * this.entities = Arrays.<Entity>copyOf(device.entities,
+         * device.entities.length + 1); this.entities[this.entities.length - 1]
+         * = newEntity; Arrays.sort(this.entities);
+         */
+        this.oldAPs = null;
+        if (device.oldAPs != null) {
+            this.oldAPs = new ArrayList<AttachmentPoint>(device.oldAPs);
+        }
+        this.attachmentPoints = null;
+        if (device.attachmentPoints != null) {
+            this.attachmentPoints = new ArrayList<AttachmentPoint>(
+                    device.attachmentPoints);
+        }
+
+        this.macAddressString = HexEncode.longToHexString(this.entities[0]
+                .getMacAddress());
+
+        this.entityClass = device.entityClass;
+        vlanIds = computeVlandIds();
+    }
+
+    private Short[] computeVlandIds() {
+        if (entities.length == 1) {
+            if (entities[0].getVlan() != null) {
+                return new Short[] { entities[0].getVlan() };
+            } else {
+                return new Short[] { Short.valueOf((short) -1) };
+            }
+        }
+
+        TreeSet<Short> vals = new TreeSet<Short>();
+        for (Entity e : entities) {
+            if (e.getVlan() == null)
+                vals.add((short) -1);
+            else
+                vals.add(e.getVlan());
+        }
+        return vals.toArray(new Short[vals.size()]);
+    }
+
+    /**
+     * Given a list of attachment points (apList), the procedure would return a
+     * map of attachment points for each L2 domain. L2 domain id is the key.
+     *
+     * @param apList
+     * @return
+     */
+    private Map<Long, AttachmentPoint> getAPMap(List<AttachmentPoint> apList) {
+
+        if (apList == null)
+            return null;
+        // ITopologyService topology = deviceManager.topology;
+
+        // Get the old attachment points and sort them.
+        List<AttachmentPoint> oldAP = new ArrayList<AttachmentPoint>();
+        if (apList != null)
+            oldAP.addAll(apList);
+
+        // Remove invalid attachment points before sorting.
+        List<AttachmentPoint> tempAP = new ArrayList<AttachmentPoint>();
+        for (AttachmentPoint ap : oldAP) {
+            if (deviceManager.isValidAttachmentPoint(ap.getPort())) {
+                tempAP.add(ap);
+            }
+        }
+        oldAP = tempAP;
+
+        Collections.sort(oldAP, deviceManager.apComparator);
+
+        // Map of attachment point by L2 domain Id.
+        Map<Long, AttachmentPoint> apMap = new HashMap<Long, AttachmentPoint>();
+
+        for (int i = 0; i < oldAP.size(); ++i) {
+            AttachmentPoint ap = oldAP.get(i);
+            // if this is not a valid attachment point, continue
+            if (!deviceManager.isValidAttachmentPoint(ap.getPort()))
+                continue;
+
+            // long id = topology.getL2DomainId(ap.getSw());
+            // XXX - Missing functionality
+            long id = 0;
+
+            apMap.put(id, ap);
+        }
+
+        if (apMap.isEmpty())
+            return null;
+        return apMap;
+    }
+
+    /**
+     * Remove all attachment points that are older than INACTIVITY_INTERVAL from
+     * the list.
+     *
+     * @param apList
+     * @return
+     */
+    private boolean removeExpiredAttachmentPoints(List<AttachmentPoint> apList) {
+
+        List<AttachmentPoint> expiredAPs = new ArrayList<AttachmentPoint>();
+
+        if (apList == null)
+            return false;
+
+        for (AttachmentPoint ap : apList) {
+            if (ap.getLastSeen() + AttachmentPoint.INACTIVITY_INTERVAL < System
+                    .currentTimeMillis())
+                expiredAPs.add(ap);
+        }
+        if (expiredAPs.size() > 0) {
+            apList.removeAll(expiredAPs);
+            return true;
+        } else
+            return false;
+    }
+
+    /**
+     * Get a list of duplicate attachment points, given a list of old attachment
+     * points and one attachment point per L2 domain. Given a true attachment
+     * point in the L2 domain, say trueAP, another attachment point in the same
+     * L2 domain, say ap, is duplicate if: 1. ap is inconsistent with trueAP,
+     * and 2. active time of ap is after that of trueAP; and 3. last seen time
+     * of ap is within the last INACTIVITY_INTERVAL
+     *
+     * @param oldAPList
+     * @param apMap
+     * @return
+     */
+    List<AttachmentPoint> getDuplicateAttachmentPoints(
+            List<AttachmentPoint> oldAPList, Map<Long, AttachmentPoint> apMap) {
+        // ITopologyService topology = deviceManager.topology;
+        List<AttachmentPoint> dupAPs = new ArrayList<AttachmentPoint>();
+        long timeThreshold = System.currentTimeMillis()
+                - AttachmentPoint.INACTIVITY_INTERVAL;
+
+        if (oldAPList == null || apMap == null)
+            return dupAPs;
+
+        for (AttachmentPoint ap : oldAPList) {
+            // XXX - Missing functionality
+            // long id = topology.getL2DomainId(ap.getSw());
+            long id = 0;
+            AttachmentPoint trueAP = apMap.get(id);
+
+            if (trueAP == null)
+                continue;
+            // XXX - Missing functionality
+            // boolean c = (topology.isConsistent(trueAP.getSw(),
+            // trueAP.getPort(),
+            // ap.getSw(), ap.getPort()));
+            boolean c = true;
+            boolean active = (ap.getActiveSince() > trueAP.getActiveSince());
+            boolean last = ap.getLastSeen() > timeThreshold;
+            if (!c && active && last) {
+                dupAPs.add(ap);
+            }
+        }
+
+        return dupAPs;
+    }
+
+    /**
+     * Update the known attachment points. This method is called whenever
+     * topology changes. The method returns true if there's any change to the
+     * list of attachment points -- which indicates a possible device move.
+     *
+     * @return
+     */
+    protected boolean updateAttachmentPoint() {
+        boolean moved = false;
+        this.oldAPs = attachmentPoints;
+        if (attachmentPoints == null || attachmentPoints.isEmpty())
+            return false;
+
+        List<AttachmentPoint> apList = new ArrayList<AttachmentPoint>();
+        if (attachmentPoints != null)
+            apList.addAll(attachmentPoints);
+        Map<Long, AttachmentPoint> newMap = getAPMap(apList);
+        if (newMap == null || newMap.size() != apList.size()) {
+            moved = true;
+        }
+
+        // Prepare the new attachment point list.
+        if (moved) {
+            log.info("updateAttachmentPoint: ap {}  newmap {} ",
+                    attachmentPoints, newMap);
+            List<AttachmentPoint> newAPList = new ArrayList<AttachmentPoint>();
+            if (newMap != null)
+                newAPList.addAll(newMap.values());
+            this.attachmentPoints = newAPList;
+        }
+
+        // Set the oldAPs to null.
+        return moved;
+    }
+
+    /**
+     * Update the list of attachment points given that a new packet-in was seen
+     * from (sw, port) at time (lastSeen). The return value is true if there was
+     * any change to the list of attachment points for the device -- which
+     * indicates a device move.
+     *
+     * @param sw
+     * @param port
+     * @param lastSeen
+     * @return
+     */
+    protected boolean updateAttachmentPoint(NodeConnector port, long lastSeen) {
+        // ITopologyService topology = deviceManager.topology;
+        List<AttachmentPoint> oldAPList;
+        List<AttachmentPoint> apList;
+        boolean oldAPFlag = false;
+
+        if (!deviceManager.isValidAttachmentPoint(port))
+            return false;
+        AttachmentPoint newAP = new AttachmentPoint(port, lastSeen);
+        // Copy the oldAP and ap list.
+        apList = new ArrayList<AttachmentPoint>();
+        if (attachmentPoints != null)
+            apList.addAll(attachmentPoints);
+        oldAPList = new ArrayList<AttachmentPoint>();
+        if (oldAPs != null)
+            oldAPList.addAll(oldAPs);
+
+        // if the sw, port is in old AP, remove it from there
+        // and update the lastSeen in that object.
+        if (oldAPList.contains(newAP)) {
+            int index = oldAPList.indexOf(newAP);
+            newAP = oldAPList.remove(index);
+            newAP.setLastSeen(lastSeen);
+            this.oldAPs = oldAPList;
+            oldAPFlag = true;
+        }
+
+        // newAP now contains the new attachment point.
+
+        // Get the APMap is null or empty.
+        Map<Long, AttachmentPoint> apMap = getAPMap(apList);
+        if (apMap == null || apMap.isEmpty()) {
+            apList.add(newAP);
+            attachmentPoints = apList;
+            // there are no old attachment points - since the device exists,
+            // this
+            // may be because the host really moved (so the old AP port went
+            // down);
+            // or it may be because the switch restarted (so old APs were
+            // nullified).
+            // For now we will treat both cases as host moved.
+            return true;
+        }
+
+        // XXX - Missing functionality
+        // long id = topology.getL2DomainId(sw);
+        long id = 0;
+        AttachmentPoint oldAP = apMap.get(id);
+
+        if (oldAP == null) // No attachment on this L2 domain.
+        {
+            apList = new ArrayList<AttachmentPoint>();
+            apList.addAll(apMap.values());
+            apList.add(newAP);
+            this.attachmentPoints = apList;
+            return true; // new AP found on an L2 island.
+        }
+
+        // There is already a known attachment point on the same L2 island.
+        // we need to compare oldAP and newAP.
+        if (oldAP.equals(newAP)) {
+            // nothing to do here. just the last seen has to be changed.
+            if (newAP.lastSeen > oldAP.lastSeen) {
+                oldAP.setLastSeen(newAP.lastSeen);
+            }
+            this.attachmentPoints = new ArrayList<AttachmentPoint>(
+                    apMap.values());
+            return false; // nothing to do here.
+        }
+
+        int x = deviceManager.apComparator.compare(oldAP, newAP);
+        if (x < 0) {
+            // newAP replaces oldAP.
+            apMap.put(id, newAP);
+            this.attachmentPoints = new ArrayList<AttachmentPoint>(
+                    apMap.values());
+
+            oldAPList = new ArrayList<AttachmentPoint>();
+            if (oldAPs != null)
+                oldAPList.addAll(oldAPs);
+            oldAPList.add(oldAP);
+            this.oldAPs = oldAPList;
+            // XXX - Missing functionality
+            // if (!topology.isInSameBroadcastDomain(oldAP.getSw(),
+            // oldAP.getPort(),
+            // newAP.getSw(), newAP.getPort()))
+            // return true; // attachment point changed.
+            return true;
+        } else if (oldAPFlag) {
+            // retain oldAP as is. Put the newAP in oldAPs for flagging
+            // possible duplicates.
+            oldAPList = new ArrayList<AttachmentPoint>();
+            if (oldAPs != null)
+                oldAPList.addAll(oldAPs);
+            // Add to oldAPList only if it was picked up from the oldAPList
+            oldAPList.add(newAP);
+            this.oldAPs = oldAPList;
+            // XXX - Missing functionality
+            // if (!topology.isInSameBroadcastDomain(oldAP.getSw(),
+            // oldAP.getPort(),
+            // newAP.getSw(), newAP.getPort()))
+            // return true; // attachment point changed.
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Delete (sw,port) from the list of list of attachment points and oldAPs.
+     *
+     * @param sw
+     * @param port
+     * @return
+     */
+    public boolean deleteAttachmentPoint(NodeConnector port) {
+        AttachmentPoint ap = new AttachmentPoint(port, 0);
+
+        if (this.oldAPs != null) {
+            ArrayList<AttachmentPoint> apList = new ArrayList<AttachmentPoint>();
+            apList.addAll(this.oldAPs);
+            int index = apList.indexOf(ap);
+            if (index > 0) {
+                apList.remove(index);
+                this.oldAPs = apList;
+            }
+        }
+
+        if (this.attachmentPoints != null) {
+            ArrayList<AttachmentPoint> apList = new ArrayList<AttachmentPoint>();
+            apList.addAll(this.attachmentPoints);
+            int index = apList.indexOf(ap);
+            if (index > 0) {
+                apList.remove(index);
+                this.attachmentPoints = apList;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // *******
+    // IDevice
+    // *******
+
+    @Override
+    public SwitchPort[] getOldAP() {
+        List<SwitchPort> sp = new ArrayList<SwitchPort>();
+        SwitchPort[] returnSwitchPorts = new SwitchPort[] {};
+        if (oldAPs == null)
+            return returnSwitchPorts;
+        if (oldAPs.isEmpty())
+            return returnSwitchPorts;
+
+        // copy ap list.
+        List<AttachmentPoint> oldAPList;
+        oldAPList = new ArrayList<AttachmentPoint>();
+
+        if (oldAPs != null)
+            oldAPList.addAll(oldAPs);
+        removeExpiredAttachmentPoints(oldAPList);
+
+        if (oldAPList != null) {
+            for (AttachmentPoint ap : oldAPList) {
+                SwitchPort swport = new SwitchPort(ap.getPort());
+                sp.add(swport);
+            }
+        }
+        return sp.toArray(new SwitchPort[sp.size()]);
+    }
+
+    @Override
+    public SwitchPort[] getAttachmentPoints() {
+        return getAttachmentPoints(false);
+    }
+
+    @Override
+    public SwitchPort[] getAttachmentPoints(boolean includeError) {
+        List<SwitchPort> sp = new ArrayList<SwitchPort>();
+        SwitchPort[] returnSwitchPorts = new SwitchPort[] {};
+        if (attachmentPoints == null)
+            return returnSwitchPorts;
+        if (attachmentPoints.isEmpty())
+            return returnSwitchPorts;
+
+        // copy ap list.
+        List<AttachmentPoint> apList = attachmentPoints;
+
+        if (apList != null) {
+            for (AttachmentPoint ap : apList) {
+                SwitchPort swport = new SwitchPort(ap.getPort());
+                sp.add(swport);
+            }
+        }
+
+        if (!includeError)
+            return sp.toArray(new SwitchPort[sp.size()]);
+
+        List<AttachmentPoint> oldAPList;
+        oldAPList = new ArrayList<AttachmentPoint>();
+
+        if (oldAPs != null)
+            oldAPList.addAll(oldAPs);
+
+        if (removeExpiredAttachmentPoints(oldAPList))
+            this.oldAPs = oldAPList;
+
+        List<AttachmentPoint> dupList;
+        // get AP map.
+        Map<Long, AttachmentPoint> apMap = getAPMap(apList);
+        dupList = this.getDuplicateAttachmentPoints(oldAPList, apMap);
+        if (dupList != null) {
+            for (AttachmentPoint ap : dupList) {
+                SwitchPort swport = new SwitchPort(ap.getPort(),
+                        ErrorStatus.DUPLICATE_DEVICE);
+                sp.add(swport);
+            }
+        }
+        return sp.toArray(new SwitchPort[sp.size()]);
+    }
+
+    @Override
+    public Long getDeviceKey() {
+        return deviceKey;
+    }
+
+    @Override
+    public long getMACAddress() {
+        // we assume only one MAC per device for now.
+        return entities[0].getMacAddress();
+    }
+
+    @Override
+    public String getMACAddressString() {
+        return macAddressString;
+    }
+
+    @Override
+    public Short[] getVlanId() {
+        return Arrays.copyOf(vlanIds, vlanIds.length);
+    }
+
+    static final EnumSet<DeviceField> ipv4Fields = EnumSet.of(DeviceField.IPV4);
+
+    @Override
+    public Integer[] getIPv4Addresses() {
+        // XXX - TODO we can cache this result. Let's find out if this
+        // is really a performance bottleneck first though.
+
+        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;
+            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());
+        }
+
+        return vals.toArray(new Integer[vals.size()]);
+    }
+
+    @Override
+    public Short[] getSwitchPortVlanIds(SwitchPort swp) {
+        TreeSet<Short> vals = new TreeSet<Short>();
+        for (Entity e : entities) {
+            if (e.getPort().equals(swp.getPort())) {
+                if (e.getVlan() == null)
+                    vals.add(VLAN_UNTAGGED);
+                else
+                    vals.add(e.getVlan());
+            }
+        }
+        return vals.toArray(new Short[vals.size()]);
+    }
+
+    @Override
+    public Date getLastSeen() {
+        Date d = null;
+        for (int i = 0; i < entities.length; i++) {
+            if (d == null
+                    || entities[i].getLastSeenTimestamp().compareTo(d) > 0)
+                d = entities[i].getLastSeenTimestamp();
+        }
+        return d;
+    }
+
+    // ***************
+    // Getters/Setters
+    // ***************
+
+    @Override
+    public IEntityClass getEntityClass() {
+        return entityClass;
+    }
+
+    public Entity[] getEntities() {
+        return entities;
+    }
+
+    public String getDHCPClientName() {
+        return dhcpClientName;
+    }
+
+    // ***************
+    // Utility Methods
+    // ***************
+
+    /**
+     * Check whether the device contains the specified entity
+     *
+     * @param entity
+     *            the entity to search for
+     * @return the index of the entity, or <0 if not found
+     */
+    protected int entityIndex(Entity entity) {
+        return Arrays.binarySearch(entities, entity);
+    }
+
+    // ******
+    // Object
+    // ******
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + Arrays.hashCode(entities);
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        Device other = (Device) obj;
+        if (!deviceKey.equals(other.deviceKey))
+            return false;
+        if (!Arrays.equals(entities, other.entities))
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("Device [deviceKey=");
+        builder.append(deviceKey);
+        builder.append(", entityClass=");
+        builder.append(entityClass.getName());
+        builder.append(", MAC=");
+        builder.append(macAddressString);
+        builder.append(", IPs=[");
+        boolean isFirst = true;
+        for (Integer ip : getIPv4Addresses()) {
+            if (!isFirst)
+                builder.append(", ");
+            isFirst = false;
+            // builder.append(IPv4.fromIPv4Address(ip));
+            builder.append(ip);
+        }
+        builder.append("], APs=");
+        builder.append(Arrays.toString(getAttachmentPoints(true)));
+        builder.append("]");
+        return builder.toString();
+    }
+}
diff --git a/opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/DeviceIndex.java b/opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/DeviceIndex.java
new file mode 100644 (file)
index 0000000..5f06806
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2012 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * 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.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the
+ *    License. You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an "AS
+ *    IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ *    express or implied. See the License for the specific language
+ *    governing permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.hosttracker.internal;
+
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.Iterator;
+
+import org.opendaylight.controller.hosttracker.Entity;
+import org.opendaylight.controller.hosttracker.IDeviceService.DeviceField;
+
+/**
+ * An index that maps key fields of an entity to device keys
+ */
+public abstract class DeviceIndex {
+    /**
+     * The key fields for this index
+     */
+    protected EnumSet<DeviceField> keyFields;
+
+    /**
+     * Construct a new device index using the provided key fields
+     *
+     * @param keyFields
+     *            the key fields to use
+     */
+    public DeviceIndex(EnumSet<DeviceField> keyFields) {
+        super();
+        this.keyFields = keyFields;
+    }
+
+    /**
+     * Find all device keys in the index that match the given entity on all the
+     * key fields for this index
+     *
+     * @param e
+     *            the entity to search for
+     * @return an iterator over device keys
+     */
+    public abstract Iterator<Long> queryByEntity(Entity entity);
+
+    /**
+     * Get all device keys in the index. If certain devices exist multiple
+     * times, then these devices may be returned multiple times
+     *
+     * @return an iterator over device keys
+     */
+    public abstract Iterator<Long> getAll();
+
+    /**
+     * Attempt to update an index with the entities in the provided
+     * {@link Device}. If the update fails because of a 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.
+     */
+    public abstract boolean updateIndex(Device device, Long deviceKey);
+
+    /**
+     * Add a mapping from the given entity to the given device key. This update
+     * will not fail because of a concurrent update
+     *
+     * @param device
+     *            the device to update
+     * @param deviceKey
+     *            the device key for the device
+     */
+    public abstract void updateIndex(Entity entity, Long deviceKey);
+
+    /**
+     * Remove the entry for the given entity
+     *
+     * @param entity
+     *            the entity to remove
+     */
+    public abstract void removeEntity(Entity entity);
+
+    /**
+     * Remove the given device key from the index for the given entity
+     *
+     * @param entity
+     *            the entity to search for
+     * @param deviceKey
+     *            the key to remove
+     */
+    public abstract void removeEntity(Entity entity, Long deviceKey);
+
+    /**
+     * Remove the give device from the index only if this the collection of
+     * others does not contain an entity that is identical on all the key fields
+     * for this index.
+     *
+     * @param entity
+     *            the entity to search for
+     * @param deviceKey
+     *            the key to remove
+     * @param others
+     *            the others against which to check
+     */
+    public void removeEntityIfNeeded(Entity entity, Long deviceKey,
+            Collection<Entity> others) {
+        IndexedEntity ie = new IndexedEntity(keyFields, entity);
+        for (Entity o : others) {
+            IndexedEntity oio = new IndexedEntity(keyFields, o);
+            if (oio.equals(ie))
+                return;
+        }
+
+        Iterator<Long> keyiter = this.queryByEntity(entity);
+        while (keyiter.hasNext()) {
+            Long key = keyiter.next();
+            if (key.equals(deviceKey)) {
+                removeEntity(entity, deviceKey);
+                break;
+            }
+        }
+    }
+
+}
diff --git a/opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/DeviceIndexInterator.java b/opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/DeviceIndexInterator.java
new file mode 100644 (file)
index 0000000..469bd61
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2012 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * 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.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the
+ *    License. You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an "AS
+ *    IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ *    express or implied. See the License for the specific language
+ *    governing permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.hosttracker.internal;
+
+import java.util.Iterator;
+
+/**
+ * An iterator for handling device index queries
+ */
+public class DeviceIndexInterator implements Iterator<Device> {
+    private DeviceManagerImpl deviceManager;
+    private Iterator<Long> subIterator;
+
+    /**
+     * Construct a new device index iterator referring to a device manager
+     * instance and an iterator over device keys
+     *
+     * @param deviceManager
+     *            the device manager
+     * @param subIterator
+     *            an iterator over device keys
+     */
+    public DeviceIndexInterator(DeviceManagerImpl deviceManager,
+            Iterator<Long> subIterator) {
+        super();
+        this.deviceManager = deviceManager;
+        this.subIterator = subIterator;
+    }
+
+    @Override
+    public boolean hasNext() {
+        return subIterator.hasNext();
+    }
+
+    @Override
+    public Device next() {
+        Long next = subIterator.next();
+        return deviceManager.deviceMap.get(next);
+    }
+
+    @Override
+    public void remove() {
+        subIterator.remove();
+    }
+
+}
diff --git a/opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/DeviceIterator.java b/opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/DeviceIterator.java
new file mode 100644 (file)
index 0000000..68f175c
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2012 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * 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.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the
+ *    License. You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an "AS
+ *    IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ *    express or implied. See the License for the specific language
+ *    governing permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.hosttracker.internal;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+import org.opendaylight.controller.hosttracker.IEntityClass;
+import org.opendaylight.controller.hosttracker.SwitchPort;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.utils.FilterIterator;
+
+/**
+ * An iterator for handling device queries
+ */
+public class DeviceIterator extends FilterIterator<Device> {
+    private IEntityClass[] entityClasses;
+
+    private Long macAddress;
+    private Short vlan;
+    private Integer ipv4Address;
+    private NodeConnector port;
+
+    /**
+     * Construct a new device iterator over the key fields
+     *
+     * @param subIterator
+     *            an iterator over the full data structure to scan
+     * @param entityClasses
+     *            the entity classes to search for
+     * @param macAddress
+     *            The MAC address
+     * @param vlan
+     *            the VLAN
+     * @param ipv4Address
+     *            the ipv4 address
+     * @param switchDPID
+     *            the switch DPID
+     * @param switchPort
+     *            the switch port
+     */
+    public DeviceIterator(Iterator<Device> subIterator,
+            IEntityClass[] entityClasses, Long macAddress, Short vlan,
+            Integer ipv4Address, NodeConnector port) {
+        super(subIterator);
+        this.entityClasses = entityClasses;
+        this.subIterator = subIterator;
+        this.macAddress = macAddress;
+        this.vlan = vlan;
+        this.ipv4Address = ipv4Address;
+        this.port = port;
+    }
+
+    @Override
+    protected boolean matches(Device value) {
+        boolean match;
+        if (entityClasses != null) {
+            IEntityClass clazz = value.getEntityClass();
+            if (clazz == null)
+                return false;
+
+            match = false;
+            for (IEntityClass entityClass : entityClasses) {
+                if (clazz.equals(entityClass)) {
+                    match = true;
+                    break;
+                }
+            }
+            if (!match)
+                return false;
+        }
+        if (macAddress != null) {
+            if (macAddress.longValue() != value.getMACAddress())
+                return false;
+        }
+        if (vlan != null) {
+            Short[] vlans = value.getVlanId();
+            if (Arrays.binarySearch(vlans, vlan) < 0)
+                return false;
+        }
+        if (ipv4Address != null) {
+            Integer[] ipv4Addresses = value.getIPv4Addresses();
+            if (Arrays.binarySearch(ipv4Addresses, ipv4Address) < 0)
+                return false;
+        }
+        if (port != null) {
+            SwitchPort[] sps = value.getAttachmentPoints();
+            if (sps == null)
+                return false;
+
+            match = false;
+            for (SwitchPort sp : sps) {
+                if (sp.getPort().equals(sp.getPort())) {
+                    match = true;
+                    break;
+                }
+            }
+            if (!match)
+                return false;
+        }
+        return true;
+    }
+}
diff --git a/opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/DeviceManagerImpl.java b/opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/DeviceManagerImpl.java
new file mode 100755 (executable)
index 0000000..8435a94
--- /dev/null
@@ -0,0 +1,2292 @@
+/*
+ * Copyright (c) 2011,2012 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * 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.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the
+ *    License. You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an "AS
+ *    IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ *    express or implied. See the License for the specific language
+ *    governing permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.hosttracker.internal;
+
+import static org.opendaylight.controller.hosttracker.internal.DeviceManagerImpl.DeviceUpdate.Change.ADD;
+import static org.opendaylight.controller.hosttracker.internal.DeviceManagerImpl.DeviceUpdate.Change.CHANGE;
+import static org.opendaylight.controller.hosttracker.internal.DeviceManagerImpl.DeviceUpdate.Change.DELETE;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.opendaylight.controller.hosttracker.Entity;
+import org.opendaylight.controller.hosttracker.IDevice;
+import org.opendaylight.controller.hosttracker.IDeviceListener;
+import org.opendaylight.controller.hosttracker.IDeviceService;
+import org.opendaylight.controller.hosttracker.IEntityClass;
+import org.opendaylight.controller.hosttracker.IEntityClassListener;
+import org.opendaylight.controller.hosttracker.IEntityClassifierService;
+import org.opendaylight.controller.hosttracker.SwitchPort;
+import org.opendaylight.controller.sal.core.Edge;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.NodeConnector.NodeConnectorIDType;
+import org.opendaylight.controller.sal.packet.ARP;
+import org.opendaylight.controller.sal.packet.Ethernet;
+import org.opendaylight.controller.sal.packet.IDataPacketService;
+import org.opendaylight.controller.sal.packet.IListenDataPacket;
+import org.opendaylight.controller.sal.packet.Packet;
+import org.opendaylight.controller.sal.packet.PacketResult;
+import org.opendaylight.controller.sal.packet.RawPacket;
+import org.opendaylight.controller.sal.topology.TopoEdgeUpdate;
+import org.opendaylight.controller.sal.utils.ListenerDispatcher;
+import org.opendaylight.controller.sal.utils.MultiIterator;
+import org.opendaylight.controller.sal.utils.SingletonTask;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+import org.opendaylight.controller.topologymanager.ITopologyManager;
+import org.opendaylight.controller.topologymanager.ITopologyManagerAware;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * DeviceManager creates Devices based upon MAC addresses seen in the network.
+ * It tracks any network addresses mapped to the Device, and its location within
+ * the network.
+ *
+ * @author readams
+ */
+public class DeviceManagerImpl implements IDeviceService, IEntityClassListener,
+        IListenDataPacket, ITopologyManagerAware {
+    protected static Logger logger = LoggerFactory
+            .getLogger(DeviceManagerImpl.class);
+
+    public static final String MODULE_NAME = "devicemanager";
+
+    // protected ITopologyService topology;
+    // protected IStorageSourceService storageSource;
+    // protected IRestApiService restApi;
+    // protected IThreadPoolService threadPool;
+    // protected IFlowReconcileService flowReconcileMgr;
+    // protected IFlowReconcileEngineService flowReconcileEngine;
+    // protected IDebugCounterService debugCounters;
+    // private ISyncService syncService;
+    // private IStoreClient<String,DeviceSyncRepresentation> storeClient;
+    // private DeviceSyncManager deviceSyncManager;
+
+    private ITopologyManager topology;
+    private ISwitchManager switchManager = null;
+    private IDataPacketService dataPacketService = null;
+
+    public static final String CNT_INCOMING = MODULE_NAME + "-incoming";
+    public static final String CNT_RECONCILE_REQUEST = MODULE_NAME
+            + "-reconcileRequest";
+    public static final String CNT_RECONCILE_NO_SOURCE = MODULE_NAME
+            + "-reconcileNoSourceDevice";
+    public static final String CNT_RECONCILE_NO_DEST = MODULE_NAME
+            + "-reconcileNoDestDevice";
+    public static final String CNT_BROADCAST_SOURCE = MODULE_NAME
+            + "-broadcastSource";
+    public static final String CNT_NO_SOURCE = MODULE_NAME + "-noSourceDevice";
+    public static final String CNT_NO_DEST = MODULE_NAME + "-noDestDevice";
+    public static final String CNT_DHCP_CLIENT_NAME_SNOOPED = MODULE_NAME
+            + "-dhcpClientNameSnooped";
+    public static final String CNT_DEVICE_ON_INTERAL_PORT_NOT_LEARNED = MODULE_NAME
+            + "-deviceOnInternalPortNotLearned";
+    public static final String CNT_PACKET_NOT_ALLOWED = MODULE_NAME
+            + "-packetNotAllowed";
+    public static final String CNT_NEW_DEVICE = MODULE_NAME + "-newDevice";
+    public static final String CNT_PACKET_ON_INTERNAL_PORT_FOR_KNOWN_DEVICE = MODULE_NAME
+            + "-packetOnInternalPortForKnownDevice";
+    public static final String CNT_NEW_ENTITY = MODULE_NAME + "-newEntity";
+    public static final String CNT_DEVICE_CHANGED = MODULE_NAME
+            + "-deviceChanged";
+    public static final String CNT_DEVICE_MOVED = MODULE_NAME + "-deviceMoved";
+    public static final String CNT_CLEANUP_ENTITIES_RUNS = MODULE_NAME
+            + "-cleanupEntitiesRuns";
+    public static final String CNT_ENTITY_REMOVED_TIMEOUT = MODULE_NAME
+            + "-entityRemovedTimeout";
+    public static final String CNT_DEVICE_DELETED = MODULE_NAME
+            + "-deviceDeleted";
+    public static final String CNT_DEVICE_RECLASSIFY_DELETE = MODULE_NAME
+            + "-deviceReclassifyDelete";
+    public static final String CNT_DEVICE_STORED = MODULE_NAME
+            + "-deviceStored";
+    public static final String CNT_DEVICE_STORE_THROTTLED = MODULE_NAME
+            + "-deviceStoreThrottled";
+    public static final String CNT_DEVICE_REMOVED_FROM_STORE = MODULE_NAME
+            + "-deviceRemovedFromStore";
+    public static final String CNT_SYNC_EXCEPTION = MODULE_NAME
+            + "-syncException";
+    public static final String CNT_DEVICES_FROM_STORE = MODULE_NAME
+            + "-devicesFromStore";
+    public static final String CNT_CONSOLIDATE_STORE_RUNS = MODULE_NAME
+            + "-consolidateStoreRuns";
+    public static final String CNT_CONSOLIDATE_STORE_DEVICES_REMOVED = MODULE_NAME
+            + "-consolidateStoreDevicesRemoved";
+
+    static final String DEVICE_SYNC_STORE_NAME = DeviceManagerImpl.class
+            .getCanonicalName() + ".stateStore";
+
+    /**
+     * Time interval between writes of entries for the same device to the sync
+     * store.
+     */
+    // static final int DEFAULT_SYNC_STORE_WRITE_INTERVAL_MS =
+    // 5*60*1000; // 5 min
+    // private int syncStoreWriteIntervalMs =
+    // DEFAULT_SYNC_STORE_WRITE_INTERVAL_MS;
+
+    /**
+     * Time after SLAVE->MASTER until we run the consolidate store code.
+     */
+    // static final int DEFAULT_INITIAL_SYNC_STORE_CONSOLIDATE_MS =
+    // 15*1000; // 15 sec
+    // private int initialSyncStoreConsolidateMs =
+    // DEFAULT_INITIAL_SYNC_STORE_CONSOLIDATE_MS;
+
+    /**
+     * Time interval between consolidate store runs.
+     */
+    // static final int DEFAULT_SYNC_STORE_CONSOLIDATE_INTERVAL_MS =
+    // 75*60*1000; // 75 min
+    // private final int syncStoreConsolidateIntervalMs =
+    // DEFAULT_SYNC_STORE_CONSOLIDATE_INTERVAL_MS;
+
+    /**
+     * Time in milliseconds before entities will expire
+     */
+    protected static final int ENTITY_TIMEOUT = 60 * 60 * 1000;
+
+    /**
+     * Time in seconds between cleaning up old entities/devices
+     */
+    protected static final int ENTITY_CLEANUP_INTERVAL = 60 * 60;
+
+    /**
+     * This is the master device map that maps device IDs to {@link Device}
+     * objects.
+     */
+    protected ConcurrentHashMap<Long, Device> deviceMap;
+
+    /**
+     * Counter used to generate device keys
+     */
+    protected long deviceKeyCounter = 0;
+
+    /**
+     * Lock for incrementing the device key counter
+     */
+    protected Object deviceKeyLock = new Object();
+
+    /**
+     * This is the primary entity index that contains all entities
+     */
+    protected DeviceUniqueIndex primaryIndex;
+
+    /**
+     * This stores secondary indices over the fields in the devices
+     */
+    protected Map<EnumSet<DeviceField>, DeviceIndex> secondaryIndexMap;
+
+    /**
+     * This map contains state for each of the {@ref IEntityClass} that exist
+     */
+    protected ConcurrentHashMap<String, ClassState> classStateMap;
+
+    /**
+     * This is the list of indices we want on a per-class basis
+     */
+    protected Set<EnumSet<DeviceField>> perClassIndices;
+
+    /**
+     * The entity classifier currently in use
+     */
+    protected IEntityClassifierService entityClassifier;
+
+    /**
+     * Used to cache state about specific entity classes
+     */
+    protected class ClassState {
+
+        /**
+         * The class index
+         */
+        protected DeviceUniqueIndex classIndex;
+
+        /**
+         * This stores secondary indices over the fields in the device for the
+         * class
+         */
+        protected Map<EnumSet<DeviceField>, DeviceIndex> secondaryIndexMap;
+
+        /**
+         * Allocate a new {@link ClassState} object for the class
+         *
+         * @param clazz
+         *            the class to use for the state
+         */
+        public ClassState(IEntityClass clazz) {
+            EnumSet<DeviceField> keyFields = clazz.getKeyFields();
+            EnumSet<DeviceField> primaryKeyFields = entityClassifier
+                    .getKeyFields();
+            boolean keyFieldsMatchPrimary = primaryKeyFields.equals(keyFields);
+
+            if (!keyFieldsMatchPrimary)
+                classIndex = new DeviceUniqueIndex(keyFields);
+
+            secondaryIndexMap = new HashMap<EnumSet<DeviceField>, DeviceIndex>();
+            for (EnumSet<DeviceField> fields : perClassIndices) {
+                secondaryIndexMap.put(fields, new DeviceMultiIndex(fields));
+            }
+        }
+    }
+
+    /**
+     * Device manager event listeners reclassifyDeviceListeners are notified
+     * first before reconcileDeviceListeners. This is to make sure devices are
+     * correctly reclassified before reconciliation.
+     */
+    protected ListenerDispatcher<String, IDeviceListener> deviceListeners;
+
+    /**
+     * A device update event to be dispatched
+     */
+    protected static class DeviceUpdate {
+        public enum Change {
+            ADD, DELETE, CHANGE;
+        }
+
+        /**
+         * The affected device
+         */
+        protected Device device;
+
+        /**
+         * The change that was made
+         */
+        protected Change change;
+
+        /**
+         * If not added, then this is the list of fields changed
+         */
+        protected EnumSet<DeviceField> fieldsChanged;
+
+        public DeviceUpdate(Device device, Change change,
+                EnumSet<DeviceField> fieldsChanged) {
+            super();
+            this.device = device;
+            this.change = change;
+            this.fieldsChanged = fieldsChanged;
+        }
+
+        @Override
+        public String toString() {
+            String devIdStr = device.getEntityClass().getName() + "::"
+                    + device.getMACAddressString();
+            return "DeviceUpdate [device=" + devIdStr + ", change=" + change
+                    + ", fieldsChanged=" + fieldsChanged + "]";
+        }
+
+    }
+
+    /**
+     * AttachmentPointComparator
+     *
+     * Compares two attachment points and returns the latest one. It is assumed
+     * that the two attachment points are in the same L2 domain.
+     *
+     * @author srini
+     */
+    protected class AttachmentPointComparator implements
+            Comparator<AttachmentPoint> {
+        public AttachmentPointComparator() {
+            super();
+        }
+
+        @Override
+        public int compare(AttachmentPoint oldAP, AttachmentPoint newAP) {
+            // First compare based on L2 domain ID;
+
+            // XXX - missing functionality -- need topology
+            // long oldDomain = topology.getL2DomainId(oldSw);
+            // boolean oldBD = topology.isBroadcastDomainPort(oldSw, oldPort);
+            long oldDomain = 0;
+            boolean oldBD = false;
+
+            // XXX - missing functionality -- need topology
+            // long newDomain = topology.getL2DomainId(newSw);
+            // boolean newBD = topology.isBroadcastDomainPort(newSw, newPort);
+            long newDomain = 0;
+            boolean newBD = false;
+
+            if (oldDomain < newDomain)
+                return -1;
+            else if (oldDomain > newDomain)
+                return 1;
+
+            // Give preference to OFPP_LOCAL always
+            if (!oldAP.getPort().getType().equals(NodeConnectorIDType.SWSTACK)
+                    && newAP.getPort().getType()
+                            .equals(NodeConnectorIDType.SWSTACK)) {
+                return -1;
+            } else if (oldAP.getPort().getType()
+                    .equals(NodeConnectorIDType.SWSTACK)
+                    && !newAP.getPort().getType()
+                            .equals(NodeConnectorIDType.SWSTACK)) {
+                return 1;
+            }
+
+            // We expect that the last seen of the new AP is higher than
+            // old AP, if it is not, just reverse and send the negative
+            // of the result.
+            if (oldAP.getActiveSince() > newAP.getActiveSince())
+                return -compare(newAP, oldAP);
+
+            long activeOffset = 0;
+            // XXX - missing functionality -- need topology
+            // if (!topology.isConsistent(oldSw, oldPort, newSw, newPort)) {
+            if (!newBD && oldBD) {
+                return -1;
+            }
+            if (newBD && oldBD) {
+                activeOffset = AttachmentPoint.EXTERNAL_TO_EXTERNAL_TIMEOUT;
+            } else if (newBD && !oldBD) {
+                activeOffset = AttachmentPoint.OPENFLOW_TO_EXTERNAL_TIMEOUT;
+            }
+
+            // } else {
+            // // The attachment point is consistent.
+            // activeOffset = AttachmentPoint.CONSISTENT_TIMEOUT;
+            // }
+
+            if ((newAP.getActiveSince() > oldAP.getLastSeen() + activeOffset)
+                    || (newAP.getLastSeen() > oldAP.getLastSeen()
+                            + AttachmentPoint.INACTIVITY_INTERVAL)) {
+                return -1;
+            }
+            return 1;
+        }
+    }
+
+    /**
+     * Comparator for sorting by cluster ID
+     */
+    public AttachmentPointComparator apComparator;
+
+    /**
+     * Switch ports where attachment points shouldn't be learned
+     */
+    private Set<SwitchPort> suppressAPs;
+
+    /**
+     * Periodic task to clean up expired entities
+     */
+    public SingletonTask entityCleanupTask;
+
+    // ********************
+    // Dependency injection
+    // ********************
+
+    void setDataPacketService(IDataPacketService s) {
+        this.dataPacketService = s;
+    }
+
+    void unsetDataPacketService(IDataPacketService s) {
+        if (this.dataPacketService == s) {
+            this.dataPacketService = null;
+        }
+    }
+
+    public void setTopologyManager(ITopologyManager s) {
+        this.topology = s;
+    }
+
+    public void unsetTopologyManager(ITopologyManager s) {
+        if (this.topology == s) {
+            logger.debug("Topology Manager Service removed!");
+            this.topology = null;
+        }
+    }
+
+    private volatile boolean stopped = true;
+    private ScheduledExecutorService ses;
+
+    public void stop() {
+        stopped = true;
+        if (ses != null)
+            ses.shutdownNow();
+    }
+
+    public void start() {
+        this.perClassIndices = new HashSet<EnumSet<DeviceField>>();
+
+        // XXX - TODO need to make it possible to register a non-default
+        // classifier
+        entityClassifier = new DefaultEntityClassifier();
+        this.deviceListeners = new ListenerDispatcher<String, IDeviceListener>();
+        this.suppressAPs = Collections
+                .newSetFromMap(new ConcurrentHashMap<SwitchPort, Boolean>());
+        primaryIndex = new DeviceUniqueIndex(entityClassifier.getKeyFields());
+        secondaryIndexMap = new HashMap<EnumSet<DeviceField>, DeviceIndex>();
+
+        deviceMap = new ConcurrentHashMap<Long, Device>();
+        classStateMap = new ConcurrentHashMap<String, ClassState>();
+        apComparator = new AttachmentPointComparator();
+
+        addIndex(true, EnumSet.of(DeviceField.IPV4));
+
+        // floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
+        // floodlightProvider.addHAListener(this.haListenerDelegate);
+        // if (topology != null)
+        // topology.addListener(this);
+        // flowReconcileMgr.addFlowReconcileListener(this);
+        // entityClassifier.addListener(this);
+
+        stopped = false;
+        // XXX - Should use a common threadpool but this doesn't currently exist
+        ses = Executors.newScheduledThreadPool(1);
+        Runnable ecr = new Runnable() {
+            @Override
+            public void run() {
+                cleanupEntities();
+                if (!stopped)
+                    entityCleanupTask.reschedule(ENTITY_CLEANUP_INTERVAL,
+                            TimeUnit.SECONDS);
+            }
+        };
+        entityCleanupTask = new SingletonTask(ses, ecr);
+        entityCleanupTask.reschedule(ENTITY_CLEANUP_INTERVAL, TimeUnit.SECONDS);
+
+        /*
+         * XXX Missing functionality if (restApi != null) {
+         * restApi.addRestletRoutable(new DeviceRoutable()); } else {
+         * logger.debug("Could not instantiate REST API"); }
+         */
+
+        registerDeviceManagerDebugCounters();
+
+        /*
+         * XXX Missing functionality try {
+         * this.syncService.registerStore(DEVICE_SYNC_STORE_NAME, Scope.LOCAL);
+         * this.storeClient = this.syncService
+         * .getStoreClient(DEVICE_SYNC_STORE_NAME, String.class,
+         * DeviceSyncRepresentation.class); } catch (SyncException e) { throw
+         * new FloodlightModuleException("Error while setting up sync service",
+         * e); }
+         *
+         * Runnable consolidateStoreRunner = new Runnable() {
+         *
+         * @Override public void run() { deviceSyncManager.consolidateStore();
+         * storeConsolidateTask.reschedule(syncStoreConsolidateIntervalMs,
+         * TimeUnit.MILLISECONDS); debugCounters.flushCounters(); } };
+         * storeConsolidateTask = new SingletonTask(ses,
+         * consolidateStoreRunner); if (isMaster)
+         * storeConsolidateTask.reschedule(syncStoreConsolidateIntervalMs,
+         * TimeUnit.MILLISECONDS);
+         */
+    }
+
+    /**
+     * Periodic task to consolidate entries in the store. I.e., delete entries
+     * in the store that are not known to DeviceManager
+     */
+    // XXX - Missing functionality
+    // private SingletonTask storeConsolidateTask;
+
+    // *********************
+    // IDeviceManagerService
+    // *********************
+
+    @Override
+    public IDevice getDevice(Long deviceKey) {
+        return deviceMap.get(deviceKey);
+    }
+
+    @Override
+    public IDevice findDevice(long macAddress, Short vlan, Integer ipv4Address,
+            NodeConnector port) throws IllegalArgumentException {
+        if (vlan != null && vlan.shortValue() <= 0)
+            vlan = null;
+        if (ipv4Address != null && ipv4Address == 0)
+            ipv4Address = null;
+        Entity e = new Entity(macAddress, vlan, ipv4Address, port, null);
+        if (!allKeyFieldsPresent(e, entityClassifier.getKeyFields())) {
+            throw new IllegalArgumentException("Not all key fields specified."
+                    + " Required fields: " + entityClassifier.getKeyFields());
+        }
+        return findDeviceByEntity(e);
+    }
+
+    @Override
+    public IDevice findClassDevice(IEntityClass entityClass, long macAddress,
+            Short vlan, Integer ipv4Address) throws IllegalArgumentException {
+        if (vlan != null && vlan.shortValue() <= 0)
+            vlan = null;
+        if (ipv4Address != null && ipv4Address == 0)
+            ipv4Address = null;
+        Entity e = new Entity(macAddress, vlan, ipv4Address, null, null);
+        if (entityClass == null
+                || !allKeyFieldsPresent(e, entityClass.getKeyFields())) {
+            throw new IllegalArgumentException("Not all key fields and/or "
+                    + " no source device specified. Required fields: "
+                    + entityClassifier.getKeyFields());
+        }
+        return findDestByEntity(entityClass, e);
+    }
+
+    @Override
+    public Collection<? extends IDevice> getAllDevices() {
+        return Collections.unmodifiableCollection(deviceMap.values());
+    }
+
+    @Override
+    public void addIndex(boolean perClass, EnumSet<DeviceField> keyFields) {
+        if (perClass) {
+            perClassIndices.add(keyFields);
+        } else {
+            secondaryIndexMap.put(keyFields, new DeviceMultiIndex(keyFields));
+        }
+    }
+
+    @Override
+    public Iterator<? extends IDevice> queryDevices(Long macAddress,
+            Short vlan, Integer ipv4Address, NodeConnector port) {
+        DeviceIndex index = null;
+        if (secondaryIndexMap.size() > 0) {
+            EnumSet<DeviceField> keys = getEntityKeys(macAddress, vlan,
+                    ipv4Address, port);
+            index = secondaryIndexMap.get(keys);
+        }
+
+        Iterator<Device> deviceIterator = null;
+        if (index == null) {
+            // Do a full table scan
+            deviceIterator = deviceMap.values().iterator();
+        } else {
+            // index lookup
+            Entity entity = new Entity((macAddress == null ? 0 : macAddress),
+                    vlan, ipv4Address, port, null);
+            deviceIterator = new DeviceIndexInterator(this,
+                    index.queryByEntity(entity));
+        }
+
+        DeviceIterator di = new DeviceIterator(deviceIterator, null,
+                macAddress, vlan, ipv4Address, port);
+        return di;
+    }
+
+    @Override
+    public Iterator<? extends IDevice> queryClassDevices(
+            IEntityClass entityClass, Long macAddress, Short vlan,
+            Integer ipv4Address, NodeConnector port) {
+        ArrayList<Iterator<Device>> iterators = new ArrayList<Iterator<Device>>();
+        ClassState classState = getClassState(entityClass);
+
+        DeviceIndex index = null;
+        if (classState.secondaryIndexMap.size() > 0) {
+            EnumSet<DeviceField> keys = getEntityKeys(macAddress, vlan,
+                    ipv4Address, port);
+            index = classState.secondaryIndexMap.get(keys);
+        }
+
+        Iterator<Device> iter;
+        if (index == null) {
+            index = classState.classIndex;
+            if (index == null) {
+                // scan all devices
+                return new DeviceIterator(deviceMap.values().iterator(),
+                        new IEntityClass[] { entityClass }, macAddress, vlan,
+                        ipv4Address, port);
+            } else {
+                // scan the entire class
+                iter = new DeviceIndexInterator(this, index.getAll());
+            }
+        } else {
+            // index lookup
+            Entity entity = new Entity((macAddress == null ? 0 : macAddress),
+                    vlan, ipv4Address, port, null);
+            iter = new DeviceIndexInterator(this, index.queryByEntity(entity));
+        }
+        iterators.add(iter);
+
+        return new MultiIterator<Device>(iterators.iterator());
+    }
+
+    protected Iterator<Device> getDeviceIteratorForQuery(Long macAddress,
+            Short vlan, Integer ipv4Address, NodeConnector port) {
+        DeviceIndex index = null;
+        if (secondaryIndexMap.size() > 0) {
+            EnumSet<DeviceField> keys = getEntityKeys(macAddress, vlan,
+                    ipv4Address, port);
+            index = secondaryIndexMap.get(keys);
+        }
+
+        Iterator<Device> deviceIterator = null;
+        if (index == null) {
+            // Do a full table scan
+            deviceIterator = deviceMap.values().iterator();
+        } else {
+            // index lookup
+            Entity entity = new Entity((macAddress == null ? 0 : macAddress),
+                    vlan, ipv4Address, port, null);
+            deviceIterator = new DeviceIndexInterator(this,
+                    index.queryByEntity(entity));
+        }
+
+        DeviceIterator di = new DeviceIterator(deviceIterator, null,
+                macAddress, vlan, ipv4Address, port);
+        return di;
+    }
+
+    @Override
+    public void addListener(IDeviceListener listener) {
+        deviceListeners.addListener("device", listener);
+        logListeners();
+    }
+
+    @Override
+    public void addSuppressAPs(NodeConnector port) {
+        suppressAPs.add(new SwitchPort(port));
+    }
+
+    @Override
+    public void removeSuppressAPs(NodeConnector port) {
+        suppressAPs.remove(new SwitchPort(port));
+    }
+
+    @Override
+    public Set<SwitchPort> getSuppressAPs() {
+        return Collections.unmodifiableSet(suppressAPs);
+    }
+
+    private void logListeners() {
+        List<IDeviceListener> listeners = deviceListeners.getOrderedListeners();
+        if (listeners != null) {
+            StringBuffer sb = new StringBuffer();
+            sb.append("DeviceListeners: ");
+            for (IDeviceListener l : listeners) {
+                sb.append(l.getName());
+                sb.append(",");
+            }
+            logger.debug(sb.toString());
+        }
+    }
+
+    // ***************
+    // IFlowReconcileListener
+    // ***************
+    /*
+     * XXX - Missing functionality
+     *
+     * @Override public Command reconcileFlows(ArrayList<OFMatchReconcile>
+     * ofmRcList) { ListIterator<OFMatchReconcile> iter =
+     * ofmRcList.listIterator(); while (iter.hasNext()) { OFMatchReconcile ofm =
+     * iter.next();
+     *
+     * // Remove the STOPPed flow. if (Command.STOP == reconcileFlow(ofm)) {
+     * iter.remove(); } }
+     *
+     * if (ofmRcList.size() > 0) { return Command.CONTINUE; } else { return
+     * Command.STOP; } }
+     *
+     * protected Command reconcileFlow(OFMatchReconcile ofm) {
+     * debugCounters.updateCounter(CNT_RECONCILE_REQUEST); // Extract source
+     * entity information Entity srcEntity =
+     * getEntityFromFlowMod(ofm.ofmWithSwDpid, true); if (srcEntity == null) {
+     * debugCounters.updateCounter(CNT_RECONCILE_NO_SOURCE); return
+     * Command.STOP; }
+     *
+     * // Find the device by source entity Device srcDevice =
+     * findDeviceByEntity(srcEntity); if (srcDevice == null) {
+     * debugCounters.updateCounter(CNT_RECONCILE_NO_SOURCE); return
+     * Command.STOP; } // Store the source device in the context
+     * fcStore.put(ofm.cntx, CONTEXT_SRC_DEVICE, srcDevice);
+     *
+     * // Find the device matching the destination from the entity // classes of
+     * the source. Entity dstEntity = getEntityFromFlowMod(ofm.ofmWithSwDpid,
+     * false); Device dstDevice = null; if (dstEntity != null) { dstDevice =
+     * findDestByEntity(srcDevice.getEntityClass(), dstEntity); if (dstDevice !=
+     * null) fcStore.put(ofm.cntx, CONTEXT_DST_DEVICE, dstDevice); else
+     * debugCounters.updateCounter(CNT_RECONCILE_NO_DEST); } else {
+     * debugCounters.updateCounter(CNT_RECONCILE_NO_DEST); } if
+     * (logger.isTraceEnabled()) {
+     * logger.trace("Reconciling flow: match={}, srcEntity={}, srcDev={}, " +
+     * "dstEntity={}, dstDev={}", new Object[] {ofm.ofmWithSwDpid.getOfMatch(),
+     * srcEntity, srcDevice, dstEntity, dstDevice } ); } return
+     * Command.CONTINUE; }
+     */
+
+    // *****************
+    // IListenDataPacket
+    // *****************
+
+    @Override
+    public PacketResult receiveDataPacket(RawPacket inPkt) {
+        // XXX - Can this really pass in null? Why would you ever want that?
+        if (inPkt == null) {
+            return PacketResult.IGNORED;
+        }
+        try {
+            throw new Exception("Sample");
+        } catch (Exception e) {
+            logger.error("Sample stack trace", e);
+        }
+
+        Packet formattedPak = this.dataPacketService.decodeDataPacket(inPkt);
+        Ethernet eth;
+        if (formattedPak instanceof Ethernet) {
+            eth = (Ethernet) formattedPak;
+        } else {
+            return PacketResult.IGNORED;
+        }
+
+        // Extract source entity information
+        NodeConnector inPort = inPkt.getIncomingNodeConnector();
+        Entity srcEntity = getSourceEntityFromPacket(eth, inPort);
+        if (srcEntity == null) {
+            // debugCounters.updateCounter(CNT_BROADCAST_SOURCE);
+            return PacketResult.CONSUME;
+        }
+
+        // Learn from ARP packet for special VRRP settings.
+        // In VRRP settings, the source MAC address and sender MAC
+        // addresses can be different. In such cases, we need to learn
+        // the IP to MAC mapping of the VRRP IP address. The source
+        // entity will not have that information. Hence, a separate call
+        // to learn devices in such cases.
+        learnDeviceFromArpResponseData(eth, inPort);
+
+        // Learn/lookup device information
+        Device srcDevice = learnDeviceByEntity(srcEntity);
+        if (srcDevice == null) {
+            // debugCounters.updateCounter(CNT_NO_SOURCE);
+            return PacketResult.CONSUME;
+        }
+        logger.trace("Saw packet from device {}", srcDevice);
+
+        // // Store the source device in the context
+        // fcStore.put(cntx, CONTEXT_SRC_DEVICE, srcDevice);
+        //
+        // // Find the device matching the destination from the entity
+        // // classes of the source.
+        // Entity dstEntity = getDestEntityFromPacket(eth);
+        // Device dstDevice = null;
+        // if (dstEntity != null) {
+        // dstDevice =
+        // findDestByEntity(srcDevice.getEntityClass(), dstEntity);
+        // if (dstDevice != null)
+        // fcStore.put(cntx, CONTEXT_DST_DEVICE, dstDevice);
+        // //else
+        // //debugCounters.updateCounter(CNT_NO_DEST);
+        // } else {
+        // //debugCounters.updateCounter(CNT_NO_DEST);
+        // }
+        //
+        // if (logger.isTraceEnabled()) {
+        // logger.trace("Received PI: {} on switch {}, port {} *** eth={}" +
+        // " *** srcDev={} *** dstDev={} *** ",
+        // new Object[] { pi, sw.getStringId(), pi.getInPort(), eth,
+        // srcDevice, dstDevice });
+        // }
+        //
+        // snoopDHCPClientName(eth, srcDevice);
+
+        return PacketResult.KEEP_PROCESSING;
+    }
+
+    // ****************
+    // Internal methods
+    // ****************
+
+    /**
+     * Snoop and record client-provided host name from DHCP requests
+     *
+     * @param eth
+     * @param srcDevice
+     */
+    // private void snoopDHCPClientName(Ethernet eth, Device srcDevice) {
+    // if (! (eth.getPayload() instanceof IPv4) )
+    // return;
+    // IPv4 ipv4 = (IPv4) eth.getPayload();
+    // if (! (ipv4.getPayload() instanceof UDP) )
+    // return;
+    // UDP udp = (UDP) ipv4.getPayload();
+    // if (!(udp.getPayload() instanceof DHCP))
+    // return;
+    // DHCP dhcp = (DHCP) udp.getPayload();
+    // byte opcode = dhcp.getOpCode();
+    // if (opcode == DHCP.OPCODE_REQUEST) {
+    // DHCPOption dhcpOption = dhcp.getOption(
+    // DHCPOptionCode.OptionCode_Hostname);
+    // if (dhcpOption != null) {
+    // debugCounters.updateCounter(CNT_DHCP_CLIENT_NAME_SNOOPED);
+    // srcDevice.dhcpClientName = new String(dhcpOption.getData());
+    // }
+    // }
+    // }
+
+    /**
+     * Check whether the given attachment point is valid given the current
+     * topology
+     *
+     * @param switchDPID
+     *            the DPID
+     * @param switchPort
+     *            the port
+     * @return true if it's a valid attachment point
+     */
+    public boolean isValidAttachmentPoint(NodeConnector port) {
+        // XXX - missing functionality -- need topology module
+        // if (topology.isAttachmentPointPort(port) == false)
+        // return false;
+        if (topology.isInternal(port))
+            return false;
+        if (!switchManager.isNodeConnectorEnabled(port))
+            return false;
+        if (suppressAPs.contains(new SwitchPort(port)))
+            return false;
+
+        return true;
+    }
+
+    /**
+     * Get sender IP address from packet if the packet is either an ARP packet.
+     *
+     * @param eth
+     * @param dlAddr
+     * @return
+     */
+    private int getSrcNwAddr(Ethernet eth, long dlAddr) {
+        if (eth.getPayload() instanceof ARP) {
+            ARP arp = (ARP) eth.getPayload();
+            if ((arp.getProtocolType() == ARP.PROTO_TYPE_IP)
+                    && (toLong(arp.getSenderHardwareAddress()) == dlAddr)) {
+                return toIPv4Address(arp.getSenderProtocolAddress());
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Parse an entity from an {@link Ethernet} packet.
+     *
+     * @param eth
+     *            the packet to parse
+     * @param sw
+     *            the switch on which the packet arrived
+     * @param pi
+     *            the original packetin
+     * @return the entity from the packet
+     */
+    protected Entity getSourceEntityFromPacket(Ethernet eth, NodeConnector port) {
+        byte[] dlAddrArr = eth.getSourceMACAddress();
+        long dlAddr = toLong(dlAddrArr);
+
+        // Ignore broadcast/multicast source
+        if ((dlAddrArr[0] & 0x1) != 0)
+            return null;
+
+        // XXX missing functionality
+        // short vlan = 0;
+        int nwSrc = getSrcNwAddr(eth, dlAddr);
+        return new Entity(dlAddr, null, ((nwSrc != 0) ? nwSrc : null), port,
+                new Date());
+    }
+
+    /**
+     * Learn device from ARP data in scenarios where the Ethernet source MAC is
+     * different from the sender hardware address in ARP data.
+     */
+    protected void learnDeviceFromArpResponseData(Ethernet eth,
+            NodeConnector port) {
+
+        if (!(eth.getPayload() instanceof ARP))
+            return;
+        ARP arp = (ARP) eth.getPayload();
+
+        byte[] dlAddrArr = eth.getSourceMACAddress();
+        long dlAddr = toLong(dlAddrArr);
+
+        byte[] senderHardwareAddr = arp.getSenderHardwareAddress();
+        long senderAddr = toLong(senderHardwareAddr);
+
+        if (dlAddr == senderAddr)
+            return;
+
+        // Ignore broadcast/multicast source
+        if ((senderHardwareAddr[0] & 0x1) != 0)
+            return;
+
+        // short vlan = eth.getVlanID();
+        int nwSrc = toIPv4Address(arp.getSenderProtocolAddress());
+
+        Entity e = new Entity(senderAddr, null, ((nwSrc != 0) ? nwSrc : null),
+                port, new Date());
+
+        learnDeviceByEntity(e);
+    }
+
+    /**
+     * Get a (partial) entity for the destination from the packet.
+     *
+     * @param eth
+     * @return
+     */
+    // protected Entity getDestEntityFromPacket(Ethernet eth) {
+    // byte[] dlAddrArr = eth.getDestinationMACAddress();
+    // long dlAddr = Ethernet.toLong(dlAddrArr);
+    // short vlan = eth.getVlanID();
+    // int nwDst = 0;
+    //
+    // // Ignore broadcast/multicast destination
+    // if ((dlAddrArr[0] & 0x1) != 0)
+    // return null;
+    //
+    // if (eth.getPayload() instanceof IPv4) {
+    // IPv4 ipv4 = (IPv4) eth.getPayload();
+    // nwDst = ipv4.getDestinationAddress();
+    // }
+    //
+    // return new Entity(dlAddr,
+    // ((vlan >= 0) ? vlan : null),
+    // ((nwDst != 0) ? nwDst : null),
+    // null,
+    // null,
+    // null);
+    // }
+
+    /**
+     * Parse an entity from an OFMatchWithSwDpid.
+     *
+     * @param ofmWithSwDpid
+     * @return the entity from the packet
+     */
+    // private Entity getEntityFromFlowMod(OFMatchWithSwDpid ofmWithSwDpid,
+    // boolean isSource) {
+    // byte[] dlAddrArr = ofmWithSwDpid.getOfMatch().getDataLayerSource();
+    // int nwSrc = ofmWithSwDpid.getOfMatch().getNetworkSource();
+    // if (!isSource) {
+    // dlAddrArr = ofmWithSwDpid.getOfMatch().getDataLayerDestination();
+    // nwSrc = ofmWithSwDpid.getOfMatch().getNetworkDestination();
+    // }
+    //
+    // long dlAddr = Ethernet.toLong(dlAddrArr);
+    //
+    // // Ignore broadcast/multicast source
+    // if ((dlAddrArr[0] & 0x1) != 0)
+    // return null;
+    //
+    // Long swDpid = null;
+    // Short inPort = null;
+    //
+    // if (isSource) {
+    // swDpid = ofmWithSwDpid.getSwitchDataPathId();
+    // inPort = ofmWithSwDpid.getOfMatch().getInputPort();
+    // }
+    //
+    // /**for the new flow cache design, the flow mods retrived are not always
+    // from the source, learn AP should be disabled --meiyang*/
+    // boolean learnap = false;
+    // /**
+    // * if (swDpid == null ||
+    // inPort == null ||
+    // !isValidAttachmentPoint(swDpid, inPort)) {
+    // // 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 = ofmWithSwDpid.getOfMatch().getDataLayerVirtualLan();
+    // return new Entity(dlAddr,
+    // ((vlan >= 0) ? vlan : null),
+    // ((nwSrc != 0) ? nwSrc : null),
+    // (learnap ? swDpid : null),
+    // (learnap ? (int)inPort : null),
+    // new Date());
+    // }
+
+    /**
+     * 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) {
+        // 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);
+            if (entityClass == null) {
+                return null;
+            }
+            ClassState classState = getClassState(entityClass);
+
+            if (classState.classIndex != null) {
+                deviceKey = classState.classIndex.findByEntity(entity);
+            }
+        }
+        if (deviceKey == null)
+            return null;
+        return deviceMap.get(deviceKey);
+    }
+
+    /**
+     * 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.
+     *
+     * @param reference
+     *            the source device's entity class. The returned destination
+     *            will be in the same entity class as the source.
+     * @param dstEntity
+     *            the entity to look up
+     * @return an {@link Device} or null if no device is found.
+     */
+    protected Device findDestByEntity(IEntityClass reference, Entity dstEntity) {
+
+        // 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(reference);
+            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 the
+     * provided {@link Entity}.
+     *
+     * @param clazz
+     *            the entity class to search for the entity
+     * @param entity
+     *            the entity to search for
+     * @return The {@link Device} object if found private Device
+     *         findDeviceInClassByEntity(IEntityClass clazz, Entity entity) { //
+     *         XXX - TODO throw new UnsupportedOperationException(); }
+     */
+
+    /**
+     * Look up a {@link Device} based on the provided {@link Entity}. Also
+     * learns based on the new entity, and will update existing devices as
+     * required.
+     *
+     * @param entity
+     *            the {@link Entity}
+     * @return The {@link Device} object if found
+     */
+    protected Device learnDeviceByEntity(Entity entity) {
+        logger.info("Primary index {}", primaryIndex);
+        ArrayList<Long> deleteQueue = null;
+        LinkedList<DeviceUpdate> deviceUpdates = null;
+        Device device = null;
+
+        // we may need to restart the learning process if we detect
+        // concurrent modification. Note that we ensure that at least
+        // one thread should always succeed so we don't get into infinite
+        // starvation loops
+        while (true) {
+            deviceUpdates = null;
+
+            // 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);
+                if (entityClass == null) {
+                    // could not classify entity. No device
+                    device = null;
+                    break;
+                }
+                ClassState classState = getClassState(entityClass);
+
+                if (classState.classIndex != null) {
+                    deviceKey = classState.classIndex.findByEntity(entity);
+                }
+            }
+            if (deviceKey != null) {
+                // If the primary or secondary index contains the entity
+                // use resulting device key to look up the device in the
+                // device map, and use the referenced Device below.
+                device = deviceMap.get(deviceKey);
+                if (device == null) {
+                    // This can happen due to concurrent modification
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("No device for deviceKey {} while "
+                                + "while processing entity {}", deviceKey,
+                                entity);
+                    }
+                    // if so, then try again till we don't even get the device
+                    // key
+                    // and so we recreate the device
+                    continue;
+                }
+            } else {
+                // If the secondary index does not contain the entity,
+                // create a new Device object containing the entity, and
+                // generate a new device ID if the the entity is on an
+                // attachment point port. Otherwise ignore.
+                if (entity.hasSwitchPort()
+                        && !isValidAttachmentPoint(entity.getPort())) {
+                    // debugCounters.updateCounter(CNT_DEVICE_ON_INTERAL_PORT_NOT_LEARNED);
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("Not learning new device on internal"
+                                + " link: {}", entity);
+                    }
+                    device = null;
+                    break;
+                }
+                // Before we create the new device also check if
+                // the entity is allowed (e.g., for spoofing protection)
+                if (!isEntityAllowed(entity, entityClass)) {
+                    // debugCounters.updateCounter(CNT_PACKET_NOT_ALLOWED);
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("PacketIn is not allowed {} {}",
+                                entityClass.getName(), entity);
+                    }
+                    device = null;
+                    break;
+                }
+                synchronized (deviceKeyLock) {
+                    deviceKey = Long.valueOf(deviceKeyCounter++);
+                }
+                device = allocateDevice(deviceKey, entity, entityClass);
+
+                // Add the new device to the primary map with a simple put
+                deviceMap.put(deviceKey, device);
+
+                // update indices
+                if (!updateIndices(device, deviceKey)) {
+                    if (deleteQueue == null)
+                        deleteQueue = new ArrayList<Long>();
+                    deleteQueue.add(deviceKey);
+                    continue;
+                }
+
+                updateSecondaryIndices(entity, entityClass, deviceKey);
+
+                // We need to count and log here. If we log earlier we could
+                // hit a concurrent modification and restart the dev creation
+                // and potentially count the device twice.
+                // debugCounters.updateCounter(CNT_NEW_DEVICE);
+                if (logger.isDebugEnabled()) {
+                    logger.debug(
+                            "New device created: {} deviceKey={}, entity={}",
+                            new Object[] { device, deviceKey, entity });
+                }
+                // generate new device update
+                deviceUpdates = updateUpdates(deviceUpdates, new DeviceUpdate(
+                        device, ADD, null));
+
+                break;
+            }
+            // if it gets here, we have a pre-existing Device for this Entity
+            if (!isEntityAllowed(entity, device.getEntityClass())) {
+                // debugCounters.updateCounter(CNT_PACKET_NOT_ALLOWED);
+                if (logger.isDebugEnabled()) {
+                    logger.info("PacketIn is not allowed {} {}", device
+                            .getEntityClass().getName(), entity);
+                }
+                return null;
+            }
+            // If this is not an attachment point port we don't learn the new
+            // entity
+            // and don't update indexes. But we do allow the device to continue
+            // up
+            // the chain.
+            if (entity.hasSwitchPort()
+                    && !isValidAttachmentPoint(entity.getPort())) {
+                // debugCounters.updateCounter(CNT_PACKET_ON_INTERNAL_PORT_FOR_KNOWN_DEVICE);
+                break;
+            }
+            int entityindex = -1;
+            if ((entityindex = device.entityIndex(entity)) >= 0) {
+                // Entity already exists
+                // update timestamp on the found entity
+                Date lastSeen = entity.getLastSeenTimestamp();
+                if (lastSeen == null) {
+                    lastSeen = new Date();
+                    entity.setLastSeenTimestamp(lastSeen);
+                }
+                device.entities[entityindex].setLastSeenTimestamp(lastSeen);
+                // we break the loop after checking for changes to the AP
+            } else {
+                // New entity for this device
+                // compute the insertion point for the entity.
+                // see Arrays.binarySearch()
+                entityindex = -(entityindex + 1);
+                Device newDevice = allocateDevice(device, entity, entityindex);
+
+                // generate updates
+                EnumSet<DeviceField> changedFields = findChangedFields(device,
+                        entity);
+
+                // update the device map with a replace call
+                boolean res = deviceMap.replace(deviceKey, device, newDevice);
+                // If replace returns false, restart the process from the
+                // beginning (this implies another thread concurrently
+                // modified this Device).
+                if (!res)
+                    continue;
+
+                device = newDevice;
+                // update indices
+                if (!updateIndices(device, deviceKey)) {
+                    continue;
+                }
+                updateSecondaryIndices(entity, device.getEntityClass(),
+                        deviceKey);
+
+                // We need to count here after all the possible "continue"
+                // statements in this branch
+                // debugCounters.updateCounter(CNT_NEW_ENTITY);
+                if (changedFields.size() > 0) {
+                    // debugCounters.updateCounter(CNT_DEVICE_CHANGED);
+                    deviceUpdates = updateUpdates(deviceUpdates,
+                            new DeviceUpdate(newDevice, CHANGE, changedFields));
+                }
+                // we break the loop after checking for changed AP
+            }
+            // Update attachment point (will only be hit if the device
+            // already existed and no concurrent modification)
+            if (entity.hasSwitchPort()) {
+                boolean moved = device.updateAttachmentPoint(entity.getPort(),
+                        entity.getLastSeenTimestamp().getTime());
+                // TODO: use update mechanism instead of sending the
+                // notification directly
+                if (moved) {
+                    // we count device moved events in
+                    // sendDeviceMovedNotification()
+                    sendDeviceMovedNotification(device);
+                    if (logger.isTraceEnabled()) {
+                        logger.trace("Device moved: attachment points {},"
+                                + "entities {}", device.attachmentPoints,
+                                device.entities);
+                    }
+                } else {
+                    if (logger.isTraceEnabled()) {
+                        logger.trace("Device attachment point updated: "
+                                + "attachment points {}," + "entities {}",
+                                device.attachmentPoints, device.entities);
+                    }
+                }
+            }
+            break;
+        }
+
+        if (deleteQueue != null) {
+            for (Long l : deleteQueue) {
+                Device dev = deviceMap.get(l);
+                this.deleteDevice(dev);
+            }
+        }
+
+        processUpdates(deviceUpdates);
+        // deviceSyncManager.storeDeviceThrottled(device);
+
+        return device;
+    }
+
+    protected boolean isEntityAllowed(Entity entity, IEntityClass entityClass) {
+        return true;
+    }
+
+    protected EnumSet<DeviceField> findChangedFields(Device device,
+            Entity newEntity) {
+        EnumSet<DeviceField> changedFields = EnumSet.of(DeviceField.IPV4,
+                DeviceField.VLAN, DeviceField.SWITCHPORT);
+
+        if (newEntity.getIpv4Address() == null)
+            changedFields.remove(DeviceField.IPV4);
+        if (newEntity.getVlan() == null)
+            changedFields.remove(DeviceField.VLAN);
+        if (newEntity.getPort() == null)
+            changedFields.remove(DeviceField.SWITCHPORT);
+
+        if (changedFields.size() == 0)
+            return changedFields;
+
+        for (Entity entity : device.getEntities()) {
+            if (newEntity.getIpv4Address() == null
+                    || (entity.getIpv4Address() != null && entity
+                            .getIpv4Address()
+                            .equals(newEntity.getIpv4Address())))
+                changedFields.remove(DeviceField.IPV4);
+            if (newEntity.getVlan() == null
+                    || (entity.getVlan() != null && entity.getVlan().equals(
+                            newEntity.getVlan())))
+                changedFields.remove(DeviceField.VLAN);
+            if (newEntity.getPort() == null
+                    || (entity.getPort() != null && entity.getPort().equals(
+                            newEntity.getPort())))
+                changedFields.remove(DeviceField.SWITCHPORT);
+        }
+
+        return changedFields;
+    }
+
+    /**
+     * Send update notifications to listeners
+     *
+     * @param updates
+     *            the updates to process.
+     */
+    protected void processUpdates(Queue<DeviceUpdate> updates) {
+        if (updates == null)
+            return;
+        DeviceUpdate update = null;
+        while (null != (update = updates.poll())) {
+            if (logger.isTraceEnabled()) {
+                logger.trace("Dispatching device update: {}", update);
+            }
+            // if (update.change == DeviceUpdate.Change.DELETE)
+            // deviceSyncManager.removeDevice(update.device);
+            // else
+            // deviceSyncManager.storeDevice(update.device);
+            List<IDeviceListener> listeners = deviceListeners
+                    .getOrderedListeners();
+            notifyListeners(listeners, update);
+        }
+    }
+
+    protected void notifyListeners(List<IDeviceListener> listeners,
+            DeviceUpdate update) {
+        if (listeners == null) {
+            return;
+        }
+        for (IDeviceListener listener : listeners) {
+            switch (update.change) {
+            case ADD:
+                listener.deviceAdded(update.device);
+                break;
+            case DELETE:
+                listener.deviceRemoved(update.device);
+                break;
+            case CHANGE:
+                for (DeviceField field : update.fieldsChanged) {
+                    switch (field) {
+                    case IPV4:
+                        listener.deviceIPV4AddrChanged(update.device);
+                        break;
+                    case SWITCHPORT:
+                        // listener.deviceMoved(update.device);
+                        break;
+                    case VLAN:
+                        listener.deviceVlanChanged(update.device);
+                        break;
+                    default:
+                        logger.debug("Unknown device field changed {}",
+                                update.fieldsChanged.toString());
+                        break;
+                    }
+                }
+                break;
+            }
+        }
+    }
+
+    /**
+     * 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.getIpv4Address() == null)
+                    return false;
+                break;
+            case SWITCHPORT:
+                if (e.getPort() == 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) {
+        if (update == null)
+            return list;
+        if (list == null)
+            list = new LinkedList<DeviceUpdate>();
+        list.add(update);
+
+        return list;
+    }
+
+    /**
+     * Get the secondary index for a class. Will return null if the secondary
+     * index was created concurrently in another thread.
+     *
+     * @param clazz
+     *            the class for the index
+     * @return
+     */
+    private ClassState getClassState(IEntityClass clazz) {
+        ClassState classState = classStateMap.get(clazz.getName());
+        if (classState != null)
+            return classState;
+
+        classState = new ClassState(clazz);
+        ClassState r = classStateMap.putIfAbsent(clazz.getName(), classState);
+        if (r != null) {
+            // concurrent add
+            return r;
+        }
+        return classState;
+    }
+
+    /**
+     * Update both the primary and class indices for the provided device. 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.
+     */
+    private boolean updateIndices(Device device, Long deviceKey) {
+        if (!primaryIndex.updateIndex(device, deviceKey)) {
+            return false;
+        }
+        IEntityClass entityClass = device.getEntityClass();
+        ClassState classState = getClassState(entityClass);
+
+        if (classState.classIndex != null) {
+            if (!classState.classIndex.updateIndex(device, deviceKey))
+                return false;
+        }
+        return true;
+    }
+
+    /**
+     * Update the secondary indices for the given entity and associated entity
+     * classes
+     *
+     * @param entity
+     *            the entity to update
+     * @param entityClass
+     *            the entity class for the entity
+     * @param deviceKey
+     *            the device key to set up
+     */
+    private void updateSecondaryIndices(Entity entity,
+            IEntityClass entityClass, Long deviceKey) {
+        for (DeviceIndex index : secondaryIndexMap.values()) {
+            index.updateIndex(entity, deviceKey);
+        }
+        ClassState state = getClassState(entityClass);
+        for (DeviceIndex index : state.secondaryIndexMap.values()) {
+            index.updateIndex(entity, deviceKey);
+        }
+    }
+
+    /**
+     * Clean up expired entities/devices
+     */
+    protected void cleanupEntities() {
+        // debugCounters.updateCounter(CNT_CLEANUP_ENTITIES_RUNS);
+
+        Calendar c = Calendar.getInstance();
+        c.add(Calendar.MILLISECOND, -ENTITY_TIMEOUT);
+        Date cutoff = c.getTime();
+
+        ArrayList<Entity> toRemove = new ArrayList<Entity>();
+        ArrayList<Entity> toKeep = new ArrayList<Entity>();
+
+        Iterator<Device> diter = deviceMap.values().iterator();
+        LinkedList<DeviceUpdate> deviceUpdates = new LinkedList<DeviceUpdate>();
+
+        while (diter.hasNext()) {
+            Device d = diter.next();
+
+            while (true) {
+                deviceUpdates.clear();
+                toRemove.clear();
+                toKeep.clear();
+                for (Entity e : d.getEntities()) {
+                    if (e.getLastSeenTimestamp() != null
+                            && 0 > e.getLastSeenTimestamp().compareTo(cutoff)) {
+                        // individual entity needs to be removed
+                        toRemove.add(e);
+                    } else {
+                        toKeep.add(e);
+                    }
+                }
+                if (toRemove.size() == 0) {
+                    break;
+                }
+
+                // debugCounters.updateCounter(CNT_ENTITY_REMOVED_TIMEOUT);
+                for (Entity e : toRemove) {
+                    removeEntity(e, d.getEntityClass(), d.getDeviceKey(),
+                            toKeep);
+                }
+
+                if (toKeep.size() > 0) {
+                    Device newDevice = allocateDevice(d.getDeviceKey(),
+                            d.getDHCPClientName(), d.oldAPs,
+                            d.attachmentPoints, toKeep, d.getEntityClass());
+
+                    EnumSet<DeviceField> changedFields = EnumSet
+                            .noneOf(DeviceField.class);
+                    for (Entity e : toRemove) {
+                        changedFields.addAll(findChangedFields(newDevice, e));
+                    }
+                    DeviceUpdate update = null;
+                    if (changedFields.size() > 0) {
+                        update = new DeviceUpdate(d, CHANGE, changedFields);
+                    }
+
+                    if (!deviceMap.replace(newDevice.getDeviceKey(), d,
+                            newDevice)) {
+                        // concurrent modification; try again
+                        // need to use device that is the map now for the next
+                        // iteration
+                        d = deviceMap.get(d.getDeviceKey());
+                        if (null != d)
+                            continue;
+                    }
+                    if (update != null) {
+                        // need to count after all possibly continue stmts in
+                        // this branch
+                        // debugCounters.updateCounter(CNT_DEVICE_CHANGED);
+                        deviceUpdates.add(update);
+                    }
+                } else {
+                    DeviceUpdate update = new DeviceUpdate(d, DELETE, null);
+                    if (!deviceMap.remove(d.getDeviceKey(), d)) {
+                        // concurrent modification; try again
+                        // need to use device that is the map now for the next
+                        // iteration
+                        d = deviceMap.get(d.getDeviceKey());
+                        if (null != d)
+                            continue;
+                        // debugCounters.updateCounter(CNT_DEVICE_DELETED);
+                    }
+                    deviceUpdates.add(update);
+                }
+                processUpdates(deviceUpdates);
+                break;
+            }
+        }
+    }
+
+    protected void removeEntity(Entity removed, IEntityClass entityClass,
+            Long deviceKey, Collection<Entity> others) {
+        // Don't count in this method. This method CAN BE called to clean-up
+        // after concurrent device adds/updates and thus counting here
+        // is misleading
+        for (DeviceIndex index : 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);
+
+        if (classState.classIndex != null) {
+            classState.classIndex.removeEntityIfNeeded(removed, deviceKey,
+                    others);
+        }
+    }
+
+    /**
+     * method to delete a given device, remove all entities first and then
+     * finally delete the device itself.
+     *
+     * @param device
+     */
+    protected void deleteDevice(Device device) {
+        // Don't count in this method. This method CAN BE called to clean-up
+        // after concurrent device adds/updates and thus counting here
+        // is misleading
+        ArrayList<Entity> emptyToKeep = new ArrayList<Entity>();
+        for (Entity entity : device.getEntities()) {
+            this.removeEntity(entity, device.getEntityClass(),
+                    device.getDeviceKey(), emptyToKeep);
+        }
+        if (!deviceMap.remove(device.getDeviceKey(), device)) {
+            if (logger.isDebugEnabled())
+                logger.debug("device map does not have this device -"
+                        + device.toString());
+        }
+    }
+
+    private EnumSet<DeviceField> getEntityKeys(Long macAddress, Short vlan,
+            Integer ipv4Address, NodeConnector port) {
+        // 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);
+        if (ipv4Address != null)
+            keys.add(DeviceField.IPV4);
+        if (port != null)
+            keys.add(DeviceField.SWITCHPORT);
+        return keys;
+    }
+
+    protected Iterator<Device> queryClassByEntity(IEntityClass clazz,
+            EnumSet<DeviceField> keyFields, Entity entity) {
+        ClassState classState = getClassState(clazz);
+        DeviceIndex index = classState.secondaryIndexMap.get(keyFields);
+        if (index == null)
+            return Collections.<Device> emptySet().iterator();
+        return new DeviceIndexInterator(this, index.queryByEntity(entity));
+    }
+
+    protected Device allocateDevice(Long deviceKey, Entity entity,
+            IEntityClass entityClass) {
+        return new Device(this, deviceKey, entity, entityClass);
+    }
+
+    // TODO: FIX THIS.
+    protected Device allocateDevice(Long deviceKey, String dhcpClientName,
+            List<AttachmentPoint> aps, List<AttachmentPoint> trueAPs,
+            Collection<Entity> entities, IEntityClass entityClass) {
+        return new Device(this, deviceKey, dhcpClientName, aps, trueAPs,
+                entities, entityClass);
+    }
+
+    protected Device allocateDevice(Device device, Entity entity,
+            int insertionpoint) {
+        return new Device(device, entity, insertionpoint);
+    }
+
+    // not used
+    protected Device allocateDevice(Device device, Set<Entity> entities) {
+        List<AttachmentPoint> newPossibleAPs = new ArrayList<AttachmentPoint>();
+        List<AttachmentPoint> newAPs = new ArrayList<AttachmentPoint>();
+        for (Entity entity : entities) {
+            if (entity.getPort() != null) {
+                AttachmentPoint aP = new AttachmentPoint(entity.getPort(), 0);
+                newPossibleAPs.add(aP);
+            }
+        }
+        if (device.attachmentPoints != null) {
+            for (AttachmentPoint oldAP : device.attachmentPoints) {
+                if (newPossibleAPs.contains(oldAP)) {
+                    newAPs.add(oldAP);
+                }
+            }
+        }
+        if (newAPs.isEmpty())
+            newAPs = null;
+        Device d = new Device(this, device.getDeviceKey(),
+                device.getDHCPClientName(), newAPs, null, entities,
+                device.getEntityClass());
+        d.updateAttachmentPoint();
+        return d;
+    }
+
+    // *********************
+    // ITopologyManagerAware
+    // *********************
+
+    @Override
+    public void edgeUpdate(List<TopoEdgeUpdate> topoedgeupdateList) {
+        Iterator<Device> diter = deviceMap.values().iterator();
+
+        while (diter.hasNext()) {
+            Device d = diter.next();
+            if (d.updateAttachmentPoint()) {
+                if (logger.isDebugEnabled()) {
+                    logger.debug("Attachment point changed for device: {}", d);
+                }
+                sendDeviceMovedNotification(d);
+            }
+        }
+    }
+
+    @Override
+    public void edgeOverUtilized(Edge edge) {
+        // nothing to do
+    }
+
+    @Override
+    public void edgeUtilBackToNormal(Edge edge) {
+        // nothing to do
+    }
+
+    // *********************
+    // IEntityClassListener
+    // *********************
+
+    @Override
+    public void entityClassChanged(Set<String> entityClassNames) {
+        /*
+         * iterate through the devices, reclassify the devices that belong to
+         * these entity class names
+         */
+        Iterator<Device> diter = deviceMap.values().iterator();
+        while (diter.hasNext()) {
+            Device d = diter.next();
+            if (d.getEntityClass() == null
+                    || entityClassNames.contains(d.getEntityClass().getName()))
+                reclassifyDevice(d);
+        }
+    }
+
+    // *************
+    // Local methods
+    // *************
+    /**
+     * Send update notifications to listeners
+     *
+     * @param updates
+     *            the updates to process.
+     */
+    protected void sendDeviceMovedNotification(Device d) {
+        // debugCounters.updateCounter(CNT_DEVICE_MOVED);
+        // deviceSyncManager.storeDevice(d);
+        List<IDeviceListener> listeners = deviceListeners.getOrderedListeners();
+        if (listeners != null) {
+            for (IDeviceListener listener : listeners) {
+                listener.deviceMoved(d);
+            }
+        }
+    }
+
+    /**
+     * this method will reclassify and reconcile a device - possibilities are -
+     * create new device(s), remove entities from this device. If the device
+     * entity class did not change then it returns false else true.
+     *
+     * @param device
+     */
+    protected boolean reclassifyDevice(Device device) {
+        // first classify all entities of this device
+        if (device == null) {
+            logger.debug("In reclassify for null device");
+            return false;
+        }
+        boolean needToReclassify = false;
+        for (Entity entity : device.entities) {
+            IEntityClass entityClass = this.entityClassifier
+                    .classifyEntity(entity);
+            if (entityClass == null || device.getEntityClass() == null) {
+                needToReclassify = true;
+                break;
+            }
+            if (!entityClass.getName()
+                    .equals(device.getEntityClass().getName())) {
+                needToReclassify = true;
+                break;
+            }
+        }
+        if (needToReclassify == false) {
+            return false;
+        }
+
+        // debugCounters.updateCounter(CNT_DEVICE_RECLASSIFY_DELETE);
+        LinkedList<DeviceUpdate> deviceUpdates = new LinkedList<DeviceUpdate>();
+        // delete this device and then re-learn all the entities
+        this.deleteDevice(device);
+        deviceUpdates.add(new DeviceUpdate(device, DeviceUpdate.Change.DELETE,
+                null));
+        if (!deviceUpdates.isEmpty())
+            processUpdates(deviceUpdates);
+        for (Entity entity : device.entities) {
+            this.learnDeviceByEntity(entity);
+        }
+        return true;
+    }
+
+    /**
+     * For testing: sets the interval between writes of the same device to the
+     * device store.
+     *
+     * @param intervalMs
+     */
+    // void setSyncStoreWriteInterval(int intervalMs) {
+    // this.syncStoreWriteIntervalMs = intervalMs;
+    // }
+
+    /**
+     * For testing: sets the time between transition to MASTER and consolidate
+     * store
+     *
+     * @param intervalMs
+     */
+    // void setInitialSyncStoreConsolidateMs(int intervalMs) {
+    // this.initialSyncStoreConsolidateMs = intervalMs;
+    // }
+
+    private long toLong(byte[] address) {
+        long mac = 0;
+        for (int i = 0; i < 6; i++) {
+            long t = (address[i] & 0xffL) << ((5 - i) * 8);
+            mac |= t;
+        }
+        return mac;
+    }
+
+    /**
+     * Accepts an IPv4 address in a byte array and returns the corresponding
+     * 32-bit integer value.
+     *
+     * @param ipAddress
+     * @return
+     */
+    private static int toIPv4Address(byte[] ipAddress) {
+        int ip = 0;
+        for (int i = 0; i < 4; i++) {
+            int t = (ipAddress[i] & 0xff) << ((3 - i) * 8);
+            ip |= t;
+        }
+        return ip;
+    }
+
+    private void registerDeviceManagerDebugCounters() {
+        /*
+         * XXX Missing functionality if (debugCounters == null) {
+         * logger.error("Debug Counter Service not found."); debugCounters = new
+         * NullDebugCounter(); return; }
+         * debugCounters.registerCounter(CNT_INCOMING,
+         * "All incoming packets seen by this module",
+         * CounterType.ALWAYS_COUNT);
+         * debugCounters.registerCounter(CNT_RECONCILE_REQUEST,
+         * "Number of flows that have been received for reconciliation by " +
+         * "this module", CounterType.ALWAYS_COUNT);
+         * debugCounters.registerCounter(CNT_RECONCILE_NO_SOURCE,
+         * "Number of flow reconcile events that failed because no source " +
+         * "device could be identified", CounterType.WARN); // is this really a
+         * warning debugCounters.registerCounter(CNT_RECONCILE_NO_DEST,
+         * "Number of flow reconcile events that failed because no " +
+         * "destination device could be identified", CounterType.WARN); // is
+         * this really a warning
+         * debugCounters.registerCounter(CNT_BROADCAST_SOURCE,
+         * "Number of packetIns that were discarded because the source " +
+         * "MAC was broadcast or multicast", CounterType.WARN);
+         * debugCounters.registerCounter(CNT_NO_SOURCE,
+         * "Number of packetIns that were discarded because the " +
+         * "could not identify a source device. This can happen if a " +
+         * "packet is not allowed, appears on an illegal port, does not " +
+         * "have a valid address space, etc.", CounterType.WARN);
+         * debugCounters.registerCounter(CNT_NO_DEST,
+         * "Number of packetIns that did not have an associated " +
+         * "destination device. E.g., because the destination MAC is " +
+         * "broadcast/multicast or is not yet known to the controller.",
+         * CounterType.ALWAYS_COUNT);
+         * debugCounters.registerCounter(CNT_DHCP_CLIENT_NAME_SNOOPED,
+         * "Number of times a DHCP client name was snooped from a " +
+         * "packetIn.", CounterType.ALWAYS_COUNT);
+         * debugCounters.registerCounter(CNT_DEVICE_ON_INTERAL_PORT_NOT_LEARNED,
+         * "Number of times packetIn was received on an internal port and" +
+         * "no source device is known for the source MAC. The packetIn is " +
+         * "discarded.", CounterType.WARN);
+         * debugCounters.registerCounter(CNT_PACKET_NOT_ALLOWED,
+         * "Number of times a packetIn was not allowed due to spoofing " +
+         * "protection configuration.", CounterType.WARN); // is this really a
+         * warning? debugCounters.registerCounter(CNT_NEW_DEVICE,
+         * "Number of times a new device was learned",
+         * CounterType.ALWAYS_COUNT); debugCounters.registerCounter(
+         * CNT_PACKET_ON_INTERNAL_PORT_FOR_KNOWN_DEVICE,
+         * "Number of times a packetIn was received on an internal port " +
+         * "for a known device.", CounterType.ALWAYS_COUNT);
+         * debugCounters.registerCounter(CNT_NEW_ENTITY,
+         * "Number of times a new entity was learned for an existing device",
+         * CounterType.ALWAYS_COUNT);
+         * debugCounters.registerCounter(CNT_DEVICE_CHANGED,
+         * "Number of times device properties have changed",
+         * CounterType.ALWAYS_COUNT);
+         * debugCounters.registerCounter(CNT_DEVICE_MOVED,
+         * "Number of times devices have moved", CounterType.ALWAYS_COUNT);
+         * debugCounters.registerCounter(CNT_CLEANUP_ENTITIES_RUNS,
+         * "Number of times the entity cleanup task has been run",
+         * CounterType.ALWAYS_COUNT);
+         * debugCounters.registerCounter(CNT_ENTITY_REMOVED_TIMEOUT,
+         * "Number of times entities have been removed due to timeout " +
+         * "(entity has been inactive for " + ENTITY_TIMEOUT/1000 + "s)",
+         * CounterType.ALWAYS_COUNT);
+         * debugCounters.registerCounter(CNT_DEVICE_DELETED,
+         * "Number of devices that have been removed due to inactivity",
+         * CounterType.ALWAYS_COUNT);
+         * debugCounters.registerCounter(CNT_DEVICE_RECLASSIFY_DELETE,
+         * "Number of devices that required reclassification and have been " +
+         * "temporarily delete for reclassification", CounterType.ALWAYS_COUNT);
+         * debugCounters.registerCounter(CNT_DEVICE_STORED,
+         * "Number of device entries written or updated to the sync store",
+         * CounterType.ALWAYS_COUNT);
+         * debugCounters.registerCounter(CNT_DEVICE_STORE_THROTTLED,
+         * "Number of times a device update to the sync store was " +
+         * "requested but not performed because the same device entities " +
+         * "have recently been updated already", CounterType.ALWAYS_COUNT);
+         * debugCounters.registerCounter(CNT_DEVICE_REMOVED_FROM_STORE,
+         * "Number of devices that were removed from the sync store " +
+         * "because the local controller removed the device due to " +
+         * "inactivity", CounterType.ALWAYS_COUNT);
+         * debugCounters.registerCounter(CNT_SYNC_EXCEPTION,
+         * "Number of times an operation on the sync store resulted in " +
+         * "sync exception", CounterType.WARN); // it this an error?
+         * debugCounters.registerCounter(CNT_DEVICES_FROM_STORE,
+         * "Number of devices that were read from the sync store after " +
+         * "the local controller transitioned from SLAVE to MASTER",
+         * CounterType.ALWAYS_COUNT);
+         * debugCounters.registerCounter(CNT_CONSOLIDATE_STORE_RUNS,
+         * "Number of times the task to consolidate entries in the " +
+         * "store witch live known devices has been run",
+         * CounterType.ALWAYS_COUNT);
+         * debugCounters.registerCounter(CNT_CONSOLIDATE_STORE_DEVICES_REMOVED,
+         * "Number of times a device has been removed from the sync " +
+         * "store because no corresponding live device is known. " +
+         * "This indicates a remote controller still writing device " +
+         * "entries despite the local controller being MASTER or an " +
+         * "incosistent store update from the local controller.",
+         * CounterType.WARN);
+         * debugCounters.registerCounter(CNT_TRANSITION_TO_MASTER,
+         * "Number of times this controller has transitioned from SLAVE " +
+         * "to MASTER role. Will be 0 or 1.", CounterType.ALWAYS_COUNT);
+         */
+    }
+
+    /**
+     * For testing: consolidate the store NOW
+     */
+    // void scheduleConsolidateStoreNow() {
+    // this.storeConsolidateTask.reschedule(0, TimeUnit.MILLISECONDS);
+    // }
+
+    // private class DeviceSyncManager {
+    // // maps (opaque) deviceKey to the time in System.nanoTime() when we
+    // // last wrote the device to the sync store
+    // private ConcurrentMap<Long, Long> lastWriteTimes =
+    // new ConcurrentHashMap<Long, Long>();
+    //
+    // /**
+    // * Write the given device to storage if we are MASTER.
+    // * Use this method if the device has significantly changed (e.g.,
+    // * new AP, new IP, entities removed).
+    // * @param d the device to store
+    // */
+    // public void storeDevice(Device d) {
+    // if (!isMaster)
+    // return;
+    // if (d == null)
+    // return;
+    // long now = System.nanoTime();
+    // writeUpdatedDeviceToStorage(d);
+    // lastWriteTimes.put(d.getDeviceKey(), now);
+    // }
+    //
+    // /**
+    // * Write the given device to storage if we are MASTER and if the
+    // * last write for the device was more than this.syncStoreIntervalNs
+    // * time ago.
+    // * Use this method to updated last active times in the store.
+    // * @param d the device to store
+    // */
+    // public void storeDeviceThrottled(Device d) {
+    // long intervalNs = syncStoreWriteIntervalMs*1000L*1000L;
+    // if (!isMaster)
+    // return;
+    // if (d == null)
+    // return;
+    // long now = System.nanoTime();
+    // Long last = lastWriteTimes.get(d.getDeviceKey());
+    // if (last == null ||
+    // now - last > intervalNs) {
+    // writeUpdatedDeviceToStorage(d);
+    // lastWriteTimes.put(d.getDeviceKey(), now);
+    // } else {
+    // debugCounters.updateCounter(CNT_DEVICE_STORE_THROTTLED);
+    // }
+    // }
+    //
+    // /**
+    // * Remove the given device from the store. If only some entities have
+    // * been removed the updated device should be written using
+    // * {@link #storeDevice(Device)}
+    // * @param d
+    // */
+    // public void removeDevice(Device d) {
+    // if (!isMaster)
+    // return;
+    // // FIXME: could we have a problem with concurrent put to the
+    // // hashMap? I.e., we write a stale entry to the map after the
+    // // delete and now are left with an entry we'll never clean up
+    // lastWriteTimes.remove(d.getDeviceKey());
+    // try {
+    // // TODO: should probably do versioned delete. OTOH, even
+    // // if we accidentally delete, we'll write it again after
+    // // the next entity ....
+    // debugCounters.updateCounter(CNT_DEVICE_REMOVED_FROM_STORE);
+    // storeClient.delete(DeviceSyncRepresentation.computeKey(d));
+    // } catch(ObsoleteVersionException e) {
+    // // FIXME
+    // } catch (SyncException e) {
+    // debugCounters.updateCounter(CNT_SYNC_EXCEPTION);
+    // logger.error("Could not remove device " + d + " from store", e);
+    // }
+    // }
+    //
+    // /**
+    // * Remove the given Versioned device from the store. If the device
+    // * was locally modified ignore the delete request.
+    // * @param syncedDeviceKey
+    // */
+    // private void removeDevice(Versioned<DeviceSyncRepresentation> dev) {
+    // try {
+    // debugCounters.updateCounter(CNT_DEVICE_REMOVED_FROM_STORE);
+    // storeClient.delete(dev.getValue().getKey(),
+    // dev.getVersion());
+    // } catch(ObsoleteVersionException e) {
+    // // Key was locally modified by another thread.
+    // // Do not delete and ignore.
+    // } catch(SyncException e) {
+    // debugCounters.updateCounter(CNT_SYNC_EXCEPTION);
+    // logger.error("Failed to remove device entry for " +
+    // dev.toString() + " from store.", e);
+    // }
+    // }
+    //
+    // /**
+    // * Synchronously transition from SLAVE to MASTER. By iterating through
+    // * the store and learning all devices from the store
+    // */
+    // private void goToMaster() {
+    // if (logger.isDebugEnabled()) {
+    // logger.debug("Transitioning to MASTER role");
+    // }
+    // debugCounters.updateCounter(CNT_TRANSITION_TO_MASTER);
+    // IClosableIterator<Map.Entry<String,Versioned<DeviceSyncRepresentation>>>
+    // iter = null;
+    // try {
+    // iter = storeClient.entries();
+    // } catch (SyncException e) {
+    // debugCounters.updateCounter(CNT_SYNC_EXCEPTION);
+    // logger.error("Failed to read devices from sync store", e);
+    // return;
+    // }
+    // try {
+    // while(iter.hasNext()) {
+    // Versioned<DeviceSyncRepresentation> versionedDevice =
+    // iter.next().getValue();
+    // DeviceSyncRepresentation storedDevice =
+    // versionedDevice.getValue();
+    // if (storedDevice == null)
+    // continue;
+    // debugCounters.updateCounter(CNT_DEVICES_FROM_STORE);
+    // for(SyncEntity se: storedDevice.getEntities()) {
+    // learnDeviceByEntity(se.asEntity());
+    // }
+    // }
+    // } finally {
+    // if (iter != null)
+    // iter.close();
+    // }
+    // storeConsolidateTask.reschedule(initialSyncStoreConsolidateMs,
+    // TimeUnit.MILLISECONDS);
+    // }
+    //
+    // /**
+    // * Actually perform the write of the device to the store
+    // * FIXME: concurrent modification behavior
+    // * @param device The device to write
+    // */
+    // private void writeUpdatedDeviceToStorage(Device device) {
+    // try {
+    // debugCounters.updateCounter(CNT_DEVICE_STORED);
+    // // FIXME: use a versioned put
+    // DeviceSyncRepresentation storeDevice =
+    // new DeviceSyncRepresentation(device);
+    // storeClient.put(storeDevice.getKey(), storeDevice);
+    // } catch (ObsoleteVersionException e) {
+    // // FIXME: what's the right behavior here. Can the store client
+    // // even throw this error?
+    // } catch (SyncException e) {
+    // debugCounters.updateCounter(CNT_SYNC_EXCEPTION);
+    // logger.error("Could not write device " + device +
+    // " to sync store:", e);
+    // }
+    // }
+    //
+    // /**
+    // * Iterate through all entries in the sync store. For each device
+    // * in the store check if any stored entity matches a live device. If
+    // * no entities match a live device we remove the entry from the store.
+    // *
+    // * Note: we do not check if all devices known to device manager are
+    // * in the store. We rely on regular packetIns for that.
+    // * Note: it's possible that multiple entries in the store map to the
+    // * same device. We don't check or handle this case.
+    // *
+    // * We need to perform this check after a SLAVE->MASTER transition to
+    // * get rid of all entries the old master might have written to the
+    // * store after we took over. We also run it regularly in MASTER
+    // * state to ensure we don't have stale entries in the store
+    // */
+    // private void consolidateStore() {
+    // if (!isMaster)
+    // return;
+    // debugCounters.updateCounter(CNT_CONSOLIDATE_STORE_RUNS);
+    // if (logger.isDebugEnabled()) {
+    // logger.debug("Running consolidateStore.");
+    // }
+    // IClosableIterator<Map.Entry<String,Versioned<DeviceSyncRepresentation>>>
+    // iter = null;
+    // try {
+    // iter = storeClient.entries();
+    // } catch (SyncException e) {
+    // debugCounters.updateCounter(CNT_SYNC_EXCEPTION);
+    // logger.error("Failed to read devices from sync store", e);
+    // return;
+    // }
+    // try {
+    // while(iter.hasNext()) {
+    // boolean found = false;
+    // Versioned<DeviceSyncRepresentation> versionedDevice =
+    // iter.next().getValue();
+    // DeviceSyncRepresentation storedDevice =
+    // versionedDevice.getValue();
+    // if (storedDevice == null)
+    // continue;
+    // for(SyncEntity se: storedDevice.getEntities()) {
+    // try {
+    // // Do we have a device for this entity??
+    // IDevice d = findDevice(se.macAddress, se.vlan,
+    // se.ipv4Address,
+    // se.switchDPID,
+    // se.switchPort);
+    // if (d != null) {
+    // found = true;
+    // break;
+    // }
+    // } catch (IllegalArgumentException e) {
+    // // not all key fields provided. Skip entity
+    // }
+    // }
+    // if (!found) {
+    // // We currently DO NOT have a live device that
+    // // matches the current device from the store.
+    // // Delete device from store.
+    // if (logger.isDebugEnabled()) {
+    // logger.debug("Removing device {} from store. No "
+    // + "corresponding live device",
+    // storedDevice.getKey());
+    // }
+    // debugCounters.updateCounter(CNT_CONSOLIDATE_STORE_DEVICES_REMOVED);
+    // removeDevice(versionedDevice);
+    // }
+    // }
+    // } finally {
+    // if (iter != null)
+    // iter.close();
+    // }
+    // }
+    // }
+    //
+    //
+    // /**
+    // * For testing. Sets the syncService. Only call after init but before
+    // * startUp. Used by MockDeviceManager
+    // * @param syncService
+    // */
+    // protected void setSyncServiceIfNotSet(ISyncService syncService) {
+    // if (this.syncService == null)
+    // this.syncService = syncService;
+    // }
+}
diff --git a/opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/DeviceMultiIndex.java b/opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/DeviceMultiIndex.java
new file mode 100644 (file)
index 0000000..85cb094
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2012 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * 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.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the
+ *    License. You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an "AS
+ *    IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ *    express or implied. See the License for the specific language
+ *    governing permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.hosttracker.internal;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.opendaylight.controller.hosttracker.Entity;
+import org.opendaylight.controller.hosttracker.IDeviceService.DeviceField;
+import org.opendaylight.controller.sal.utils.IterableIterator;
+
+/**
+ * An index that maps key fields of an entity to device keys, with multiple
+ * device keys allowed per entity
+ */
+public class DeviceMultiIndex extends DeviceIndex {
+    /**
+     * The index
+     */
+    private ConcurrentHashMap<IndexedEntity, Collection<Long>> index;
+
+    /**
+     * @param keyFields
+     */
+    public DeviceMultiIndex(EnumSet<DeviceField> keyFields) {
+        super(keyFields);
+        index = new ConcurrentHashMap<IndexedEntity, Collection<Long>>();
+    }
+
+    // ***********
+    // DeviceIndex
+    // ***********
+
+    @Override
+    public Iterator<Long> queryByEntity(Entity entity) {
+        IndexedEntity ie = new IndexedEntity(keyFields, entity);
+        Collection<Long> devices = index.get(ie);
+        if (devices != null)
+            return devices.iterator();
+
+        return Collections.<Long> emptySet().iterator();
+    }
+
+    @Override
+    public Iterator<Long> getAll() {
+        Iterator<Collection<Long>> iter = index.values().iterator();
+        return new IterableIterator<Long>(iter);
+    }
+
+    @Override
+    public boolean updateIndex(Device device, Long deviceKey) {
+        for (Entity e : device.entities) {
+            updateIndex(e, deviceKey);
+        }
+        return true;
+    }
+
+    @Override
+    public void updateIndex(Entity entity, Long deviceKey) {
+        Collection<Long> devices = null;
+
+        IndexedEntity ie = new IndexedEntity(keyFields, entity);
+        if (!ie.hasNonNullKeys())
+            return;
+
+        devices = index.get(ie);
+        if (devices == null) {
+            Map<Long, Boolean> chm = new ConcurrentHashMap<Long, Boolean>();
+            devices = Collections.newSetFromMap(chm);
+            Collection<Long> r = index.putIfAbsent(ie, devices);
+            if (r != null)
+                devices = r;
+        }
+
+        devices.add(deviceKey);
+    }
+
+    @Override
+    public void removeEntity(Entity entity) {
+        IndexedEntity ie = new IndexedEntity(keyFields, entity);
+        index.remove(ie);
+    }
+
+    @Override
+    public void removeEntity(Entity entity, Long deviceKey) {
+        IndexedEntity ie = new IndexedEntity(keyFields, entity);
+        Collection<Long> devices = index.get(ie);
+        if (devices != null)
+            devices.remove(deviceKey);
+    }
+}
diff --git a/opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/DeviceUniqueIndex.java b/opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/DeviceUniqueIndex.java
new file mode 100644 (file)
index 0000000..6ca0556
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2012 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * 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.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the
+ *    License. You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an "AS
+ *    IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ *    express or implied. See the License for the specific language
+ *    governing permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.hosttracker.internal;
+
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.opendaylight.controller.hosttracker.Entity;
+import org.opendaylight.controller.hosttracker.IDeviceService;
+
+/**
+ * An index that maps key fields of an entity uniquely to a device key
+ */
+public class DeviceUniqueIndex extends DeviceIndex {
+    /**
+     * The index
+     */
+    private final ConcurrentHashMap<IndexedEntity, Long> index;
+
+    /**
+     * Construct a new device index using the provided key fields
+     *
+     * @param keyFields
+     *            the key fields to use
+     */
+    public DeviceUniqueIndex(EnumSet<IDeviceService.DeviceField> keyFields) {
+        super(keyFields);
+        index = new ConcurrentHashMap<IndexedEntity, Long>();
+    }
+
+    // ***********
+    // DeviceIndex
+    // ***********
+
+    @Override
+    public Iterator<Long> queryByEntity(Entity entity) {
+        final Long deviceKey = findByEntity(entity);
+        if (deviceKey != null)
+            return Collections.<Long> singleton(deviceKey).iterator();
+
+        return Collections.<Long> emptySet().iterator();
+    }
+
+    @Override
+    public Iterator<Long> getAll() {
+        return index.values().iterator();
+    }
+
+    @Override
+    public boolean updateIndex(Device device, Long deviceKey) {
+        for (Entity e : device.entities) {
+            IndexedEntity ie = new IndexedEntity(keyFields, e);
+            if (!ie.hasNonNullKeys())
+                continue;
+
+            Long ret = index.putIfAbsent(ie, deviceKey);
+            if (ret != null && !ret.equals(deviceKey)) {
+                // If the return value is non-null, then fail the insert
+                // (this implies that a device using this entity has
+                // already been created in another thread).
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void updateIndex(Entity entity, Long deviceKey) {
+        IndexedEntity ie = new IndexedEntity(keyFields, entity);
+        if (!ie.hasNonNullKeys())
+            return;
+        index.put(ie, deviceKey);
+    }
+
+    @Override
+    public void removeEntity(Entity entity) {
+        IndexedEntity ie = new IndexedEntity(keyFields, entity);
+        index.remove(ie);
+    }
+
+    @Override
+    public void removeEntity(Entity entity, Long deviceKey) {
+        IndexedEntity ie = new IndexedEntity(keyFields, entity);
+        index.remove(ie, deviceKey);
+    }
+
+    // **************
+    // Public Methods
+    // **************
+
+    /**
+     * Look up a {@link Device} based on the provided {@link Entity}.
+     *
+     * @param entity
+     *            the entity to search for
+     * @return The key for the {@link Device} object if found
+     */
+    public Long findByEntity(Entity entity) {
+        IndexedEntity ie = new IndexedEntity(keyFields, entity);
+        Long deviceKey = index.get(ie);
+        if (deviceKey == null)
+            return null;
+        return deviceKey;
+    }
+
+}
diff --git a/opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/IndexedEntity.java b/opendaylight/hosttracker_new/implementation/src/main/java/org/opendaylight/controller/hosttracker/internal/IndexedEntity.java
new file mode 100644 (file)
index 0000000..fa9fad8
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2013 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.hosttracker.internal;
+
+import java.util.EnumSet;
+
+import org.opendaylight.controller.hosttracker.Entity;
+import org.opendaylight.controller.hosttracker.IDeviceService;
+import org.opendaylight.controller.hosttracker.IDeviceService.DeviceField;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is a thin wrapper around {@link Entity} that allows overriding the
+ * behavior of {@link Object#hashCode()} and {@link Object#equals(Object)} so
+ * that the keying behavior in a hash map can be changed dynamically
+ *
+ * @author readams
+ */
+public class IndexedEntity {
+    protected EnumSet<DeviceField> keyFields;
+    protected Entity entity;
+    private int hashCode = 0;
+    protected static Logger logger = LoggerFactory
+            .getLogger(IndexedEntity.class);
+
+    /**
+     * Create a new {@link IndexedEntity} for the given {@link Entity} using the
+     * provided key fields.
+     *
+     * @param keyFields
+     *            The key fields that will be used for computing
+     *            {@link IndexedEntity#hashCode()} and
+     *            {@link IndexedEntity#equals(Object)}
+     * @param entity
+     *            the entity to wrap
+     */
+    public IndexedEntity(EnumSet<DeviceField> keyFields, Entity entity) {
+        super();
+        this.keyFields = keyFields;
+        this.entity = entity;
+    }
+
+    /**
+     * Check whether this entity has non-null values in any of its key fields
+     *
+     * @return true if any key fields have a non-null value
+     */
+    public boolean hasNonNullKeys() {
+        for (DeviceField f : keyFields) {
+            switch (f) {
+            case MAC:
+                return true;
+            case IPV4:
+                if (entity.getIpv4Address() != null)
+                    return true;
+                break;
+            case SWITCHPORT:
+                if (entity.getPort() != null)
+                    return true;
+                break;
+            case VLAN:
+                if (entity.getVlan() != null)
+                    return true;
+                break;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+
+        if (hashCode != 0) {
+            return hashCode;
+        }
+
+        final int prime = 31;
+        hashCode = 1;
+        for (DeviceField f : keyFields) {
+            switch (f) {
+            case MAC:
+                hashCode = prime
+                        * hashCode
+                        + (int) (entity.getMacAddress() ^ (entity
+                                .getMacAddress() >>> 32));
+                break;
+            case IPV4:
+                hashCode = prime
+                        * hashCode
+                        + ((entity.getIpv4Address() == null) ? 0 : entity
+                                .getIpv4Address().hashCode());
+                break;
+            case SWITCHPORT:
+                hashCode = prime
+                        * hashCode
+                        + ((entity.getPort() == null) ? 0 : entity.getPort()
+                                .hashCode());
+                break;
+            case VLAN:
+                hashCode = prime
+                        * hashCode
+                        + ((entity.getVlan() == null) ? 0 : entity.getVlan()
+                                .hashCode());
+                break;
+            }
+        }
+        return hashCode;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        IndexedEntity other = (IndexedEntity) obj;
+
+        if (!keyFields.equals(other.keyFields))
+            return false;
+
+        for (IDeviceService.DeviceField f : keyFields) {
+            switch (f) {
+            case MAC:
+                if (entity.getMacAddress() != other.entity.getMacAddress())
+                    return false;
+                break;
+            case IPV4:
+                if (entity.getIpv4Address() == null) {
+                    if (other.entity.getIpv4Address() != null)
+                        return false;
+                } else if (!entity.getIpv4Address().equals(
+                        other.entity.getIpv4Address()))
+                    return false;
+                break;
+            case SWITCHPORT:
+                if (entity.getPort() == null) {
+                    if (other.entity.getPort() != null)
+                        return false;
+                } else if (!entity.getPort().equals(other.entity.getPort()))
+                    return false;
+                break;
+            case VLAN:
+                if (entity.getVlan() == null) {
+                    if (other.entity.getVlan() != null)
+                        return false;
+                } else if (!entity.getVlan().equals(other.entity.getVlan()))
+                    return false;
+                break;
+            }
+        }
+
+        return true;
+    }
+
+}
diff --git a/opendaylight/hosttracker_new/implementation/src/test/java/org/opendaylight/controller/hosttracker/internal/DeviceManagerImplTest.java b/opendaylight/hosttracker_new/implementation/src/test/java/org/opendaylight/controller/hosttracker/internal/DeviceManagerImplTest.java
new file mode 100644 (file)
index 0000000..3bed3a5
--- /dev/null
@@ -0,0 +1,2644 @@
+package org.opendaylight.controller.hosttracker.internal;
+
+///*
+// * Copyright (c) 2011,2013 Big Switch Networks, Inc.
+// *
+// * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+// *
+// * 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.
+// *
+// * This file incorporates work covered by the following copyright and
+// * permission notice:
+// *
+// *    Originally created by David Erickson, Stanford University
+// *
+// *    Licensed under the Apache License, Version 2.0 (the "License");
+// *    you may not use this file except in compliance with the
+// *    License. You may obtain a copy of the License at
+// *
+// *         http://www.apache.org/licenses/LICENSE-2.0
+// *
+// *    Unless required by applicable law or agreed to in writing,
+// *    software distributed under the License is distributed on an "AS
+// *    IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+// *    express or implied. See the License for the specific language
+// *    governing permissions and limitations under the License.
+// */
+//
+//package org.opendaylight.controller.hosttracker.internal;
+//
+//import static org.easymock.EasyMock.anyLong;
+//import static org.easymock.EasyMock.anyObject;
+//import static org.easymock.EasyMock.anyShort;
+//import static org.easymock.EasyMock.createMock;
+//import static org.easymock.EasyMock.createNiceMock;
+//import static org.easymock.EasyMock.eq;
+//import static org.easymock.EasyMock.expect;
+//import static org.easymock.EasyMock.expectLastCall;
+//import static org.easymock.EasyMock.isA;
+//import static org.easymock.EasyMock.or;
+//import static org.easymock.EasyMock.replay;
+//import static org.easymock.EasyMock.reset;
+//import static org.easymock.EasyMock.verify;
+//import static org.junit.Assert.*;
+//
+//import java.util.ArrayList;
+//import java.util.Arrays;
+//import java.util.Calendar;
+//import java.util.Collection;
+//import java.util.Collections;
+//import java.util.Date;
+//import java.util.EnumSet;
+//import java.util.HashMap;
+//import java.util.HashSet;
+//import java.util.Iterator;
+//import java.util.List;
+//import java.util.Map;
+//import java.util.Map.Entry;
+//import java.util.Set;
+//import java.util.concurrent.ConcurrentHashMap;
+//
+//import org.junit.Before;
+//import org.junit.Test;
+//
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//
+//public class DeviceManagerImplTest {
+//
+//    protected static Logger logger =
+//            LoggerFactory.getLogger(DeviceManagerImplTest.class);
+//
+//    protected OFPacketIn packetIn_1, packetIn_2, packetIn_3;
+//    protected IPacket testARPReplyPacket_1, testARPReplyPacket_2,
+//    testARPReplyPacket_3;
+//    protected IPacket testARPReqPacket_1, testARPReqPacket_2;
+//    protected byte[] testARPReplyPacket_1_Srld, testARPReplyPacket_2_Srld;
+//    private MockSyncService syncService;
+//    private IStoreClient<String, DeviceSyncRepresentation> storeClient;
+//
+//    DeviceManagerImpl deviceManager;
+//    MemoryStorageSource storageSource;
+//    FlowReconcileManager flowReconcileMgr;
+//
+//    private IOFSwitch makeSwitchMock(long id) {
+//        IOFSwitch mockSwitch = createMock(IOFSwitch.class);
+//        ImmutablePort port = ImmutablePort.create("p1", (short)1);
+//        expect(mockSwitch.getId()).andReturn(id).anyTimes();
+//        expect(mockSwitch.getStringId())
+//                .andReturn(HexString.toHexString(id, 6)).anyTimes();
+//        expect(mockSwitch.getPort(anyShort()))
+//                .andReturn(port).anyTimes();
+//        return mockSwitch;
+//    }
+//
+//    /*
+//     * return an EasyMock ITopologyService that's setup so that it will
+//     * answer all questions a device or device manager will ask
+//     * (isAttachmentPointPort, etc.) in a way so that every port is a
+//     * non-BD, attachment point port.
+//     * The returned mock is still in record mode
+//     */
+//    private ITopologyService makeMockTopologyAllPortsAp() {
+//        ITopologyService mockTopology = createMock(ITopologyService.class);
+//        mockTopology.isAttachmentPointPort(anyLong(), anyShort());
+//        expectLastCall().andReturn(true).anyTimes();
+//        mockTopology.getL2DomainId(anyLong());
+//        expectLastCall().andReturn(1L).anyTimes();
+//        mockTopology.isBroadcastDomainPort(anyLong(), anyShort());
+//        expectLastCall().andReturn(false).anyTimes();
+//        mockTopology.isConsistent(anyLong(), anyShort(), anyLong(), anyShort());
+//        expectLastCall().andReturn(false).anyTimes();
+//        mockTopology.isInSameBroadcastDomain(anyLong(), anyShort(),
+//                                             anyLong(), anyShort());
+//        expectLastCall().andReturn(false).anyTimes();
+//        return mockTopology;
+//    }
+//
+//    @Override
+//    @Before
+//    public void setUp() throws Exception {
+//        doSetUp(Role.MASTER);
+//    }
+//
+//    public void doSetUp(Role initialRole) throws Exception {
+//        super.setUp();
+//
+//        this.syncService = new MockSyncService();
+//
+//        FloodlightModuleContext fmc = new FloodlightModuleContext();
+//        RestApiServer restApi = new RestApiServer();
+//        MockThreadPoolService tp = new MockThreadPoolService();
+//        ITopologyService topology = createMock(ITopologyService.class);
+//        fmc.addService(IThreadPoolService.class, tp);
+//        mockFloodlightProvider = getMockFloodlightProvider();
+//        mockFloodlightProvider.setRole(initialRole, "");
+//
+//        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);
+//        fmc.addService(ITopologyService.class, topology);
+//        fmc.addService(ISyncService.class, syncService);
+//        tp.init(fmc);
+//        restApi.init(fmc);
+//        storageSource.init(fmc);
+//        deviceManager.init(fmc);
+//        flowReconcileMgr.init(fmc);
+//        entityClassifier.init(fmc);
+//        syncService.init(fmc);
+//        storageSource.startUp(fmc);
+//        deviceManager.startUp(fmc);
+//        flowReconcileMgr.startUp(fmc);
+//        tp.startUp(fmc);
+//        entityClassifier.startUp(fmc);
+//        syncService.startUp(fmc);
+//
+//        this.storeClient =
+//                this.syncService.getStoreClient(DeviceManagerImpl.DEVICE_SYNC_STORE_NAME,
+//                            String.class, DeviceSyncRepresentation.class);
+//
+//        reset(topology);
+//        topology.addListener(deviceManager);
+//        expectLastCall().anyTimes();
+//        replay(topology);
+//
+//        IOFSwitch mockSwitch1 = makeSwitchMock(1L);
+//        IOFSwitch mockSwitch10 = makeSwitchMock(10L);
+//        IOFSwitch mockSwitch5 = makeSwitchMock(5L);
+//        IOFSwitch mockSwitch50 = makeSwitchMock(50L);
+//        Map<Long, IOFSwitch> switches = new HashMap<Long,IOFSwitch>();
+//        switches.put(1L, mockSwitch1);
+//        switches.put(10L, mockSwitch10);
+//        switches.put(5L, mockSwitch5);
+//        switches.put(50L, mockSwitch50);
+//        mockFloodlightProvider.setSwitches(switches);
+//
+//        replay(mockSwitch1, mockSwitch5, mockSwitch10, mockSwitch50);
+//
+//        // Build our test packet
+//        this.testARPReplyPacket_1 = new Ethernet()
+//        .setSourceMACAddress("00:44:33:22:11:01")
+//        .setDestinationMACAddress("00:11:22:33:44:55")
+//        .setEtherType(Ethernet.TYPE_ARP)
+//        .setVlanID((short)5)
+//        .setPayload(
+//                    new ARP()
+//                    .setHardwareType(ARP.HW_TYPE_ETHERNET)
+//                    .setProtocolType(ARP.PROTO_TYPE_IP)
+//                    .setHardwareAddressLength((byte) 6)
+//                    .setProtocolAddressLength((byte) 4)
+//                    .setOpCode(ARP.OP_REPLY)
+//                    .setSenderHardwareAddress(Ethernet.toMACAddress("00:44:33:22:11:01"))
+//                    .setSenderProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.1"))
+//                    .setTargetHardwareAddress(Ethernet.toMACAddress("00:11:22:33:44:55"))
+//                    .setTargetProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.2")));
+//        this.testARPReplyPacket_1_Srld = testARPReplyPacket_1.serialize();
+//
+//        // Another test packet with a different source IP
+//        this.testARPReplyPacket_2 = new Ethernet()
+//        .setSourceMACAddress("00:99:88:77:66:55")
+//        .setDestinationMACAddress("00:11:22:33:44:55")
+//        .setEtherType(Ethernet.TYPE_ARP)
+//        .setVlanID((short)5)
+//        .setPayload(
+//                    new ARP()
+//                    .setHardwareType(ARP.HW_TYPE_ETHERNET)
+//                    .setProtocolType(ARP.PROTO_TYPE_IP)
+//                    .setHardwareAddressLength((byte) 6)
+//                    .setProtocolAddressLength((byte) 4)
+//                    .setOpCode(ARP.OP_REPLY)
+//                    .setSenderHardwareAddress(Ethernet.toMACAddress("00:44:33:22:11:01"))
+//                    .setSenderProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.1"))
+//                    .setTargetHardwareAddress(Ethernet.toMACAddress("00:11:22:33:44:55"))
+//                    .setTargetProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.2")));
+//        this.testARPReplyPacket_2_Srld = testARPReplyPacket_2.serialize();
+//
+//        // Build the PacketIn
+//        this.packetIn_1 = ((OFPacketIn) mockFloodlightProvider.
+//                getOFMessageFactory().getMessage(OFType.PACKET_IN))
+//                .setBufferId(-1)
+//                .setInPort((short) 1)
+//                .setPacketData(this.testARPReplyPacket_1_Srld)
+//                .setReason(OFPacketInReason.NO_MATCH)
+//                .setTotalLength((short) this.testARPReplyPacket_1_Srld.length);
+//
+//        // Build the PacketIn
+//        this.packetIn_2 = ((OFPacketIn) mockFloodlightProvider.
+//                getOFMessageFactory().getMessage(OFType.PACKET_IN))
+//                .setBufferId(-1)
+//                .setInPort((short) 1)
+//                .setPacketData(this.testARPReplyPacket_2_Srld)
+//                .setReason(OFPacketInReason.NO_MATCH)
+//                .setTotalLength((short) this.testARPReplyPacket_2_Srld.length);
+//    }
+//
+//
+//
+//
+//
+//    @Test
+//    public void testLastSeen() throws Exception {
+//        Calendar c = Calendar.getInstance();
+//        Date d1 = c.getTime();
+//        Entity entity1 = new Entity(1L, null, null, null, null, d1);
+//        c.add(Calendar.SECOND, 1);
+//        Entity entity2 = new Entity(1L, null, 1, null, null, c.getTime());
+//
+//        IDevice d = deviceManager.learnDeviceByEntity(entity2);
+//        assertEquals(c.getTime(), d.getLastSeen());
+//        d = deviceManager.learnDeviceByEntity(entity1);
+//        assertEquals(c.getTime(), d.getLastSeen());
+//
+//        deviceManager.startUp(null);
+//        d = deviceManager.learnDeviceByEntity(entity1);
+//        assertEquals(d1, d.getLastSeen());
+//        d = deviceManager.learnDeviceByEntity(entity2);
+//        assertEquals(c.getTime(), d.getLastSeen());
+//    }
+//
+//    @Test
+//    public void testEntityLearning() throws Exception {
+//        IDeviceListener mockListener =
+//                createMock(IDeviceListener.class);
+//        expect(mockListener.getName()).andReturn("mockListener").atLeastOnce();
+//        expect(mockListener.isCallbackOrderingPostreq((String)anyObject(), (String)anyObject()))
+//        .andReturn(false).atLeastOnce();
+//        expect(mockListener.isCallbackOrderingPrereq((String)anyObject(), (String)anyObject()))
+//        .andReturn(false).atLeastOnce();
+//
+//        replay(mockListener);
+//        deviceManager.addListener(mockListener);
+//        verify(mockListener);
+//        reset(mockListener);
+//        deviceManager.entityClassifier= new MockEntityClassifier();
+//        deviceManager.startUp(null);
+//
+//        ITopologyService mockTopology = createMock(ITopologyService.class);
+//        expect(mockTopology.getL2DomainId(anyLong())).
+//        andReturn(1L).anyTimes();
+//        expect(mockTopology.isBroadcastDomainPort(anyLong(), anyShort())).
+//        andReturn(false).anyTimes();
+//
+//        expect(mockTopology.isAttachmentPointPort(anyLong(),
+//                                                  anyShort())).andReturn(true).anyTimes();
+//        expect(mockTopology.isConsistent(10L, (short)1, 10L, (short)1)).
+//        andReturn(true).anyTimes();
+//        expect(mockTopology.isConsistent(1L, (short)1, 1L, (short)1)).
+//        andReturn(true).anyTimes();
+//        expect(mockTopology.isConsistent(50L, (short)3, 50L, (short)3)).
+//        andReturn(true).anyTimes();
+//
+//        Date topologyUpdateTime = new Date();
+//        expect(mockTopology.getLastUpdateTime()).andReturn(topologyUpdateTime).
+//        anyTimes();
+//
+//        deviceManager.topology = mockTopology;
+//
+//        Entity entity1 = new Entity(1L, null, null, 1L, 1, new Date());
+//        Entity entity2 = new Entity(1L, null, null, 10L, 1, new Date());
+//        Entity entity3 = new Entity(1L, null, 1, 10L, 1, new Date());
+//        Entity entity4 = new Entity(1L, null, 1, 1L, 1, new Date());
+//        Entity entity5 = new Entity(2L, (short)4, 1, 5L, 2, new Date());
+//        Entity entity6 = new Entity(2L, (short)4, 1, 50L, 3, new Date());
+//        Entity entity7 = new Entity(2L, (short)4, 2, 50L, 3, new Date());
+//
+//        mockListener.deviceAdded(isA(IDevice.class));
+//        replay(mockListener, mockTopology);
+//
+//        Device d1 = deviceManager.learnDeviceByEntity(entity1);
+//        assertSame(d1, deviceManager.learnDeviceByEntity(entity1));
+//        assertSame(d1, deviceManager.findDeviceByEntity(entity1));
+//        assertEquals(DefaultEntityClassifier.entityClass ,
+//                          d1.getEntityClass());
+//        assertArrayEquals(new Short[] { -1 }, d1.getVlanId());
+//        assertArrayEquals(new Integer[] { }, d1.getIPv4Addresses());
+//
+//        assertEquals(1, deviceManager.getAllDevices().size());
+//        verify(mockListener);
+//
+//        reset(mockListener);
+//        mockListener.deviceAdded(isA(IDevice.class));
+//        replay(mockListener);
+//
+//        Device d2 = deviceManager.learnDeviceByEntity(entity2);
+//        assertFalse(d1.equals(d2));
+//        assertNotSame(d1, d2);
+//        assertNotSame(d1.getDeviceKey(), d2.getDeviceKey());
+//        assertEquals(MockEntityClassifier.testEC, d2.getEntityClass());
+//        assertArrayEquals(new Short[] { -1 }, d2.getVlanId());
+//        assertArrayEquals(new Integer[] { }, d2.getIPv4Addresses());
+//
+//        assertEquals(2, deviceManager.getAllDevices().size());
+//        verify(mockListener);
+//
+//        reset(mockListener);
+//        mockListener.deviceIPV4AddrChanged(isA(IDevice.class));
+//        replay(mockListener);
+//
+//        Device d3 = deviceManager.learnDeviceByEntity(entity3);
+//        assertNotSame(d2, d3);
+//        assertEquals(d2.getDeviceKey(), d3.getDeviceKey());
+//        assertEquals(MockEntityClassifier.testEC, d3.getEntityClass());
+//        assertArrayEquals(new Integer[] { 1 },
+//                          d3.getIPv4Addresses());
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(10L, 1) },
+//                          d3.getAttachmentPoints());
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(10L, 1) },
+//                          d3.getAttachmentPoints(true));
+//        assertArrayEquals(new Short[] { -1 },
+//                          d3.getVlanId());
+//
+//        assertEquals(2, deviceManager.getAllDevices().size());
+//        verify(mockListener);
+//
+//        reset(mockListener);
+//        mockListener.deviceIPV4AddrChanged(isA(IDevice.class));
+//        replay(mockListener);
+//
+//        Device d4 = deviceManager.learnDeviceByEntity(entity4);
+//        assertNotSame(d1, d4);
+//        assertEquals(d1.getDeviceKey(), d4.getDeviceKey());
+//        assertEquals(DefaultEntityClassifier.entityClass, d4.getEntityClass());
+//        assertArrayEquals(new Integer[] { 1 },
+//                          d4.getIPv4Addresses());
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) },
+//                          d4.getAttachmentPoints());
+//        assertArrayEquals(new Short[] { -1 },
+//                          d4.getVlanId());
+//
+//        assertEquals(2, deviceManager.getAllDevices().size());
+//        verify(mockListener);
+//
+//        reset(mockListener);
+//        mockListener.deviceAdded((isA(IDevice.class)));
+//        replay(mockListener);
+//
+//        Device d5 = deviceManager.learnDeviceByEntity(entity5);
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(5L, 2) },
+//                          d5.getAttachmentPoints());
+//        assertArrayEquals(new Short[] { (short) 4 },
+//                          d5.getVlanId());
+//        assertEquals(2L, d5.getMACAddress());
+//        assertEquals("00:00:00:00:00:02", d5.getMACAddressString());
+//        verify(mockListener);
+//
+//        reset(mockListener);
+//        mockListener.deviceAdded(isA(IDevice.class));
+//        replay(mockListener);
+//
+//        Device d6 = deviceManager.learnDeviceByEntity(entity6);
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(50L, 3) },
+//                          d6.getAttachmentPoints());
+//        assertArrayEquals(new Short[] { (short) 4 },
+//                          d6.getVlanId());
+//
+//        assertEquals(4, deviceManager.getAllDevices().size());
+//        verify(mockListener);
+//
+//        reset(mockListener);
+//        mockListener.deviceIPV4AddrChanged(isA(IDevice.class));
+//        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 },
+//                          d7.getVlanId());
+//
+//        assertEquals(4, deviceManager.getAllDevices().size());
+//        verify(mockListener);
+//
+//
+//        reset(mockListener);
+//        replay(mockListener);
+//
+//        reset(deviceManager.topology);
+//        deviceManager.topology.addListener(deviceManager);
+//        expectLastCall().times(1);
+//        replay(deviceManager.topology);
+//
+//        deviceManager.entityClassifier = new MockEntityClassifierMac();
+//        deviceManager.startUp(null);
+//        Entity entityNoClass = new Entity(5L, (short)1, 5, -1L, 1, new Date());
+//        assertEquals(null, deviceManager.learnDeviceByEntity(entityNoClass));
+//
+//        verify(mockListener);
+//    }
+//
+//
+//    private void doTestEntityOrdering(boolean computeInsertionPoint) throws Exception {
+//        Entity e = new Entity(10L, null, null, null, null, null);
+//        IEntityClass ec = createNiceMock(IEntityClass.class);
+//        Device d = new Device(deviceManager, 1L, e, ec);
+//
+//        int expectedLength = 1;
+//        Long[] macs = new Long[] {  5L,  // new first element
+//                                   15L,  // new last element
+//                                    7L,  // insert in middle
+//                                   12L,  // insert in middle
+//                                    6L,  // insert at idx 1
+//                                   14L,  // insert at idx length-2
+//                                    1L,
+//                                   20L
+//                                  };
+//
+//        for (Long mac: macs) {
+//            e = new Entity(mac, null, null, null, null, null);
+//            int insertionPoint;
+//            if (computeInsertionPoint) {
+//                insertionPoint = -(Arrays.binarySearch(d.entities, e)+1);
+//            } else {
+//                insertionPoint = -1;
+//            }
+//            d = deviceManager.allocateDevice(d, e, insertionPoint);
+//            expectedLength++;
+//            assertEquals(expectedLength, d.entities.length);
+//            for (int i = 0; i < d.entities.length-1; i++)
+//                assertEquals(-1, d.entities[i].compareTo(d.entities[i+1]));
+//        }
+//    }
+//
+//    @Test
+//    public void testEntityOrderingExternal() throws Exception {
+//        doTestEntityOrdering(true);
+//    }
+//
+//    @Test
+//    public void testEntityOrderingInternal() throws Exception {
+//        doTestEntityOrdering(false);
+//    }
+//
+//    @Test
+//    public void testAttachmentPointLearning() throws Exception {
+//        IDeviceListener mockListener =
+//                createMock(IDeviceListener.class);
+//        expect(mockListener.getName()).andReturn("mockListener").atLeastOnce();
+//        expect(mockListener.isCallbackOrderingPostreq((String)anyObject(), (String)anyObject()))
+//        .andReturn(false).atLeastOnce();
+//        expect(mockListener.isCallbackOrderingPrereq((String)anyObject(), (String)anyObject()))
+//        .andReturn(false).atLeastOnce();
+//
+//        replay(mockListener);
+//        deviceManager.addListener(mockListener);
+//        verify(mockListener);
+//        reset(mockListener);
+//
+//        ITopologyService mockTopology = createMock(ITopologyService.class);
+//        expect(mockTopology.getL2DomainId(1L)).
+//        andReturn(1L).anyTimes();
+//        expect(mockTopology.getL2DomainId(5L)).
+//        andReturn(1L).anyTimes();
+//        expect(mockTopology.getL2DomainId(10L)).
+//        andReturn(10L).anyTimes();
+//        expect(mockTopology.getL2DomainId(50L)).
+//        andReturn(10L).anyTimes();
+//        expect(mockTopology.isBroadcastDomainPort(anyLong(), anyShort())).
+//        andReturn(false).anyTimes();
+//        expect(mockTopology.isInSameBroadcastDomain(anyLong(), anyShort(),
+//                                                    anyLong(), anyShort())).andReturn(false).anyTimes();
+//
+//        expect(mockTopology.isAttachmentPointPort(anyLong(),
+//                                                  anyShort())).andReturn(true).anyTimes();
+//        expect(mockTopology.isConsistent(1L, (short)1, 5L, (short)1)).
+//        andReturn(false).anyTimes();
+//        expect(mockTopology.isConsistent(5L, (short)1, 10L, (short)1)).
+//        andReturn(false).anyTimes();
+//        expect(mockTopology.isConsistent(10L, (short)1, 50L, (short)1)).
+//        andReturn(false).anyTimes();
+//
+//        Date topologyUpdateTime = new Date();
+//        expect(mockTopology.getLastUpdateTime()).andReturn(topologyUpdateTime).
+//        anyTimes();
+//
+//        replay(mockTopology);
+//
+//        deviceManager.topology = mockTopology;
+//
+//        Calendar c = Calendar.getInstance();
+//        Entity entity1 = new Entity(1L, null, 1, 1L, 1, c.getTime());
+//        Entity entity0 = new Entity(1L, null, null, null, null, c.getTime());
+//        c.add(Calendar.SECOND, 1);
+//        Entity entity2 = new Entity(1L, null, null, 5L, 1, c.getTime());
+//        c.add(Calendar.SECOND, 1);
+//        Entity entity3 = new Entity(1L, null, null, 10L, 1, c.getTime());
+//        c.add(Calendar.SECOND, 1);
+//        Entity entity4 = new Entity(1L, null, null, 50L, 1, c.getTime());
+//
+//        IDevice d;
+//        SwitchPort[] aps;
+//        Integer[] ips;
+//
+//        mockListener.deviceAdded(isA(IDevice.class));
+//        replay(mockListener);
+//
+//        deviceManager.learnDeviceByEntity(entity1);
+//        d = deviceManager.learnDeviceByEntity(entity0);
+//        assertEquals(1, deviceManager.getAllDevices().size());
+//        aps = d.getAttachmentPoints();
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) }, aps);
+//        ips = d.getIPv4Addresses();
+//        assertArrayEquals(new Integer[] { 1 }, ips);
+//        verify(mockListener);
+//
+//        reset(mockListener);
+//        mockListener.deviceMoved((isA(IDevice.class)));
+//        replay(mockListener);
+//
+//        d = deviceManager.learnDeviceByEntity(entity2);
+//        assertEquals(1, deviceManager.getAllDevices().size());
+//        aps = d.getAttachmentPoints();
+//
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(5L, 1) }, aps);
+//        ips = d.getIPv4Addresses();
+//        assertArrayEquals(new Integer[] { 1 }, ips);
+//        verify(mockListener);
+//
+//        reset(mockListener);
+//        mockListener.deviceMoved((isA(IDevice.class)));
+//        replay(mockListener);
+//
+//        d = deviceManager.learnDeviceByEntity(entity3);
+//        assertEquals(1, deviceManager.getAllDevices().size());
+//        aps = d.getAttachmentPoints();
+//        assertArrayEquals(new SwitchPort[] {new SwitchPort(5L, 1), new SwitchPort(10L, 1)}, aps);
+//        ips = d.getIPv4Addresses();
+//        assertArrayEquals(new Integer[] { 1 }, ips);
+//        verify(mockListener);
+//
+//        reset(mockListener);
+//        mockListener.deviceMoved((isA(IDevice.class)));
+//        replay(mockListener);
+//
+//        d = deviceManager.learnDeviceByEntity(entity4);
+//        assertEquals(1, deviceManager.getAllDevices().size());
+//        aps = d.getAttachmentPoints();
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(5L, 1),
+//                                             new SwitchPort(50L, 1) }, aps);
+//        ips = d.getIPv4Addresses();
+//        assertArrayEquals(new Integer[] { 1 }, ips);
+//        verify(mockListener);
+//    }
+//
+//    private void verifyEntityArray(Entity[] expected, Device d) {
+//        Arrays.sort(expected);
+//        assertArrayEquals(expected, d.entities);
+//    }
+//
+//    @Test
+//    public void testNoLearningOnInternalPorts() throws Exception {
+//        IDeviceListener mockListener =
+//                createMock(IDeviceListener.class);
+//
+//        expect(mockListener.getName()).andReturn("mockListener").anyTimes();
+//        expect(mockListener.isCallbackOrderingPostreq((String)anyObject(), (String)anyObject()))
+//        .andReturn(false).atLeastOnce();
+//        expect(mockListener.isCallbackOrderingPrereq((String)anyObject(), (String)anyObject()))
+//        .andReturn(false).atLeastOnce();
+//
+//        replay(mockListener);
+//        deviceManager.addListener(mockListener);
+//        verify(mockListener);
+//        reset(mockListener);
+//
+//        ITopologyService mockTopology = createMock(ITopologyService.class);
+//        expect(mockTopology.getL2DomainId(1L)).
+//        andReturn(1L).anyTimes();
+//        expect(mockTopology.getL2DomainId(2L)).
+//        andReturn(1L).anyTimes();
+//        expect(mockTopology.getL2DomainId(3L)).
+//        andReturn(1L).anyTimes();
+//        expect(mockTopology.getL2DomainId(4L)).
+//        andReturn(1L).anyTimes();
+//        expect(mockTopology.isBroadcastDomainPort(anyLong(), anyShort()))
+//                .andReturn(false).anyTimes();
+//        expect(mockTopology.isInSameBroadcastDomain(anyLong(), anyShort(),
+//                                                    anyLong(), anyShort()))
+//                .andReturn(false).anyTimes();
+//
+//        expect(mockTopology.isAttachmentPointPort(or(eq(1L), eq(3L)), anyShort()))
+//                .andReturn(true).anyTimes();
+//        // Switches 2 and 4 have only internal ports
+//        expect(mockTopology.isAttachmentPointPort(or(eq(2L), eq(4L)), anyShort()))
+//                .andReturn(false).anyTimes();
+//
+//        expect(mockTopology.isConsistent(1L, (short)1, 3L, (short)1))
+//                .andReturn(false).once();
+//
+//        Date topologyUpdateTime = new Date();
+//        expect(mockTopology.getLastUpdateTime()).andReturn(topologyUpdateTime).
+//        anyTimes();
+//
+//        replay(mockTopology);
+//
+//        deviceManager.topology = mockTopology;
+//
+//        Calendar c = Calendar.getInstance();
+//        Entity entity1 = new Entity(1L, null, 1, 1L, 1, c.getTime());
+//        c.add(Calendar.SECOND, 1);
+//        Entity entity2 = new Entity(1L, null, 2, 2L, 1, c.getTime());
+//        c.add(Calendar.SECOND, 1);
+//        Entity entity3 = new Entity(1L, null, 3, 3L, 1, c.getTime());
+//        c.add(Calendar.SECOND, 1);
+//        Entity entity4 = new Entity(1L, null, 4, 4L, 1, c.getTime());
+//
+//        IDevice d;
+//        SwitchPort[] aps;
+//        Integer[] ips;
+//
+//        mockListener.deviceAdded(isA(IDevice.class));
+//        expectLastCall().once();
+//        replay(mockListener);
+//
+//        // cannot learn device internal ports
+//        d = deviceManager.learnDeviceByEntity(entity2);
+//        assertNull(d);
+//        d = deviceManager.learnDeviceByEntity(entity4);
+//        assertNull(d);
+//
+//        d = deviceManager.learnDeviceByEntity(entity1);
+//        assertEquals(1, deviceManager.getAllDevices().size());
+//        aps = d.getAttachmentPoints();
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) }, aps);
+//        verifyEntityArray(new Entity[] { entity1 } , (Device)d);
+//        ips = d.getIPv4Addresses();
+//        assertArrayEquals(new Integer[] { 1 }, ips);
+//        verify(mockListener);
+//
+//        reset(mockListener);
+//        replay(mockListener);
+//
+//        // don't learn
+//        d = deviceManager.learnDeviceByEntity(entity2);
+//        assertEquals(1, deviceManager.getAllDevices().size());
+//        aps = d.getAttachmentPoints();
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) }, aps);
+//        verifyEntityArray(new Entity[] { entity1 } , (Device)d);
+//        ips = d.getIPv4Addresses();
+//        assertArrayEquals(new Integer[] { 1 }, ips);
+//        verify(mockListener);
+//
+//        reset(mockListener);
+//        mockListener.deviceMoved(isA(IDevice.class));
+//        mockListener.deviceIPV4AddrChanged(isA(IDevice.class));
+//        replay(mockListener);
+//
+//        // learn
+//        d = deviceManager.learnDeviceByEntity(entity3);
+//        assertEquals(1, deviceManager.getAllDevices().size());
+//        aps = d.getAttachmentPoints();
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(3L, 1) }, aps);
+//        verifyEntityArray(new Entity[] { entity1, entity3 } , (Device)d);
+//        ips = d.getIPv4Addresses();
+//        Arrays.sort(ips);
+//        assertArrayEquals(new Integer[] { 1, 3 }, ips);
+//        verify(mockListener);
+//
+//        reset(mockListener);
+//        replay(mockListener);
+//
+//        // don't learn
+//        d = deviceManager.learnDeviceByEntity(entity4);
+//        assertEquals(1, deviceManager.getAllDevices().size());
+//        aps = d.getAttachmentPoints();
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(3L, 1) }, aps);
+//        verifyEntityArray(new Entity[] { entity1, entity3 } , (Device)d);
+//        ips = d.getIPv4Addresses();
+//        Arrays.sort(ips);
+//        assertArrayEquals(new Integer[] { 1, 3 }, ips);
+//        verify(mockListener);
+//    }
+//
+//    @Test
+//    public void testAttachmentPointSuppression() throws Exception {
+//        IDeviceListener mockListener =
+//                createMock(IDeviceListener.class);
+//
+//        expect(mockListener.getName()).andReturn("mockListener").anyTimes();
+//        expect(mockListener.isCallbackOrderingPostreq((String)anyObject(), (String)anyObject()))
+//        .andReturn(false).atLeastOnce();
+//        expect(mockListener.isCallbackOrderingPrereq((String)anyObject(), (String)anyObject()))
+//        .andReturn(false).atLeastOnce();
+//
+//        replay(mockListener);
+//        deviceManager.addListener(mockListener);
+//        verify(mockListener);
+//        reset(mockListener);
+//
+//        ITopologyService mockTopology = createMock(ITopologyService.class);
+//        expect(mockTopology.getL2DomainId(1L)).
+//        andReturn(1L).anyTimes();
+//        expect(mockTopology.getL2DomainId(5L)).
+//        andReturn(1L).anyTimes();
+//        expect(mockTopology.getL2DomainId(10L)).
+//        andReturn(10L).anyTimes();
+//        expect(mockTopology.getL2DomainId(50L)).
+//        andReturn(10L).anyTimes();
+//        expect(mockTopology.isBroadcastDomainPort(anyLong(), anyShort()))
+//                .andReturn(false).anyTimes();
+//        expect(mockTopology.isInSameBroadcastDomain(anyLong(), anyShort(),
+//                                                    anyLong(), anyShort()))
+//                .andReturn(false).anyTimes();
+//
+//        expect(mockTopology.isAttachmentPointPort(anyLong(), anyShort()))
+//                .andReturn(true).anyTimes();
+//        expect(mockTopology.isConsistent(5L, (short)1, 50L, (short)1))
+//                .andReturn(false).anyTimes();
+//
+//        Date topologyUpdateTime = new Date();
+//        expect(mockTopology.getLastUpdateTime()).andReturn(topologyUpdateTime).
+//        anyTimes();
+//
+//        replay(mockTopology);
+//
+//        deviceManager.topology = mockTopology;
+//        // suppress (1L, 1) and (10L, 1)
+//        deviceManager.addSuppressAPs(1L, (short)1);
+//        deviceManager.addSuppressAPs(10L, (short)1);
+//
+//        Calendar c = Calendar.getInstance();
+//        Entity entity0 = new Entity(1L, null, null, null, null, c.getTime());
+//        // No attachment point should be learnt on 1L, 1
+//        Entity entity1 = new Entity(1L, null, 1, 1L, 1, c.getTime());
+//        c.add(Calendar.SECOND, 1);
+//        Entity entity2 = new Entity(1L, null, 1, 5L, 1, c.getTime());
+//        c.add(Calendar.SECOND, 1);
+//        Entity entity3 = new Entity(1L, null, null, 10L, 1, c.getTime());
+//        c.add(Calendar.SECOND, 1);
+//        Entity entity4 = new Entity(1L, null, null, 50L, 1, c.getTime());
+//
+//        IDevice d;
+//        SwitchPort[] aps;
+//        Integer[] ips;
+//
+//        mockListener.deviceAdded(isA(IDevice.class));
+//        mockListener.deviceIPV4AddrChanged((isA(IDevice.class)));
+//        replay(mockListener);
+//
+//        // TODO: we currently do learn entities on suppressed APs
+//        // // cannot learn device on suppressed AP
+//        // d = deviceManager.learnDeviceByEntity(entity1);
+//        // assertNull(d);
+//
+//        deviceManager.learnDeviceByEntity(entity0);
+//        d = deviceManager.learnDeviceByEntity(entity1);
+//        assertEquals(1, deviceManager.getAllDevices().size());
+//        aps = d.getAttachmentPoints();
+//        assertEquals(aps.length, 0);
+//        verifyEntityArray(new Entity[] { entity0, entity1} , (Device)d);
+//        ips = d.getIPv4Addresses();
+//        assertArrayEquals(new Integer[] { 1 }, ips);
+//        verify(mockListener);
+//
+//        reset(mockListener);
+//        mockListener.deviceMoved((isA(IDevice.class)));
+//        //mockListener.deviceIPV4AddrChanged((isA(IDevice.class)));
+//        replay(mockListener);
+//        d = deviceManager.learnDeviceByEntity(entity2);
+//        assertEquals(1, deviceManager.getAllDevices().size());
+//        aps = d.getAttachmentPoints();
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(5L, 1) }, aps);
+//        verifyEntityArray(new Entity[] { entity0, entity1, entity2 } , (Device)d);
+//        ips = d.getIPv4Addresses();
+//        assertArrayEquals(new Integer[] { 1 }, ips);
+//        verify(mockListener);
+//
+//        reset(mockListener);
+//        replay(mockListener);
+//
+//        d = deviceManager.learnDeviceByEntity(entity3);
+//        assertEquals(1, deviceManager.getAllDevices().size());
+//        aps = d.getAttachmentPoints();
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(5L, 1) }, aps);
+//        verifyEntityArray(new Entity[] { entity0, entity1, entity2, entity3 } , (Device)d);
+//        ips = d.getIPv4Addresses();
+//        assertArrayEquals(new Integer[] { 1 }, ips);
+//        verify(mockListener);
+//
+//        reset(mockListener);
+//        mockListener.deviceMoved((isA(IDevice.class)));
+//        replay(mockListener);
+//
+//        d = deviceManager.learnDeviceByEntity(entity4);
+//        assertEquals(1, deviceManager.getAllDevices().size());
+//        aps = d.getAttachmentPoints();
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(5L, 1),
+//                                             new SwitchPort(50L, 1) }, aps);
+//        verifyEntityArray(new Entity[] { entity0, entity1, entity2, entity3, entity4} , (Device)d);
+//        ips = d.getIPv4Addresses();
+//        assertArrayEquals(new Integer[] { 1 }, ips);
+//        verify(mockListener);
+//    }
+//
+//    @Test
+//    public void testBDAttachmentPointLearning() throws Exception {
+//        ITopologyService mockTopology = createMock(ITopologyService.class);
+//        expect(mockTopology.getL2DomainId(anyLong())).
+//        andReturn(1L).anyTimes();
+//        expect(mockTopology.isAttachmentPointPort(anyLong(), anyShort())).
+//        andReturn(true).anyTimes();
+//        expect(mockTopology.isBroadcastDomainPort(1L, (short)1)).
+//        andReturn(false).anyTimes();
+//        expect(mockTopology.isBroadcastDomainPort(1L, (short)2)).
+//        andReturn(true).anyTimes();
+//        expect(mockTopology.isInSameBroadcastDomain(1L, (short)1,
+//                                                    1L, (short)2)).andReturn(true).anyTimes();
+//        expect(mockTopology.isInSameBroadcastDomain(1L, (short)2,
+//                                                    1L, (short)1)).andReturn(true).anyTimes();
+//        expect(mockTopology.isConsistent(anyLong(), anyShort(), anyLong(), anyShort())).andReturn(false).anyTimes();
+//
+//        Date topologyUpdateTime = new Date();
+//        expect(mockTopology.getLastUpdateTime()).andReturn(topologyUpdateTime).
+//        anyTimes();
+//
+//        replay(mockTopology);
+//
+//        deviceManager.topology = mockTopology;
+//
+//        Calendar c = Calendar.getInstance();
+//        Entity entity1 = new Entity(1L, null, 1, 1L, 1, c.getTime());
+//        c.add(Calendar.MILLISECOND,
+//              (int)AttachmentPoint.OPENFLOW_TO_EXTERNAL_TIMEOUT/ 2);
+//        Entity entity2 = new Entity(1L, null, null, 1L, 2, c.getTime());
+//        c.add(Calendar.MILLISECOND,
+//              (int)AttachmentPoint.OPENFLOW_TO_EXTERNAL_TIMEOUT / 2 + 1);
+//        Entity entity3 = new Entity(1L, null, null, 1L, 2, c.getTime());
+//
+//        IDevice d;
+//        SwitchPort[] aps;
+//
+//        d = deviceManager.learnDeviceByEntity(entity1);
+//        assertEquals(1, deviceManager.getAllDevices().size());
+//        aps = d.getAttachmentPoints();
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) }, aps);
+//
+//        // this timestamp is too soon; don't switch
+//        d = deviceManager.learnDeviceByEntity(entity2);
+//        assertEquals(1, deviceManager.getAllDevices().size());
+//        aps = d.getAttachmentPoints();
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) }, aps);
+//
+//        // it should switch when we learn with a timestamp after the
+//        // timeout
+//        d = deviceManager.learnDeviceByEntity(entity3);
+//        assertEquals(1, deviceManager.getAllDevices().size());
+//        aps = d.getAttachmentPoints();
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 2) }, aps);
+//    }
+//
+//    /**
+//     * This test verifies that the learning behavior on OFPP_LOCAL ports.
+//     * Once a host is learned on OFPP_LOCAL, it is allowed to move only from
+//     * one OFPP_LOCAL to another OFPP_LOCAL port.
+//     * @throws Exception
+//     */
+//    @Test
+//    public void testLOCALAttachmentPointLearning() throws Exception {
+//        ITopologyService mockTopology = createMock(ITopologyService.class);
+//        expect(mockTopology.getL2DomainId(anyLong())).
+//        andReturn(1L).anyTimes();
+//        expect(mockTopology.isAttachmentPointPort(anyLong(), anyShort())).
+//        andReturn(true).anyTimes();
+//        expect(mockTopology.isBroadcastDomainPort(1L, (short)1)).
+//        andReturn(false).anyTimes();
+//        expect(mockTopology.isBroadcastDomainPort(1L, OFPort.OFPP_LOCAL.getValue())).
+//        andReturn(false).anyTimes();
+//        expect(mockTopology.isBroadcastDomainPort(1L, (short)2)).
+//        andReturn(true).anyTimes();
+//        expect(mockTopology.isInSameBroadcastDomain(1L, (short)1,
+//                                                    1L, OFPort.OFPP_LOCAL.getValue())).andReturn(true).anyTimes();
+//        expect(mockTopology.isInSameBroadcastDomain(1L, OFPort.OFPP_LOCAL.getValue(),
+//                                                    1L, (short)2)).andReturn(true).anyTimes();
+//        expect(mockTopology.isInSameBroadcastDomain(1L, (short)2,
+//                                                    1L, OFPort.OFPP_LOCAL.getValue())).andReturn(true).anyTimes();
+//        expect(mockTopology.isConsistent(anyLong(), anyShort(), anyLong(), anyShort())).andReturn(false).anyTimes();
+//
+//        Date topologyUpdateTime = new Date();
+//        expect(mockTopology.getLastUpdateTime()).andReturn(topologyUpdateTime).
+//        anyTimes();
+//
+//        replay(mockTopology);
+//
+//        deviceManager.topology = mockTopology;
+//
+//        Calendar c = Calendar.getInstance();
+//        Entity entity1 = new Entity(1L, null, 1, 1L, 1, c.getTime());
+//        c.add(Calendar.MILLISECOND,
+//              (int)AttachmentPoint.OPENFLOW_TO_EXTERNAL_TIMEOUT/ 2);
+//        Entity entity2 = new Entity(1L, null, null, 1L, (int)OFPort.OFPP_LOCAL.getValue(), c.getTime());
+//        c.add(Calendar.MILLISECOND,
+//              (int)AttachmentPoint.OPENFLOW_TO_EXTERNAL_TIMEOUT + 1);
+//        Entity entity3 = new Entity(1L, null, null, 1L, 2, c.getTime());
+//
+//        IDevice d;
+//        SwitchPort[] aps;
+//
+//        d = deviceManager.learnDeviceByEntity(entity1);
+//        assertEquals(1, deviceManager.getAllDevices().size());
+//        aps = d.getAttachmentPoints();
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) }, aps);
+//
+//        // Ensure that the attachment point changes to OFPP_LOCAL
+//        d = deviceManager.learnDeviceByEntity(entity2);
+//        assertEquals(1, deviceManager.getAllDevices().size());
+//        aps = d.getAttachmentPoints();
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, OFPort.OFPP_LOCAL.getValue()) }, aps);
+//
+//        // Even though the new attachment point is consistent with old
+//        // and the time has elapsed, OFPP_LOCAL attachment point should
+//        // be maintained.
+//        d = deviceManager.learnDeviceByEntity(entity3);
+//        assertEquals(1, deviceManager.getAllDevices().size());
+//        aps = d.getAttachmentPoints();
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, OFPort.OFPP_LOCAL.getValue()) }, aps);
+//    }
+//
+//    @Test
+//    public void testPacketInBasic(byte[] deviceMac, OFPacketIn packetIn) {
+//        // Mock up our expected behavior
+//        ITopologyService mockTopology = createMock(ITopologyService.class);
+//        deviceManager.topology = mockTopology;
+//        expect(mockTopology.isAttachmentPointPort(EasyMock.anyLong(),
+//                EasyMock.anyShort())).
+//                andReturn(true).anyTimes();
+//        expect(mockTopology.isConsistent(EasyMock.anyLong(),
+//                EasyMock.anyShort(),
+//                EasyMock.anyLong(),
+//                EasyMock.anyShort())).andReturn(false).
+//                anyTimes();
+//        expect(mockTopology.getL2DomainId(EasyMock.anyLong())).andReturn(1L).anyTimes();
+//        replay(mockTopology);
+//
+//        Date currentDate = new Date();
+//
+//        // build our expected Device
+//        Integer ipaddr = IPv4.toIPv4Address("192.168.1.1");
+//        Device device =
+//                new Device(deviceManager,
+//                        new Long(deviceManager.deviceKeyCounter),
+//                        new Entity(Ethernet.toLong(deviceMac),
+//                                (short)5,
+//                                ipaddr,
+//                                1L,
+//                                1,
+//                                currentDate),
+//                                DefaultEntityClassifier.entityClass);
+//
+//        // Get the listener and trigger the packet in
+//        IOFSwitch switch1 = mockFloodlightProvider.getSwitch(1L);
+//        mockFloodlightProvider.dispatchMessage(switch1, packetIn);
+//
+//        // Verify the replay matched our expectations
+//        // verify(mockTopology);
+//
+//        // Verify the device
+//        Device rdevice = (Device)
+//                deviceManager.findDevice(Ethernet.toLong(deviceMac),
+//                        (short)5, null, null, null);
+//
+//        assertEquals(device, rdevice);
+//        assertEquals(new Short((short)5), rdevice.getVlanId()[0]);
+//
+//        Device result = null;
+//        Iterator<? extends IDevice> dstiter =
+//                deviceManager.queryClassDevices(device.getEntityClass(),
+//                        null, null, ipaddr,
+//                        null, null);
+//        if (dstiter.hasNext()) {
+//            result = (Device)dstiter.next();
+//        }
+//
+//        assertEquals(device, result);
+//
+//        device =
+//                new Device(device,
+//                        new Entity(Ethernet.toLong(deviceMac),
+//                                (short)5,
+//                                ipaddr,
+//                                5L,
+//                                2,
+//                                currentDate),
+//                                -1);
+//
+//        reset(mockTopology);
+//        expect(mockTopology.isAttachmentPointPort(anyLong(),
+//                anyShort())).
+//                andReturn(true).
+//                anyTimes();
+//        expect(mockTopology.isConsistent(EasyMock.anyLong(),
+//                EasyMock.anyShort(),
+//                EasyMock.anyLong(),
+//                EasyMock.anyShort())).andReturn(false).
+//                anyTimes();
+//        expect(mockTopology.isBroadcastDomainPort(EasyMock.anyLong(),
+//                EasyMock.anyShort()))
+//                .andReturn(false)
+//                .anyTimes();
+//        expect(mockTopology.getL2DomainId(1L)).andReturn(1L).anyTimes();
+//        expect(mockTopology.getL2DomainId(5L)).andReturn(1L).anyTimes();
+//        expect(mockTopology.isInSameBroadcastDomain(1L, (short)1, 5L, (short)2)).
+//        andReturn(false).anyTimes();
+//
+//        // Start recording the replay on the mocks
+//        replay(mockTopology);
+//        // Get the listener and trigger the packet in
+//        IOFSwitch switch5 = mockFloodlightProvider.getSwitch(5L);
+//        mockFloodlightProvider.
+//        dispatchMessage(switch5, this.packetIn_1.setInPort((short)2));
+//
+//        // Verify the replay matched our expectations
+//        verify(mockTopology);
+//
+//        // Verify the device
+//        rdevice = (Device)
+//                deviceManager.findDevice(Ethernet.toLong(deviceMac),
+//                        (short)5, null, null, null);
+//        assertEquals(device, rdevice);
+//    }
+//
+//    @Test
+//    public void testPacketIn() throws Exception {
+//        byte[] deviceMac1 =
+//                ((Ethernet)this.testARPReplyPacket_1).getSourceMACAddress();
+//        testPacketInBasic(deviceMac1, packetIn_1);
+//    }
+//
+//    /**
+//     * This test ensures the device manager learns the source device
+//     * corresponding to the senderHardwareAddress and senderProtocolAddress
+//     * in an ARP response whenever the senderHardwareAddress is different
+//     * from the source MAC address of the Ethernet frame.
+//     *
+//     * This test is the same as testPacketIn method, except for the
+//     * packet-in that's used.
+//     * @throws Exception
+//     */
+//    @Test
+//    public void testDeviceLearningFromArpResponseData() throws Exception {
+//        ARP arp = (ARP)((Ethernet)this.testARPReplyPacket_2).getPayload();
+//        byte[] deviceMac2 = arp.getSenderHardwareAddress();
+//
+//        testPacketInBasic(deviceMac2, packetIn_2);
+//    }
+//
+//    /**
+//     * Note: Entity expiration does not result in device moved notification.
+//     * @throws Exception
+//     */
+//    public void doTestEntityExpiration() throws Exception {
+//        IDeviceListener mockListener =
+//                createMock(IDeviceListener.class);
+//        expect(mockListener.getName()).andReturn("mockListener").anyTimes();
+//        expect(mockListener.isCallbackOrderingPostreq((String)anyObject(), (String)anyObject()))
+//        .andReturn(false).atLeastOnce();
+//        expect(mockListener.isCallbackOrderingPrereq((String)anyObject(), (String)anyObject()))
+//        .andReturn(false).atLeastOnce();
+//
+//        ITopologyService mockTopology = createMock(ITopologyService.class);
+//        expect(mockTopology.isAttachmentPointPort(anyLong(),
+//                                                  anyShort())).
+//                                                  andReturn(true).anyTimes();
+//
+//        expect(mockTopology.isBroadcastDomainPort(1L, (short)1)).andReturn(false).anyTimes();
+//        expect(mockTopology.isBroadcastDomainPort(5L, (short)1)).andReturn(false).anyTimes();
+//        expect(mockTopology.getL2DomainId(1L)).andReturn(1L).anyTimes();
+//        expect(mockTopology.getL2DomainId(5L)).andReturn(5L).anyTimes();
+//        expect(mockTopology.isConsistent(1L, (short)1, 5L, (short)1)).
+//        andReturn(false).anyTimes();
+//
+//        Date topologyUpdateTime = new Date();
+//        expect(mockTopology.getLastUpdateTime()).andReturn(topologyUpdateTime).
+//        anyTimes();
+//
+//        replay(mockTopology);
+//        deviceManager.topology = mockTopology;
+//
+//        Calendar c = Calendar.getInstance();
+//        Entity entity1 = new Entity(1L, null, 2, 1L, 1, c.getTime());
+//        c.add(Calendar.MILLISECOND, -DeviceManagerImpl.ENTITY_TIMEOUT-1);
+//        Entity entity2 = new Entity(1L, null, 1, 5L, 1, c.getTime());
+//
+//        deviceManager.learnDeviceByEntity(entity1);
+//        IDevice d = deviceManager.learnDeviceByEntity(entity2);
+//        assertArrayEquals(new Integer[] { 1, 2 }, d.getIPv4Addresses());
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+//                                             new SwitchPort(5L, 1)},
+//                                             d.getAttachmentPoints());
+//        Iterator<? extends IDevice> diter =
+//                deviceManager.queryClassDevices(d.getEntityClass(),
+//                                                null, null, 1, null, null);
+//        assertTrue(diter.hasNext());
+//        assertEquals(d.getDeviceKey(), diter.next().getDeviceKey());
+//        diter = deviceManager.queryClassDevices(d.getEntityClass(),
+//                                                null, null, 2, null, null);
+//        assertTrue(diter.hasNext());
+//        assertEquals(d.getDeviceKey(), diter.next().getDeviceKey());
+//
+//        replay(mockListener);
+//        deviceManager.addListener(mockListener);
+//        verify(mockListener);
+//        reset(mockListener);
+//
+//        mockListener.deviceIPV4AddrChanged(isA(IDevice.class));
+//        replay(mockListener);
+//        deviceManager.entityCleanupTask.reschedule(0, null);
+//
+//        d = deviceManager.getDevice(d.getDeviceKey());
+//        assertArrayEquals(new Integer[] { 2 }, d.getIPv4Addresses());
+//
+//        // Attachment points are not removed, previous ones are still valid.
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+//                                             new SwitchPort(5L, 1) },
+//                          d.getAttachmentPoints());
+//        diter = deviceManager.queryClassDevices(d.getEntityClass(),
+//                                                null, null, 2, null, null);
+//        assertTrue(diter.hasNext());
+//        assertEquals(d.getDeviceKey(), diter.next().getDeviceKey());
+//        diter = deviceManager.queryClassDevices(d.getEntityClass(),
+//                                                null, null, 1, null, null);
+//        assertFalse(diter.hasNext());
+//
+//        d = deviceManager.findDevice(1L, null, null, null, null);
+//        assertArrayEquals(new Integer[] { 2 }, d.getIPv4Addresses());
+//
+//        // Attachment points are not removed, previous ones are still valid.
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+//                                             new SwitchPort(5L, 1) },
+//                          d.getAttachmentPoints());
+//
+//        verify(mockListener);
+//    }
+//
+//    public void doTestDeviceExpiration() throws Exception {
+//        IDeviceListener mockListener =
+//                createMock(IDeviceListener.class);
+//        expect(mockListener.getName()).andReturn("mockListener").anyTimes();
+//        expect(mockListener.isCallbackOrderingPostreq((String)anyObject(), (String)anyObject()))
+//        .andReturn(false).atLeastOnce();
+//        expect(mockListener.isCallbackOrderingPrereq((String)anyObject(), (String)anyObject()))
+//        .andReturn(false).atLeastOnce();
+//
+//        Calendar c = Calendar.getInstance();
+//        c.add(Calendar.MILLISECOND, -DeviceManagerImpl.ENTITY_TIMEOUT-1);
+//        Entity entity1 = new Entity(1L, null, 1, 1L, 1, c.getTime());
+//        Entity entity2 = new Entity(1L, null, 2, 5L, 1, c.getTime());
+//
+//        ITopologyService mockTopology = createMock(ITopologyService.class);
+//        deviceManager.topology = mockTopology;
+//
+//        expect(mockTopology.isAttachmentPointPort(EasyMock.anyLong(),
+//                                           EasyMock.anyShort())).
+//                                           andReturn(true).
+//                                           anyTimes();
+//        expect(mockTopology.getL2DomainId(1L)).andReturn(1L).anyTimes();
+//        expect(mockTopology.getL2DomainId(5L)).andReturn(1L).anyTimes();
+//        expect(mockTopology.isConsistent(EasyMock.anyLong(),
+//                                         EasyMock.anyShort(),
+//                                         EasyMock.anyLong(),
+//                                         EasyMock.anyShort())).andReturn(false).
+//                                         anyTimes();
+//        expect(mockTopology.isBroadcastDomainPort(EasyMock.anyLong(),
+//                                                  EasyMock.anyShort())).
+//                                                  andReturn(false).anyTimes();
+//        replay(mockTopology);
+//
+//        IDevice d = deviceManager.learnDeviceByEntity(entity2);
+//        d = deviceManager.learnDeviceByEntity(entity1);
+//        assertArrayEquals(new Integer[] { 1, 2 }, d.getIPv4Addresses());
+//
+//        replay(mockListener);
+//        deviceManager.addListener(mockListener);
+//        verify(mockListener);
+//        reset(mockListener);
+//
+//        mockListener.deviceRemoved(isA(IDevice.class));
+//        replay(mockListener);
+//        deviceManager.entityCleanupTask.reschedule(0, null);
+//
+//        IDevice r = deviceManager.getDevice(d.getDeviceKey());
+//        assertNull(r);
+//        Iterator<? extends IDevice> diter =
+//                deviceManager.queryClassDevices(d.getEntityClass(),
+//                                                null, null, 1, null, null);
+//        assertFalse(diter.hasNext());
+//
+//        r = deviceManager.findDevice(1L, null, null, null, null);
+//        assertNull(r);
+//
+//        verify(mockListener);
+//    }
+//
+//    /*
+//     * A ConcurrentHashMap for devices (deviceMap) that can be used to test
+//     * code that specially handles concurrent modification situations. In
+//     * particular, we overwrite values() and will replace / remove all the
+//     * elements returned by values.
+//     *
+//     * The remove flag in the constructor specifies if devices returned by
+//     * values() should be removed or replaced.
+//     */
+//    protected static class ConcurrentlyModifiedDeviceMap
+//                            extends ConcurrentHashMap<Long, Device> {
+//        private static final long serialVersionUID = 7784938535441180562L;
+//        protected boolean remove;
+//        public ConcurrentlyModifiedDeviceMap(boolean remove) {
+//            super();
+//            this.remove = remove;
+//        }
+//
+//        @Override
+//        public Collection<Device> values() {
+//            // Get the values from the real map and copy them since
+//            // the collection returned by values can reflect changed
+//            Collection<Device> devs = new ArrayList<Device>(super.values());
+//            for (Device d: devs) {
+//                if (remove) {
+//                    // We remove the device from the underlying map
+//                    super.remove(d.getDeviceKey());
+//                } else {
+//                    super.remove(d.getDeviceKey());
+//                    // We add a different Device instance with the same
+//                    // key to the map. We'll do some hackery so the device
+//                    // is different enough to compare differently in equals
+//                    // but otherwise looks the same.
+//                    // It's ugly but it works.
+//                    // clone entities
+//                    Device newDevice = d;
+//                    for (Entity e: d.getEntities()) {
+//                        Entity newEntity = new Entity (e.macAddress,
+//                                                       e.vlan,
+//                                                       e.ipv4Address,
+//                                                       e.switchDPID,
+//                                                       e.switchPort,
+//                                                       e.lastSeenTimestamp);
+//                        if (e.vlan == null)
+//                            newEntity.vlan = (short)1;
+//                        else
+//                             newEntity.vlan = (short)((e.vlan + 1 % 4095)+1);
+//                        newDevice = new Device(newDevice, newEntity, -1);
+//                    }
+//                    assertEquals(false, newDevice.equals(d));
+//                    super.put(newDevice.getDeviceKey(), newDevice);
+//                }
+//            }
+//            return devs;
+//        }
+//    }
+//
+//    @Test
+//    public void testEntityExpiration() throws Exception {
+//        doTestEntityExpiration();
+//    }
+//
+//    @Test
+//    public void testDeviceExpiration() throws Exception {
+//        doTestDeviceExpiration();
+//    }
+//
+//    /* Test correct entity cleanup behavior when a concurrent modification
+//     * occurs.
+//     */
+//    @Test
+//    public void testEntityExpirationConcurrentModification() throws Exception {
+//        deviceManager.deviceMap = new ConcurrentlyModifiedDeviceMap(false);
+//        doTestEntityExpiration();
+//    }
+//
+//    /* Test correct entity cleanup behavior when a concurrent remove
+//     * occurs.
+//     */
+//    @Test
+//    public void testDeviceExpirationConcurrentRemove() throws Exception {
+//        deviceManager.deviceMap = new ConcurrentlyModifiedDeviceMap(true);
+//        doTestDeviceExpiration();
+//    }
+//
+//    /* Test correct entity cleanup behavior when a concurrent modification
+//     * occurs.
+//     */
+//    @Test
+//    public void testDeviceExpirationConcurrentModification() throws Exception {
+//        deviceManager.deviceMap = new ConcurrentlyModifiedDeviceMap(false);
+//        doTestDeviceExpiration();
+//    }
+//
+//
+//    @Test
+//    public void testAttachmentPointFlapping() throws Exception {
+//        Calendar c = Calendar.getInstance();
+//
+//        ITopologyService mockTopology = createMock(ITopologyService.class);
+//        expect(mockTopology.isAttachmentPointPort(anyLong(),
+//                                                  anyShort())).andReturn(true).anyTimes();
+//        expect(mockTopology.isBroadcastDomainPort(anyLong(),
+//                                                  anyShort())).
+//                                                  andReturn(false).anyTimes();
+//        expect(mockTopology.isInSameBroadcastDomain(anyLong(), anyShort(),
+//                                                    anyLong(), anyShort())).andReturn(false).anyTimes();
+//        expect(mockTopology.getL2DomainId(anyLong())).
+//        andReturn(1L).anyTimes();
+//        expect(mockTopology.isConsistent(1L, (short)1, 1L, (short)1)).
+//        andReturn(true).anyTimes();
+//        expect(mockTopology.isConsistent(1L, (short)1, 5L, (short)1)).
+//        andReturn(false).anyTimes();
+//        expect(mockTopology.isConsistent(1L, (short)1, 10L, (short)1)).
+//        andReturn(false).anyTimes();
+//        expect(mockTopology.isConsistent(5L, (short)1, 10L, (short)1)).
+//        andReturn(false).anyTimes();
+//        expect(mockTopology.isConsistent(10L, (short)1, 1L, (short)1)).
+//        andReturn(false).anyTimes();
+//        expect(mockTopology.isConsistent(5L, (short)1, 1L, (short)1)).
+//        andReturn(false).anyTimes();
+//        expect(mockTopology.isConsistent(10L, (short)1, 5L, (short)1)).
+//        andReturn(false).anyTimes();
+//
+//        Date topologyUpdateTime = new Date();
+//        expect(mockTopology.getLastUpdateTime()).andReturn(topologyUpdateTime).
+//        anyTimes();
+//
+//
+//        replay(mockTopology);
+//        deviceManager.topology = mockTopology;
+//
+//        Entity entity1 = new Entity(1L, null, null, 1L, 1, c.getTime());
+//        Entity entity1a = new Entity(1L, null, 1, 1L, 1, c.getTime());
+//        Entity entity2 = new Entity(1L, null, null, 5L, 1, c.getTime());
+//        Entity entity3 = new Entity(1L, null, null, 10L, 1, c.getTime());
+//        entity1.setLastSeenTimestamp(c.getTime());
+//        c.add(Calendar.MILLISECOND, Entity.ACTIVITY_TIMEOUT/2);
+//        entity1a.setLastSeenTimestamp(c.getTime());
+//        c.add(Calendar.MILLISECOND, 1);
+//        entity2.setLastSeenTimestamp(c.getTime());
+//        c.add(Calendar.MILLISECOND, 1);
+//        entity3.setLastSeenTimestamp(c.getTime());
+//
+//
+//
+//        IDevice d;
+//        d = deviceManager.learnDeviceByEntity(entity1);
+//        d = deviceManager.learnDeviceByEntity(entity1a);
+//        d = deviceManager.learnDeviceByEntity(entity2);
+//        d = deviceManager.learnDeviceByEntity(entity3);
+//
+//        // all entities are active, so entity3 should win
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(10L, 1) },
+//                          d.getAttachmentPoints());
+//
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(10L, 1),},
+//                              d.getAttachmentPoints(true));
+//
+//        c.add(Calendar.MILLISECOND, Entity.ACTIVITY_TIMEOUT/4);
+//        entity1.setLastSeenTimestamp(c.getTime());
+//        d = deviceManager.learnDeviceByEntity(entity1);
+//
+//        // all are still active; entity3 should still win
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) },
+//                          d.getAttachmentPoints());
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+//                                             new SwitchPort(5L, 1,
+//                                                            ErrorStatus.DUPLICATE_DEVICE),
+//                                                            new SwitchPort(10L, 1,
+//                                                                           ErrorStatus.DUPLICATE_DEVICE) },
+//                                                                           d.getAttachmentPoints(true));
+//
+//        c.add(Calendar.MILLISECOND, Entity.ACTIVITY_TIMEOUT+2000);
+//        entity1.setLastSeenTimestamp(c.getTime());
+//        d = deviceManager.learnDeviceByEntity(entity1);
+//
+//        assertEquals(entity1.getActiveSince(), entity1.getLastSeenTimestamp());
+//        // entity1 should now be the only active entity
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) },
+//                          d.getAttachmentPoints());
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1) },
+//                          d.getAttachmentPoints(true));
+//    }
+//
+//
+//    @Test
+//    public void testAttachmentPointFlappingTwoCluster() throws Exception {
+//        Calendar c = Calendar.getInstance();
+//
+//        ITopologyService mockTopology = createMock(ITopologyService.class);
+//        expect(mockTopology.isAttachmentPointPort(anyLong(),
+//                                                  anyShort())).andReturn(true).anyTimes();
+//        expect(mockTopology.isBroadcastDomainPort(anyLong(),
+//                                                  anyShort())).
+//                                                  andReturn(false).anyTimes();
+//        expect(mockTopology.isInSameBroadcastDomain(anyLong(), anyShort(),
+//                                                    anyLong(), anyShort())).andReturn(false).anyTimes();
+//        expect(mockTopology.getL2DomainId(1L)).
+//        andReturn(1L).anyTimes();
+//        expect(mockTopology.getL2DomainId(5L)).
+//        andReturn(5L).anyTimes();
+//        expect(mockTopology.isConsistent(1L, (short)1, 1L, (short)2)).
+//        andReturn(false).anyTimes();
+//        expect(mockTopology.isConsistent(1L, (short)2, 5L, (short)1)).
+//        andReturn(false).anyTimes();
+//        expect(mockTopology.isConsistent(5L, (short)1, 5L, (short)2)).
+//        andReturn(false).anyTimes();
+//        expect(mockTopology.isConsistent(1L, (short)2, 1L, (short)1)).
+//        andReturn(false).anyTimes();
+//        expect(mockTopology.isConsistent(1L, (short)1, 5L, (short)1)).
+//        andReturn(false).anyTimes();
+//        expect(mockTopology.isConsistent(1L, (short)1, 5L, (short)2)).
+//        andReturn(false).anyTimes();
+//        expect(mockTopology.isConsistent(5L, (short)2, 5L, (short)1)).
+//        andReturn(false).anyTimes();
+//
+//        Date topologyUpdateTime = new Date();
+//        expect(mockTopology.getLastUpdateTime()).andReturn(topologyUpdateTime).
+//        anyTimes();
+//
+//        replay(mockTopology);
+//        deviceManager.topology = mockTopology;
+//
+//        Entity entity1 = new Entity(1L, null, null, 1L, 1, c.getTime());
+//        Entity entity2 = new Entity(1L, null, null, 1L, 2, c.getTime());
+//        Entity entity3 = new Entity(1L, null, null, 5L, 1, c.getTime());
+//        Entity entity4 = new Entity(1L, null, null, 5L, 2, c.getTime());
+//        entity1.setLastSeenTimestamp(c.getTime());
+//        c.add(Calendar.MILLISECOND, Entity.ACTIVITY_TIMEOUT/2);
+//        c.add(Calendar.MILLISECOND, 1);
+//        entity2.setLastSeenTimestamp(c.getTime());
+//        c.add(Calendar.MILLISECOND, 1);
+//        entity3.setLastSeenTimestamp(c.getTime());
+//        c.add(Calendar.MILLISECOND, 1);
+//        entity4.setLastSeenTimestamp(c.getTime());
+//
+//        deviceManager.learnDeviceByEntity(entity1);
+//        deviceManager.learnDeviceByEntity(entity2);
+//        deviceManager.learnDeviceByEntity(entity3);
+//        IDevice d = deviceManager.learnDeviceByEntity(entity4);
+//
+//        // all entities are active, so entities 2,4 should win
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 2),
+//                                             new SwitchPort(5L, 2) },
+//                                             d.getAttachmentPoints());
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 2),
+//                                             new SwitchPort(5L, 2)},
+//                                             d.getAttachmentPoints(true));
+//
+//        c.add(Calendar.MILLISECOND, 1);
+//        entity1.setLastSeenTimestamp(c.getTime());
+//        d = deviceManager.learnDeviceByEntity(entity1);
+//
+//        // all entities are active, so entities 2,4 should win
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+//                                             new SwitchPort(5L, 2) },
+//                                             d.getAttachmentPoints());
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+//                                             new SwitchPort(5L, 2),
+//                                             new SwitchPort(1L, 2, ErrorStatus.DUPLICATE_DEVICE)},
+//                                             d.getAttachmentPoints(true));
+//
+//        c.add(Calendar.MILLISECOND, Entity.ACTIVITY_TIMEOUT+1);
+//        entity1.setLastSeenTimestamp(c.getTime());
+//        d = deviceManager.learnDeviceByEntity(entity1);
+//
+//        // entities 3,4 are still in conflict, but 1 should be resolved
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+//                                             new SwitchPort(5L, 2) },
+//                                             d.getAttachmentPoints());
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+//                                             new SwitchPort(5L, 2)},
+//                                             d.getAttachmentPoints(true));
+//
+//        entity3.setLastSeenTimestamp(c.getTime());
+//        d = deviceManager.learnDeviceByEntity(entity3);
+//
+//        // no conflicts, 1 and 3 will win
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+//                                             new SwitchPort(5L, 1) },
+//                                             d.getAttachmentPoints());
+//        assertArrayEquals(new SwitchPort[] { new SwitchPort(1L, 1),
+//                                             new SwitchPort(5L, 1) },
+//                                             d.getAttachmentPoints(true));
+//
+//    }
+//
+//    protected void doTestDeviceQuery() throws Exception {
+//        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 entity3 = new Entity(3L, (short)3, 3, 5L, 1, new Date());
+//        Entity entity4 = new Entity(4L, (short)4, 3, 5L, 2, new Date());
+//        Entity entity5 = new Entity(1L, (short)4, 3, 5L, 2, new Date());
+//
+//        Device d1 = deviceManager.learnDeviceByEntity(entity1);
+//        deviceManager.learnDeviceByEntity(entity2);
+//        Device d3 = deviceManager.learnDeviceByEntity(entity3);
+//        Device d4 = deviceManager.learnDeviceByEntity(entity4);
+//
+//        IDevice d;
+//
+//        Iterator<? extends IDevice> iter =
+//                deviceManager.queryDevices(null, (short)1, 1, null, null);
+//        int count = 0;
+//        while (iter.hasNext()) {
+//            count += 1;
+//            d = iter.next();
+//            assertEquals(d1.getDeviceKey(), d.getDeviceKey());
+//        }
+//        assertEquals(1, count);
+//
+//        iter = deviceManager.queryDevices(null, (short)3, 3, null, null);
+//        count = 0;
+//        while (iter.hasNext()) {
+//            count += 1;
+//            d = iter.next();
+//            assertEquals(d3.getDeviceKey(), d.getDeviceKey());
+//        }
+//        assertEquals(1, count);
+//
+//        iter = deviceManager.queryDevices(null, (short)1, 3, null, null);
+//        count = 0;
+//        while (iter.hasNext()) {
+//            count += 1;
+//            iter.next();
+//        }
+//        assertEquals(0, count);
+//
+//        Device d5 = deviceManager.learnDeviceByEntity(entity5);
+//        iter = deviceManager.queryDevices(null, (short)4, 3, null, null);
+//        count = 0;
+//        Set<Long> deviceKeysFromIterator = new HashSet<Long>();
+//        while (iter.hasNext()) {
+//            count += 1;
+//            d = iter.next();
+//            deviceKeysFromIterator.add(d.getDeviceKey());
+//        }
+//        Set<Long> expectedDeviceKeys = new HashSet<Long>();
+//        expectedDeviceKeys.add(d4.getDeviceKey());
+//        expectedDeviceKeys.add(d5.getDeviceKey());
+//        assertEquals(expectedDeviceKeys, deviceKeysFromIterator);
+//        assertEquals(2, count);
+//
+//
+//        iter = deviceManager.queryDevices(1L, null, null, null, null);
+//        count = 0;
+//        deviceKeysFromIterator = new HashSet<Long>();
+//        while (iter.hasNext()) {
+//            count += 1;
+//            d = iter.next();
+//            deviceKeysFromIterator.add(d.getDeviceKey());
+//        }
+//        expectedDeviceKeys = new HashSet<Long>();
+//        expectedDeviceKeys.add(d1.getDeviceKey());
+//        expectedDeviceKeys.add(d5.getDeviceKey());
+//        assertEquals(expectedDeviceKeys, deviceKeysFromIterator);
+//        assertEquals(2, count);
+//    }
+//
+//    @Test
+//    public void testDeviceIndex() throws Exception {
+//        EnumSet<IDeviceService.DeviceField> indexFields =
+//                EnumSet.noneOf(IDeviceService.DeviceField.class);
+//        indexFields.add(IDeviceService.DeviceField.IPV4);
+//        indexFields.add(IDeviceService.DeviceField.VLAN);
+//        deviceManager.addIndex(false, indexFields);
+//
+//        indexFields = EnumSet.noneOf(IDeviceService.DeviceField.class);
+//        deviceManager.addIndex(false, indexFields);
+//
+//        ITopologyService mockTopology = createMock(ITopologyService.class);
+//        deviceManager.topology = mockTopology;
+//        expect(mockTopology.isAttachmentPointPort(anyLong(),
+//                                                  anyShort())).
+//                                                  andReturn(true).anyTimes();
+//        expect(mockTopology.getL2DomainId(EasyMock.anyLong())).andReturn(1L).anyTimes();
+//        replay(mockTopology);
+//        doTestDeviceQuery();
+//    }
+//
+//    @Test
+//    public void testDeviceQuery() throws Exception {
+//        ITopologyService mockTopology = createMock(ITopologyService.class);
+//        deviceManager.topology = mockTopology;
+//        expect(mockTopology.isAttachmentPointPort(anyLong(),
+//                                                  anyShort())).
+//                                                  andReturn(true).anyTimes();
+//        expect(mockTopology.getL2DomainId(EasyMock.anyLong())).andReturn(1L).anyTimes();
+//        replay(mockTopology);
+//
+//        doTestDeviceQuery();
+//    }
+//
+//    protected void doTestDeviceClassQuery() throws Exception {
+//        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 entity3 = new Entity(3L, (short)3, 3, 5L, 1, new Date());
+//        Entity entity4 = new Entity(4L, (short)4, 3, 5L, 2, new Date());
+//        Entity entity5 = new Entity(1L, (short)4, 3, 5L, 2, new Date());
+//
+//        IDevice d1 = deviceManager.learnDeviceByEntity(entity1);
+//        IDevice d2 = deviceManager.learnDeviceByEntity(entity2);
+//        IDevice d3 = deviceManager.learnDeviceByEntity(entity3);
+//        IDevice d4 = deviceManager.learnDeviceByEntity(entity4);
+//        assertEquals(d1.getEntityClass(), d2.getEntityClass());
+//        assertEquals(d1.getEntityClass(), d3.getEntityClass());
+//        assertEquals(d1.getEntityClass(), d4.getEntityClass());
+//
+//        IDevice d;
+//
+//        Iterator<? extends IDevice> iter =
+//                deviceManager.queryClassDevices(d1.getEntityClass(), null,
+//                                                (short)1, 1, null, null);
+//        int count = 0;
+//        while (iter.hasNext()) {
+//            count += 1;
+//            d = iter.next();
+//            assertEquals(d1.getDeviceKey(), d.getDeviceKey());
+//        }
+//        assertEquals(1, count);
+//
+//        iter = deviceManager.queryClassDevices(d1.getEntityClass(), null,
+//                                               (short)3, 3, null, null);
+//        count = 0;
+//        while (iter.hasNext()) {
+//            count += 1;
+//            d = iter.next();
+//            assertEquals(d3.getDeviceKey(), d.getDeviceKey());
+//
+//        }
+//        assertEquals(1, count);
+//
+//        iter = deviceManager.queryClassDevices(d1.getEntityClass(), null,
+//                                               (short)1, 3, null, null);
+//        count = 0;
+//        while (iter.hasNext()) {
+//            count += 1;
+//            iter.next();
+//        }
+//        assertEquals(0, count);
+//
+//        IDevice d5 = deviceManager.learnDeviceByEntity(entity5);
+//        assertEquals(d1.getEntityClass(), d5.getEntityClass());
+//        iter = deviceManager.queryClassDevices(d1.getEntityClass(), null,
+//                                               (short)4, 3, null, null);
+//        count = 0;
+//        Set<Long> deviceKeysFromIterator = new HashSet<Long>();
+//        while (iter.hasNext()) {
+//            count += 1;
+//            d = iter.next();
+//            deviceKeysFromIterator.add(d.getDeviceKey());
+//        }
+//        Set<Long> expectedDeviceKeys = new HashSet<Long>();
+//        expectedDeviceKeys.add(d4.getDeviceKey());
+//        expectedDeviceKeys.add(d5.getDeviceKey());
+//        assertEquals(expectedDeviceKeys, deviceKeysFromIterator);
+//        assertEquals(2, count);
+//    }
+//
+//    @Test
+//    public void testDeviceClassIndex() throws Exception {
+//        EnumSet<IDeviceService.DeviceField> indexFields =
+//                EnumSet.noneOf(IDeviceService.DeviceField.class);
+//        indexFields.add(IDeviceService.DeviceField.IPV4);
+//        indexFields.add(IDeviceService.DeviceField.VLAN);
+//        deviceManager.addIndex(true, indexFields);
+//
+//        ITopologyService mockTopology = createMock(ITopologyService.class);
+//        deviceManager.topology = mockTopology;
+//        expect(mockTopology.isAttachmentPointPort(anyLong(),
+//                                                  anyShort())).
+//                                                  andReturn(true).anyTimes();
+//        expect(mockTopology.getL2DomainId(EasyMock.anyLong())).andReturn(1L).anyTimes();
+//        replay(mockTopology);
+//
+//        doTestDeviceClassQuery();
+//    }
+//
+//    @Test
+//    public void testDeviceClassQuery() throws Exception {
+//        ITopologyService mockTopology = createMock(ITopologyService.class);
+//        deviceManager.topology = mockTopology;
+//        expect(mockTopology.isAttachmentPointPort(anyLong(),
+//                                                  anyShort())).
+//                                                  andReturn(true).anyTimes();
+//        expect(mockTopology.getL2DomainId(EasyMock.anyLong())).andReturn(1L).anyTimes();
+//        replay(mockTopology);
+//
+//        doTestDeviceClassQuery();
+//    }
+//
+//    @Test
+//    public void testFindDevice() throws FloodlightModuleException {
+//        boolean exceptionCaught;
+//        deviceManager.entityClassifier= new MockEntityClassifierMac();
+//        deviceManager.startUp(null);
+//
+//        ITopologyService mockTopology = createMock(ITopologyService.class);
+//        deviceManager.topology = mockTopology;
+//        expect(mockTopology.isAttachmentPointPort(anyLong(),
+//                                                  anyShort())).
+//                                                  andReturn(true).anyTimes();
+//        expect(mockTopology.getL2DomainId(EasyMock.anyLong())).andReturn(1L).anyTimes();
+//        replay(mockTopology);
+//
+//        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");
+//
+//
+//        Entity entityNoClass = new Entity(5L, (short)1, 5, -1L, 1, new Date());
+//        assertEquals(null, deviceManager.findDeviceByEntity(entityNoClass));
+//
+//
+//        // Now look up destination devices
+//        assertEquals(d1, deviceManager.findClassDevice(d2.getEntityClass(),
+//                                                  entity1.getMacAddress(),
+//                                                  entity1.getVlan(),
+//                                                  entity1.getIpv4Address()));
+//        assertEquals(d1, deviceManager.findClassDevice(d2.getEntityClass(),
+//                                                  entity1.getMacAddress(),
+//                                                  entity1.getVlan(),
+//                                                  null));
+//        assertEquals(null, deviceManager.findClassDevice(d2.getEntityClass(),
+//                                                  entity1.getMacAddress(),
+//                                                  (short) -1,
+//                                                  0));
+//    }
+//
+//
+//
+//    @Test
+//    public void testGetIPv4Addresses() {
+//        // Looks like Date is only 1s granularity
+//
+//        ITopologyService mockTopology = createMock(ITopologyService.class);
+//        deviceManager.topology = mockTopology;
+//        expect(mockTopology.isAttachmentPointPort(anyLong(),
+//                                                  anyShort())).
+//                                                  andReturn(true).anyTimes();
+//        expect(mockTopology.getL2DomainId(anyLong())).andReturn(1L).anyTimes();
+//        expect(mockTopology.isConsistent(EasyMock.anyLong(),
+//                                         EasyMock.anyShort(),
+//                                         EasyMock.anyLong(),
+//                                         EasyMock.anyShort()))
+//                                         .andReturn(false)
+//                                         .anyTimes();
+//        expect(mockTopology.isBroadcastDomainPort(EasyMock.anyLong(),
+//                                                  EasyMock.anyShort()))
+//                                                  .andReturn(false)
+//                                                  .anyTimes();
+//        expect(mockTopology.isInSameBroadcastDomain(EasyMock.anyLong(),
+//                                                    EasyMock.anyShort(),
+//                                                    EasyMock.anyLong(),
+//                                                    EasyMock.anyShort())).
+//                                                    andReturn(false).anyTimes();
+//        replay(mockTopology);
+//
+//        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);
+//    }
+//
+//    // TODO: this test should really go into a separate class that collects
+//    // unit tests for Device
+//    @Test
+//    public void testGetSwitchPortVlanId() {
+//            Entity entity1 = new Entity(1L, (short)1, null, 10L, 1, new Date());
+//            Entity entity2 = new Entity(1L, null, null, 10L, 1, new Date());
+//            Entity entity3 = new Entity(1L, (short)3, null,  1L, 1, new Date());
+//            Entity entity4 = new Entity(1L, (short)42, null,  1L, 1, new Date());
+//            Entity[] entities = new Entity[] { entity1, entity2,
+//                                               entity3, entity4
+//                                             };
+//            Device d = new Device(null,1L, null, null, null,
+//                                  Arrays.asList(entities), null);
+//            SwitchPort swp1x1 = new SwitchPort(1L, 1);
+//            SwitchPort swp1x2 = new SwitchPort(1L, 2);
+//            SwitchPort swp2x1 = new SwitchPort(2L, 1);
+//            SwitchPort swp10x1 = new SwitchPort(10L, 1);
+//            assertArrayEquals(new Short[] { -1, 1},
+//                              d.getSwitchPortVlanIds(swp10x1));
+//            assertArrayEquals(new Short[] { 3, 42},
+//                              d.getSwitchPortVlanIds(swp1x1));
+//            assertArrayEquals(new Short[0],
+//                              d.getSwitchPortVlanIds(swp1x2));
+//            assertArrayEquals(new Short[0],
+//                              d.getSwitchPortVlanIds(swp2x1));
+//    }
+//
+//    @Test
+//    public void testReclassifyDevice() throws FloodlightModuleException {
+//        MockFlexEntityClassifier flexClassifier =
+//                new MockFlexEntityClassifier();
+//        deviceManager.entityClassifier= flexClassifier;
+//        deviceManager.startUp(null);
+//
+//        ITopologyService mockTopology = createMock(ITopologyService.class);
+//        deviceManager.topology = mockTopology;
+//        expect(mockTopology.isAttachmentPointPort(anyLong(),
+//                                                  anyShort())).
+//                                                  andReturn(true).anyTimes();
+//        expect(mockTopology.getL2DomainId(anyLong())).andReturn(1L).anyTimes();
+//        expect(mockTopology.isConsistent(EasyMock.anyLong(),
+//                                         EasyMock.anyShort(),
+//                                         EasyMock.anyLong(),
+//                                         EasyMock.anyShort()))
+//                                         .andReturn(false)
+//                                         .anyTimes();
+//        expect(mockTopology.isBroadcastDomainPort(EasyMock.anyLong(),
+//                                                  EasyMock.anyShort()))
+//                                                  .andReturn(false)
+//                                                  .anyTimes();
+//        replay(mockTopology);
+//
+//        //flexClassifier.createTestEntityClass("Class1");
+//
+//        Entity entity1 = new Entity(1L, (short)1, 1, 1L, 1, new Date());
+//        Entity entity1b = new Entity(1L, (short)2, 1, 1L, 1, new Date());
+//        Entity entity2 = new Entity(2L, (short)1, 2, 2L, 2, new Date());
+//        Entity entity2b = new Entity(2L, (short)2, 2, 2L, 2, new Date());
+//
+//
+//        Device d1 = deviceManager.learnDeviceByEntity(entity1);
+//        Device d2 = deviceManager.learnDeviceByEntity(entity2);
+//        Device d1b = deviceManager.learnDeviceByEntity(entity1b);
+//        Device d2b = deviceManager.learnDeviceByEntity(entity2b);
+//
+//        d1 = deviceManager.getDeviceIteratorForQuery(entity1.getMacAddress(),
+//                        entity1.getVlan(), entity1.getIpv4Address(),
+//                        entity1.getSwitchDPID(), entity1.getSwitchPort())
+//                        .next();
+//        d1b = deviceManager.getDeviceIteratorForQuery(entity1b.getMacAddress(),
+//                entity1b.getVlan(), entity1b.getIpv4Address(),
+//                entity1b.getSwitchDPID(), entity1b.getSwitchPort()).next();
+//
+//        assertEquals(d1, d1b);
+//
+//        d2 = deviceManager.getDeviceIteratorForQuery(entity2.getMacAddress(),
+//                entity2.getVlan(), entity2.getIpv4Address(),
+//                entity2.getSwitchDPID(), entity2.getSwitchPort()).next();
+//        d2b = deviceManager.getDeviceIteratorForQuery(entity2b.getMacAddress(),
+//                entity2b.getVlan(), entity2b.getIpv4Address(),
+//                entity2b.getSwitchDPID(), entity2b.getSwitchPort()).next();
+//        assertEquals(d2, d2b);
+//
+//        IEntityClass eC1 = flexClassifier.createTestEntityClass("C1");
+//        IEntityClass eC2 = flexClassifier.createTestEntityClass("C2");
+//
+//        flexClassifier.addVlanEntities((short)1, eC1);
+//        flexClassifier.addVlanEntities((short)2, eC1);
+//
+//        deviceManager.reclassifyDevice(d1);
+//        deviceManager.reclassifyDevice(d2);
+//
+//        d1 = deviceManager.deviceMap.get(
+//                deviceManager.primaryIndex.findByEntity(entity1));
+//        d1b = deviceManager.deviceMap.get(
+//                deviceManager.primaryIndex.findByEntity(entity1b));
+//
+//        assertEquals(d1, d1b);
+//
+//        d2 = deviceManager.deviceMap.get(
+//                deviceManager.primaryIndex.findByEntity(entity2));
+//        d2b = deviceManager.deviceMap.get(
+//                deviceManager.primaryIndex.findByEntity(entity2b));
+//
+//        assertEquals(d2, d2b);
+//
+//        flexClassifier.addVlanEntities((short)1, eC2);
+//
+//        deviceManager.reclassifyDevice(d1);
+//        deviceManager.reclassifyDevice(d2);
+//        d1 = deviceManager.deviceMap.get(
+//                deviceManager.primaryIndex.findByEntity(entity1));
+//        d1b = deviceManager.deviceMap.get(
+//                deviceManager.primaryIndex.findByEntity(entity1b));
+//        d2 = deviceManager.deviceMap.get(
+//                deviceManager.primaryIndex.findByEntity(entity2));
+//        d2b = deviceManager.deviceMap.get(
+//                deviceManager.primaryIndex.findByEntity(entity2b));
+//
+//        assertNotSame(d1, d1b);
+//
+//        assertNotSame(d2, d2b);
+//
+//        flexClassifier.addVlanEntities((short)1, eC1);
+//        deviceManager.reclassifyDevice(d1);
+//        deviceManager.reclassifyDevice(d2);
+//        ClassState classState = deviceManager.classStateMap.get(eC1.getName());
+//
+//        Long deviceKey1 = null;
+//        Long deviceKey1b = null;
+//        Long deviceKey2 = null;
+//        Long deviceKey2b = null;
+//
+//        deviceKey1 =
+//                classState.classIndex.findByEntity(entity1);
+//        deviceKey1b =
+//                classState.classIndex.findByEntity(entity1b);
+//        deviceKey2 =
+//                classState.classIndex.findByEntity(entity2);
+//        deviceKey2b =
+//                classState.classIndex.findByEntity(entity2b);
+//
+//        assertEquals(deviceKey1, deviceKey1b);
+//
+//        assertEquals(deviceKey2, deviceKey2b);
+//    }
+//
+//    @Test
+//    public void testSyncEntity() {
+//        Date d1 = new Date();
+//        Date d2 = new Date(0);
+//        Entity e1 = new Entity(1L, (short)2, 3, 4L, 5, d1);
+//        e1.setActiveSince(d2);
+//        SyncEntity se1 = new SyncEntity(e1);
+//        assertEntityEquals(e1, se1);
+//        assertEquals(1L, se1.macAddress);
+//        assertEquals(Short.valueOf((short)2), se1.vlan);
+//        assertEquals(Integer.valueOf(3), se1.ipv4Address);
+//        assertEquals(Long.valueOf(4L), se1.switchDPID);
+//        assertEquals(Integer.valueOf(5), se1.switchPort);
+//        assertEquals(d1, se1.lastSeenTimestamp);
+//        assertEquals(d2, se1.activeSince);
+//        assertNotSame(d1, se1.lastSeenTimestamp);
+//        assertNotSame(d2, se1.activeSince);
+//
+//        Entity e2 = new Entity(42L, null, null, null, null, null);
+//        SyncEntity se2 = new SyncEntity(e2);
+//        assertEntityEquals(e2, se2);
+//
+//        SyncEntity se3 = new SyncEntity();
+//        SyncEntity se4 = new SyncEntity();
+//        se3.lastSeenTimestamp = new Date(1000);
+//        se4.lastSeenTimestamp = new Date(2000);
+//        assertTrue("", se3.compareTo(se4) < 0);
+//        assertTrue("", se4.compareTo(se3) > 0);
+//        se4.lastSeenTimestamp = new Date(1000);
+//        assertTrue("", se3.compareTo(se4) == 0);
+//        assertTrue("", se4.compareTo(se3) == 0);
+//        se4.lastSeenTimestamp = new Date(500);
+//        assertTrue("", se3.compareTo(se4) > 0);
+//        assertTrue("", se4.compareTo(se3) < 0);
+//    }
+//
+//    /* Test basic DeviceSyncRepresentation behavior */
+//    @Test
+//    public void testDeviceSyncRepresentationBasics() {
+//        DeviceSyncRepresentation dsr = new DeviceSyncRepresentation();
+//        assertNull(dsr.getKey());
+//        assertNull(dsr.getEntities());
+//        dsr.setKey("MyKey");
+//        assertEquals("MyKey", dsr.getKey());
+//        assertEquals("MyKey", dsr.toString());
+//
+//        List<SyncEntity> entities = new ArrayList<SyncEntity>();
+//        Entity e1a = new Entity(1L, (short)2, 3, 4L, 5, new Date(1000));
+//        Entity e1b = new Entity(1L, (short)2, null, 4L, 5, new Date(0));
+//        entities.add(new SyncEntity(e1a));
+//        entities.add(new SyncEntity(e1b));
+//        // e1b comes before e1 (lastSeen) but we add it after it to test
+//        // sorting
+//        dsr.setEntities(entities);
+//
+//        assertEquals(2, dsr.getEntities().size());
+//        // e1b has earlier time
+//        assertEquals(e1b, dsr.getEntities().get(0).asEntity());
+//        assertEquals(e1a, dsr.getEntities().get(1).asEntity());
+//
+//        dsr.setKey(null);
+//        dsr.setEntities(null);
+//        assertNull(dsr.getKey());
+//        assertNull(dsr.getEntities());
+//    }
+//
+//    @Test
+//    public void testDeviceSyncRepresentationFromDevice() {
+//        ITopologyService mockTopology = makeMockTopologyAllPortsAp();
+//        replay(mockTopology);
+//        deviceManager.topology = mockTopology;
+//
+//        deviceManager.entityClassifier = new MockEntityClassifier();
+//
+//        //**************************************
+//        // Test 1: a single entity
+//        Entity e1 = new Entity(1L, (short)2, 3, 4L, 5, new Date(1000));
+//        Device d1 = deviceManager.learnDeviceByEntity(e1);
+//        assertEquals("Sanity check failed. Device doesn't have the expected " +
+//                     "entity class. Something with the test setup is strange",
+//                     "DefaultEntityClass", d1.getEntityClass().getName());
+//        assertEquals("Sanity check failed. Device doesn't have the expected " +
+//                     "entity class. Something with the test setup is strange",
+//                     EnumSet.of(DeviceField.MAC, DeviceField.VLAN),
+//                     d1.getEntityClass().getKeyFields());
+//
+//        Long deviceKey = d1.getDeviceKey();
+//        DeviceSyncRepresentation dsr1 = new DeviceSyncRepresentation(d1);
+//        assertEquals("DefaultEntityClass::00:00:00:00:00:01::[2]::",
+//                     dsr1.getKey());
+//        assertEquals(1, dsr1.getEntities().size());
+//        assertEquals(e1, dsr1.getEntities().get(0).asEntity());
+//
+//        //**************************************
+//        // Test 1b: same device, now with a second entity (no IP).
+//        // this second entity has a lastSeen time that is earlier than the
+//        // first entity
+//        Entity e1b = new Entity(1L, (short)2, null, 4L, 5, new Date(0));
+//        d1 = deviceManager.learnDeviceByEntity(e1b);
+//        assertEquals("Sanity check failed. Should still be same device but " +
+//                     "deviceKeys differs", deviceKey, d1.getDeviceKey());
+//        dsr1 = new DeviceSyncRepresentation(d1);
+//        assertEquals("DefaultEntityClass::00:00:00:00:00:01::[2]::",
+//                     dsr1.getKey());
+//        assertEquals(2, dsr1.getEntities().size());
+//        // Entities are ordered by their lastSeen time. e1b should come
+//        // before e1.
+//        assertEquals(e1, dsr1.getEntities().get(1).asEntity());
+//        assertEquals(e1b, dsr1.getEntities().get(0).asEntity());
+//
+//        //**************************************
+//        // Test 1c: same device with a third entity that does not have a
+//        // switch port. It should be added to the DeviceSyncRepresentation
+//        Entity e1c = new Entity(1L, (short)2, 33, null, null, new Date(2000));
+//        d1 = deviceManager.learnDeviceByEntity(e1c);
+//        assertEquals("Sanity check failed. Should still be same device but " +
+//                     "deviceKeys differs", deviceKey, d1.getDeviceKey());
+//        dsr1 = new DeviceSyncRepresentation(d1);
+//        assertEquals("DefaultEntityClass::00:00:00:00:00:01::[2]::",
+//                     dsr1.getKey());
+//        assertEquals(3, dsr1.getEntities().size());
+//        // Entities are ordered by their lastSeen time
+//        assertEquals(e1c, dsr1.getEntities().get(2).asEntity());
+//        assertEquals(e1, dsr1.getEntities().get(1).asEntity());
+//        assertEquals(e1b, dsr1.getEntities().get(0).asEntity());
+//
+//        //**************************************
+//        // Test 1d: same device with a fourth entity that has a different
+//        // attachment point and that is newer. Device should move and
+//        // non-attachment point entities should be removed (e1b). Although
+//        // e1 is non-attachment point it will remain because it has an IP
+//        Entity e1d = new Entity(1L, (short)2, 33, 4L, 6, new Date(3000));
+//        d1 = deviceManager.learnDeviceByEntity(e1d);
+//        assertEquals("Sanity check failed. Should still be same device but " +
+//                     "deviceKeys differs", deviceKey, d1.getDeviceKey());
+//        dsr1 = new DeviceSyncRepresentation(d1);
+//        assertEquals("DefaultEntityClass::00:00:00:00:00:01::[2]::",
+//                     dsr1.getKey());
+//        assertEquals(3, dsr1.getEntities().size());
+//        assertEquals(e1, dsr1.getEntities().get(0).asEntity());
+//        assertEquals(e1c, dsr1.getEntities().get(1).asEntity());
+//        assertEquals(e1d, dsr1.getEntities().get(2).asEntity());
+//
+//        d1 = null;
+//
+//
+//        //**************************************
+//        // Test 2: a second device with a different entity class. The
+//        // mock entity classifier will return an entity class where all
+//        // fields are keys if the DPID is > 10L
+//        Entity e2 = new Entity(2L, (short)23, 24, 11L, 1, new Date(0));
+//        Device d2 = deviceManager.learnDeviceByEntity(e2);
+//        DeviceSyncRepresentation dsr2 = new DeviceSyncRepresentation(d2);
+//        assertEquals("Sanity check failed. Device doesn't have the expected " +
+//                     "entity class. Something with the test setup is strange",
+//                     "TestEntityClass", d2.getEntityClass().getName());
+//        assertEquals("Sanity check failed. Device doesn't have the expected " +
+//                     "entity class. Something with the test setup is strange",
+//                     EnumSet.of(DeviceField.MAC, DeviceField.VLAN,
+//                                DeviceField.SWITCH, DeviceField.PORT),
+//                     d2.getEntityClass().getKeyFields());
+//        SwitchPort swp = new SwitchPort(11L, 1, null);
+//        assertEquals("TestEntityClass::00:00:00:00:00:02::[23]::[" +
+//                     swp.toString() + "]::",
+//                     dsr2.getKey());
+//    }
+//
+//    /* interate through all entries in the sync store and return them as
+//     * list. We don't return the key from the store however, we assert
+//     * that the key from the store matches the key in the representation.
+//     * If we have a null value (tombstone) we simply add the null value to
+//     * the list to return.
+//     */
+//    private List<DeviceSyncRepresentation> getEntriesFromStore()
+//            throws Exception {
+//        List<DeviceSyncRepresentation> entries =
+//                new ArrayList<DeviceSyncRepresentation>();
+//        IClosableIterator<Entry<String, Versioned<DeviceSyncRepresentation>>> iter =
+//                storeClient.entries();
+//        try {
+//            while(iter.hasNext()) {
+//                Entry<String, Versioned<DeviceSyncRepresentation>> entry =
+//                        iter.next();
+//                DeviceSyncRepresentation dsr = entry.getValue().getValue();
+//                if (dsr != null)
+//                    assertEquals(entry.getKey(), dsr.getKey());
+//                entries.add(dsr);
+//            }
+//        } finally {
+//            if (iter != null)
+//                iter.close();
+//        }
+//        return entries;
+//    }
+//
+//    /*
+//     * assert whether the given Entity expected is equals to the given
+//     * SyncEntity actual. This method also compares the times (lastSeen,
+//     * activeSince). Entity.equals will not do that!
+//     */
+//    private static void assertEntityEquals(Entity expected, SyncEntity actual) {
+//        assertNotNull(actual);
+//        assertNotNull(expected);
+//        Entity actualEntity = actual.asEntity();
+//        assertEquals("entityFields", expected, actualEntity);
+//        assertEquals("lastSeenTimestamp",
+//                     expected.getLastSeenTimestamp(),
+//                     actualEntity.getLastSeenTimestamp());
+//        assertEquals("activeSince",
+//                     expected.getActiveSince(), actualEntity.getActiveSince());
+//    }
+//
+//    /* This test tests the normal operation as master when we write to the sync
+//     * store or delete from the store.
+//     */
+//    @Test
+//    public void testWriteToSyncStore() throws Exception {
+//        int syncStoreIntervalMs = 50;
+//        ITopologyService mockTopology = makeMockTopologyAllPortsAp();
+//        replay(mockTopology);
+//        deviceManager.topology = mockTopology;
+//        deviceManager.setSyncStoreWriteInterval(syncStoreIntervalMs);
+//
+//        Entity e1a = new Entity(1L, (short)2, 3, 4L, 5, new Date(1000));
+//        e1a.setActiveSince(new Date(0));
+//        deviceManager.learnDeviceByEntity(e1a);
+//
+//        //storeClient.put("FooBar", new DeviceSyncRepresentation());
+//
+//        List<DeviceSyncRepresentation> entries = getEntriesFromStore();
+//        assertEquals(1, entries.size());
+//        DeviceSyncRepresentation dsr1 = entries.get(0);
+//        assertEquals(1, dsr1.getEntities().size());
+//        assertEntityEquals(e1a, dsr1.getEntities().get(0));
+//
+//        // Same entity but newer timestamp. Since the device hasn't changed,
+//        // only the timestamp is updated and the write should be throttled.
+//        Entity e1b = new Entity(1L, (short)2, 3, 4L, 5, new Date(2000));
+//        e1b.setActiveSince(new Date(0));
+//        deviceManager.learnDeviceByEntity(e1a);
+//        entries = getEntriesFromStore();
+//        assertEquals(1, entries.size());
+//        dsr1 = entries.get(0);
+//        assertEquals(1, dsr1.getEntities().size());
+//        assertEntityEquals(e1a, dsr1.getEntities().get(0)); //e1a not e1b !!!
+//
+//        // Wait for the write interval to expire then write again.
+//        Thread.sleep(syncStoreIntervalMs+5);
+//        Entity e1c = new Entity(1L, (short)2, 3, 4L, 5, new Date(3000));
+//        e1c.setActiveSince(new Date(0));
+//        deviceManager.learnDeviceByEntity(e1c);
+//        entries = getEntriesFromStore();
+//        assertEquals(1, entries.size());
+//        dsr1 = entries.get(0);
+//        assertEquals(1, dsr1.getEntities().size());
+//        assertEntityEquals(e1c, dsr1.getEntities().get(0)); // e1c !!
+//
+//        // Entity for same device but with different IP. should be added
+//        // immediately
+//        Entity e1d = new Entity(1L, (short)2, 33, 4L, 5, new Date(4000));
+//        e1d.setActiveSince(new Date(0));
+//        deviceManager.learnDeviceByEntity(e1d);
+//        entries = getEntriesFromStore();
+//        assertEquals(1, entries.size());
+//        dsr1 = entries.get(0);
+//        assertEquals(2, dsr1.getEntities().size());
+//        assertEntityEquals(e1c, dsr1.getEntities().get(0)); // e1c !!
+//        assertEntityEquals(e1d, dsr1.getEntities().get(1)); // e1d !!
+//
+//        // Entity for same device with new switch port ==> moved ==> write
+//        // update immediately without throttle.
+//        // Note: the previous entities will still be there because they have
+//        // IPs (even though they aren't for the current attachment point)
+//        Entity e1e = new Entity(1L, (short)2, 33, 4L, 6, new Date(5000));
+//        e1e.setActiveSince(new Date(0));
+//        deviceManager.learnDeviceByEntity(e1e);
+//        entries = getEntriesFromStore();
+//        assertEquals(1, entries.size());
+//        dsr1 = entries.get(0);
+//        assertEquals(3, dsr1.getEntities().size());
+//        assertEntityEquals(e1c, dsr1.getEntities().get(0));
+//        assertEntityEquals(e1d, dsr1.getEntities().get(1));
+//        assertEntityEquals(e1e, dsr1.getEntities().get(2));
+//
+//        // Add a second device
+//        Entity e2 = new Entity(2L, null, null, 5L, 5, new Date());
+//        deviceManager.learnDeviceByEntity(e2);
+//        entries = getEntriesFromStore();
+//        assertEquals(2, entries.size());
+//        for (DeviceSyncRepresentation dsr: entries) {
+//            // This is a kinda ugly way to ensure we have the two
+//            // devices we need..... but it will work for now
+//            if (dsr.getKey().contains("::00:00:00:00:00:01::")) {
+//                assertEquals(3, dsr.getEntities().size());
+//                assertEntityEquals(e1c, dsr.getEntities().get(0));
+//                assertEntityEquals(e1d, dsr.getEntities().get(1));
+//                assertEntityEquals(e1e, dsr.getEntities().get(2));
+//            } else if (dsr.getKey().contains("::00:00:00:00:00:02::")) {
+//                assertEquals(1, dsr.getEntities().size());
+//                assertEntityEquals(e2, dsr.getEntities().get(0));
+//            } else {
+//                fail("Unknown entry in store: " + dsr);
+//            }
+//        }
+//
+//
+//        // Run entity cleanup. Since we've used phony time stamps for
+//        // device 1 its entities should be cleared and the device should be
+//        // removed from the store. Device 2 should remain in the store.
+//        deviceManager.cleanupEntities();
+//        entries = getEntriesFromStore();
+//        assertEquals(2, entries.size());
+//        for (DeviceSyncRepresentation dsr: entries) {
+//            if (dsr == null) {
+//                // pass
+//            } else if (dsr.getKey().contains("::00:00:00:00:00:02::")) {
+//                assertEquals(1, dsr.getEntities().size());
+//                assertEntityEquals(e2, dsr.getEntities().get(0));
+//            } else {
+//                fail("Unknown entry in store: " + dsr);
+//            }
+//        }
+//    }
+//
+//
+//    private void assertDeviceIps(Integer[] expected, IDevice d) {
+//        List<Integer> expectedList = Arrays.asList(expected);
+//        Collections.sort(expectedList);
+//        List<Integer> actualList = Arrays.asList(d.getIPv4Addresses());
+//        Collections.sort(actualList);
+//        assertEquals(expectedList, actualList);
+//    }
+//
+//    private IDevice getSingleDeviceFromDeviceManager(long mac) {
+//        Iterator<? extends IDevice> diter =
+//                deviceManager.queryDevices(mac, null, null, null, null);
+//        assertTrue("Query didn't return a device", diter.hasNext());
+//        IDevice d = diter.next();
+//        assertFalse("Query returned more than one device", diter.hasNext());
+//        return d;
+//    }
+//
+//    @Test
+//    public void testToMaster() throws Exception {
+//        int syncStoreWriteIntervalMs = 0;
+//        int initialSyncStoreConsolidateIntervalMs = 50;
+//        ITopologyService mockTopology = makeMockTopologyAllPortsAp();
+//        replay(mockTopology);
+//        deviceManager.topology = mockTopology;
+//        // We want an EntityClassifier that has switch/port as key fields
+//        deviceManager.entityClassifier = new MockEntityClassifier();
+//        deviceManager.setSyncStoreWriteInterval(syncStoreWriteIntervalMs);
+//        deviceManager.setInitialSyncStoreConsolidateMs(initialSyncStoreConsolidateIntervalMs);
+//
+//        // Add Device1 with two entities with two different IPs
+//        Entity e1a = new Entity(1L, null, 3, 4L, 5, new Date(1000));
+//        Entity e1b = new Entity(1L, null, 33,  4L, 5, new Date(2000));
+//        Device d1 = deviceManager.allocateDevice(1L, e1a,
+//                                                 DefaultEntityClassifier.entityClass);
+//        d1 = deviceManager.allocateDevice(d1, e1b, -1);
+//        DeviceSyncRepresentation dsr = new DeviceSyncRepresentation(d1);
+//        storeClient.put(dsr.getKey(), dsr);
+//
+//        // Add Device2 with different switch-ports. Only the most recent
+//        // one should be the attachment point
+//        Entity e2a = new Entity(2L, null, null, 4L, 4, new Date(1000));
+//        Entity e2b = new Entity(2L, null, null, 4L, 5, new Date(2000));
+//        Device d2 = deviceManager.allocateDevice(2L, e2a,
+//                                                 DefaultEntityClassifier.entityClass);
+//        d2 = deviceManager.allocateDevice(d2, e2b, -1);
+//        d2.updateAttachmentPoint(4L, (short)5,
+//                                 e2b.getLastSeenTimestamp().getTime());
+//        SwitchPort swp = new SwitchPort(4L, 5);
+//        SwitchPort[] aps = d2.getAttachmentPoints();
+//        // sanity check
+//        assertArrayEquals("Sanity check: should only have AP(4L,5)",
+//                          new SwitchPort[] {swp}, aps);
+//        dsr = new DeviceSyncRepresentation(d2);
+//        storeClient.put(dsr.getKey(), dsr);
+//
+//        // Add a tombstone entry to the store to make sure we don't trip a
+//        // NPE
+//        dsr = null;
+//        Versioned<DeviceSyncRepresentation> versionedDsr =
+//                storeClient.get("FooBar");
+//        storeClient.put("FooBar", versionedDsr);
+//
+//        deviceManager.getHAListener().transitionToMaster();
+//
+//        // Query for the Device1. Make sure we have the two IPs we stored.
+//        IDevice d = getSingleDeviceFromDeviceManager(1L);
+//        assertDeviceIps(new Integer[] {3, 33}, d);
+//        assertArrayEquals(new Short[] { Ethernet.VLAN_UNTAGGED }, d.getVlanId());
+//        swp = new SwitchPort(4L, 5);
+//        assertArrayEquals(new SwitchPort[] { swp }, d.getAttachmentPoints());
+//
+//        // Query for Device2. Make sure we only have the more recent AP
+//        // Query for the Device1. Make sure we have the two IPs we stored.
+//        d = getSingleDeviceFromDeviceManager(2L);
+//        assertArrayEquals(new Integer[0], d.getIPv4Addresses());
+//        assertArrayEquals(new Short[] { Ethernet.VLAN_UNTAGGED }, d.getVlanId());
+//        swp = new SwitchPort(4L, 5);
+//        assertArrayEquals(new SwitchPort[] { swp }, d.getAttachmentPoints());
+//
+//        //----------------------------
+//        // add another entry device to the store. since device manager is
+//        // already master we won't read this device and it should be
+//        // removed from the store by the consolidate task
+//        Entity e3 = new Entity(3L, null, null, 1L, 1, null);
+//        dsr = new DeviceSyncRepresentation();
+//        dsr.setKey("Device3");
+//        dsr.setEntities(Collections.singletonList(new SyncEntity(e3)));
+//        storeClient.put(dsr.getKey(), dsr);
+//
+//        // make sure it's in the store
+//        List<DeviceSyncRepresentation> entries = getEntriesFromStore();
+//        boolean found = false;
+//        for (DeviceSyncRepresentation entry: entries) {
+//            if (entry!=null && entry.getKey().equals("Device3"))
+//                found = true;
+//        }
+//        assertTrue("Device3 not in store. Entries in store: " + entries, found);
+//        // make sure it's not in DevManager
+//        Iterator<? extends IDevice> diter =
+//                deviceManager.queryDevices(3L, null, null, null, null);
+//        assertFalse("Device3 found in DeviceManager. Should be there",
+//                    diter.hasNext());
+//
+//        // Wait for consolidate
+//        Thread.sleep(initialSyncStoreConsolidateIntervalMs + 5);
+//        // make sure it's in NOT the store
+//        entries = getEntriesFromStore();
+//        found = false;
+//        for (DeviceSyncRepresentation entry: entries) {
+//            if (entry!=null && entry.getKey().equals("Device3"))
+//                found = true;
+//        }
+//        assertFalse("Device3 not is still in the store. Entries in store: "
+//                    + entries, found);
+//        // make sure it's not in DevManager
+//        diter = deviceManager.queryDevices(3L, null, null, null, null);
+//        assertFalse("Device3 found in DeviceManager. Should be there",
+//                    diter.hasNext());
+//    }
+//
+//
+//    @Test
+//    public void testConsolitateStore() throws Exception {
+//        int syncStoreInternalMs = 0;
+//        ITopologyService mockTopology = makeMockTopologyAllPortsAp();
+//        replay(mockTopology);
+//        deviceManager.topology = mockTopology;
+//        // We want an EntityClassifier that has switch/port as key fields
+//        deviceManager.entityClassifier = new MockEntityClassifier();
+//        deviceManager.setSyncStoreWriteInterval(syncStoreInternalMs);
+//
+//        // Add Device1 with two entities to store and let device manager
+//        // learn
+//        Entity e1a = new Entity(1L, null, null, 4L, 5, new Date(1000));
+//        Entity e1b = new Entity(1L, null, 3,  4L, 5, new Date(2000));
+//        Device d1 = deviceManager.learnDeviceByEntity(e1a);
+//        deviceManager.learnDeviceByEntity(e1b);
+//        String dev1Key = DeviceSyncRepresentation.computeKey(d1);
+//
+//
+//        // Add a second device to the store but do NOT add to device manager
+//        Entity e2 = new Entity(2L, null, null, 5L, 5, new Date());
+//        Device d2 = deviceManager.allocateDevice(42L, e2,
+//                                                 DefaultEntityClassifier.entityClass);
+//        DeviceSyncRepresentation dsr = new DeviceSyncRepresentation(d2);
+//        storeClient.put(dsr.getKey(), dsr);
+//        String dev2Key = DeviceSyncRepresentation.computeKey(d2);
+//
+//        // Make sure we have two devices in the store
+//        List<DeviceSyncRepresentation> entries = getEntriesFromStore();
+//        assertEquals(2, entries.size());
+//
+//        deviceManager.scheduleConsolidateStoreNow();
+//        Thread.sleep(25); // give the scheduler time to run the task
+//
+//        // We should still have two entries, however one of them will be a
+//        // tombstone
+//        entries = getEntriesFromStore();
+//        assertEquals(2, entries.size());
+//
+//        // Device 1 should still be in store
+//        Versioned<DeviceSyncRepresentation> versioned =
+//                storeClient.get(dev1Key);
+//        dsr = versioned.getValue();
+//        assertNotNull(dsr);
+//        assertEquals(2, dsr.getEntities().size());
+//        assertEntityEquals(e1a, dsr.getEntities().get(0));
+//        assertEntityEquals(e1b, dsr.getEntities().get(1));
+//
+//        // Device2 should be gone
+//        versioned = storeClient.get(dev2Key);
+//        assertNull(versioned.getValue());
+//
+//        // Run consolitate again. This time we check that tombstones in
+//        // the store are handled correctly
+//        deviceManager.scheduleConsolidateStoreNow();
+//        Thread.sleep(25); // give the scheduler time to run the task
+//
+//        // Now write a device to the store that doesn't have any switch-port
+//        // it should be removed
+//        Entity e3 = new Entity(3L, null, null, null, null, null);
+//        dsr.setKey("Device3");
+//        dsr.setEntities(Collections.singletonList(new SyncEntity(e3)));
+//        storeClient.put(dsr.getKey(), dsr);
+//
+//        // Run consolitate again. This time we check that tombstones in
+//        // the store are handled correctly
+//        deviceManager.scheduleConsolidateStoreNow();
+//        Thread.sleep(25); // give the scheduler time to run the task
+//        versioned = storeClient.get("Device3");
+//        assertNull(versioned.getValue());
+//
+//    }
+//
+// }
diff --git a/opendaylight/hosttracker_new/implementation/src/test/java/org/opendaylight/controller/hosttracker/internal/DeviceUniqueIndexTest.java b/opendaylight/hosttracker_new/implementation/src/test/java/org/opendaylight/controller/hosttracker/internal/DeviceUniqueIndexTest.java
new file mode 100644 (file)
index 0000000..a2a1004
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2012 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.hosttracker.internal;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+import org.junit.Test;
+import org.opendaylight.controller.hosttracker.Entity;
+import org.opendaylight.controller.hosttracker.IDeviceService.DeviceField;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.NodeConnector.NodeConnectorIDType;
+
+/**
+ *
+ * @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 e3_ip;
+    protected Entity e4;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        Node n1 = new Node(Node.NodeIDType.OPENFLOW, Long.valueOf(1L));
+        NodeConnector n1_1 = new NodeConnector(NodeConnectorIDType.OPENFLOW,
+                Short.valueOf((short) 1), n1);
+        e1a = new Entity(1L, (short) 1, 1, n1_1, new Date());
+        e1b = new Entity(1L, (short) 2, 1, n1_1, new Date());
+        List<Entity> d1Entities = new ArrayList<Entity>(2);
+        d1Entities.add(e1a);
+        d1Entities.add(e1b);
+        d1 = new Device(null, Long.valueOf(1), null, null, null, d1Entities,
+                null);
+
+        Node n2 = new Node(Node.NodeIDType.OPENFLOW, Long.valueOf(2L));
+        NodeConnector n2_2 = new NodeConnector(NodeConnectorIDType.OPENFLOW,
+                Short.valueOf((short) 2), n2);
+        Node n3 = new Node(Node.NodeIDType.OPENFLOW, Long.valueOf(3L));
+        NodeConnector n3_3 = new NodeConnector(NodeConnectorIDType.OPENFLOW,
+                Short.valueOf((short) 3), n3);
+
+        // e2 and e2 alt match in MAC and VLAN
+        e2 = new Entity(2L, (short) 2, 2, n2_2, new Date());
+        e2alt = new Entity(2, (short) 2, null, null, null);
+
+        // IP is null
+        e3 = new Entity(3L, (short) 3, null, n3_3, new Date());
+        e3_ip = new Entity(3L, (short) 3, 3, n3_3, new Date());
+
+        // IP and switch and port are null
+        e4 = new Entity(4L, (short) 4, 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.SWITCHPORT));
+        // only one key field is null
+        idx2.updateIndex(e3, 3L);
+        assertEquals(Long.valueOf(3L), idx2.findByEntity(e3));
+        assertEquals(null, idx2.findByEntity(e3_ip));
+        // all key fields are null
+        idx2.updateIndex(e4, 4L);
+        assertEquals(null, idx2.findByEntity(e4));
+        Device d4 = new Device(null, 4L, null, null, null,
+                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/opendaylight/hosttracker_new/implementation/src/test/java/org/opendaylight/controller/hosttracker/test/MockDevice.java b/opendaylight/hosttracker_new/implementation/src/test/java/org/opendaylight/controller/hosttracker/test/MockDevice.java
new file mode 100644 (file)
index 0000000..03f0598
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2011,2012 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * 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.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the
+ *    License. You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an "AS
+ *    IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ *    express or implied. See the License for the specific language
+ *    governing permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.hosttracker.test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.TreeSet;
+
+import org.opendaylight.controller.hosttracker.Entity;
+import org.opendaylight.controller.hosttracker.IEntityClass;
+import org.opendaylight.controller.hosttracker.SwitchPort;
+import org.opendaylight.controller.hosttracker.internal.AttachmentPoint;
+import org.opendaylight.controller.hosttracker.internal.Device;
+import org.opendaylight.controller.hosttracker.internal.DeviceManagerImpl;
+
+/**
+ * This mock device removes the dependency on topology and a parent device
+ * manager and simply assumes all its entities are current and correct
+ */
+public class MockDevice extends Device {
+
+    public MockDevice(DeviceManagerImpl deviceManager, Long deviceKey,
+            Entity entity, IEntityClass entityClass) {
+        super(deviceManager, deviceKey, entity, entityClass);
+    }
+
+    public MockDevice(Device device, Entity newEntity, int insertionpoint) {
+        super(device, newEntity, insertionpoint);
+    }
+
+    public MockDevice(DeviceManagerImpl deviceManager, Long deviceKey,
+            List<AttachmentPoint> aps, List<AttachmentPoint> trueAPs,
+            Collection<Entity> entities, IEntityClass entityClass) {
+        super(deviceManager, deviceKey, null, aps, trueAPs, entities,
+                entityClass);
+    }
+
+    @Override
+    public Integer[] getIPv4Addresses() {
+        TreeSet<Integer> vals = new TreeSet<Integer>();
+        for (Entity e : entities) {
+            if (e.getIpv4Address() == null)
+                continue;
+            vals.add(e.getIpv4Address());
+        }
+
+        return vals.toArray(new Integer[vals.size()]);
+    }
+
+    @Override
+    public SwitchPort[] getAttachmentPoints() {
+        ArrayList<SwitchPort> vals = new ArrayList<SwitchPort>(entities.length);
+        for (Entity e : entities) {
+            if (e.getPort() != null
+                    && deviceManager.isValidAttachmentPoint(e.getPort())) {
+                SwitchPort sp = new SwitchPort(e.getPort());
+                vals.add(sp);
+            }
+        }
+        return vals.toArray(new SwitchPort[vals.size()]);
+    }
+
+    @Override
+    public String toString() {
+        return "MockDevice [getEntityClass()=" + getEntityClass()
+                + ", getEntities()=" + Arrays.toString(getEntities()) + "]";
+    }
+
+}
diff --git a/opendaylight/hosttracker_new/implementation/src/test/java/org/opendaylight/controller/hosttracker/test/MockDeviceManager.java b/opendaylight/hosttracker_new/implementation/src/test/java/org/opendaylight/controller/hosttracker/test/MockDeviceManager.java
new file mode 100644 (file)
index 0000000..af2feb1
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2013 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.hosttracker.test;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+import org.opendaylight.controller.hosttracker.Entity;
+import org.opendaylight.controller.hosttracker.IDevice;
+import org.opendaylight.controller.hosttracker.IDeviceListener;
+import org.opendaylight.controller.hosttracker.IEntityClass;
+import org.opendaylight.controller.hosttracker.IEntityClassifierService;
+import org.opendaylight.controller.hosttracker.internal.AttachmentPoint;
+import org.opendaylight.controller.hosttracker.internal.Device;
+import org.opendaylight.controller.hosttracker.internal.DeviceManagerImpl;
+import org.opendaylight.controller.sal.core.NodeConnector;
+
+/**
+ * Mock device manager useful for unit tests
+ *
+ * @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;
+        // setSyncServiceIfNotSet(new MockSyncService());
+        this.start();
+    }
+
+    /**
+     * Learn a device using the given characteristics.
+     *
+     * @param macAddress
+     *            the MAC
+     * @param vlan
+     *            the VLAN (can be null)
+     * @param ipv4Address
+     *            the IP (can be null)
+     * @param switchDPID
+     *            the attachment point switch DPID (can be null)
+     * @param switchPort
+     *            the attachment point switch port (can be null)
+     * @param processUpdates
+     *            if false, will not send updates. Note that this method is not
+     *            thread safe if this is false
+     * @return the device, either new or not
+     */
+    public IDevice learnEntity(long macAddress, Short vlan,
+            Integer ipv4Address, NodeConnector port, boolean processUpdates) {
+        List<IDeviceListener> listeners = deviceListeners.getOrderedListeners();
+        if (!processUpdates) {
+            deviceListeners.clearListeners();
+        }
+
+        if (vlan != null && vlan.shortValue() <= 0)
+            vlan = null;
+        if (ipv4Address != null && ipv4Address == 0)
+            ipv4Address = null;
+        IDevice res = learnDeviceByEntity(new Entity(macAddress, vlan,
+                ipv4Address, port, new Date()));
+        // Restore listeners
+        if (listeners != null) {
+            for (IDeviceListener listener : listeners) {
+                deviceListeners.addListener("device", listener);
+            }
+        }
+        return res;
+    }
+
+    @Override
+    public void deleteDevice(Device device) {
+        super.deleteDevice(device);
+    }
+
+    /**
+     * Learn a device using the given characteristics.
+     *
+     * @param macAddress
+     *            the MAC
+     * @param vlan
+     *            the VLAN (can be null)
+     * @param ipv4Address
+     *            the IP (can be null)
+     * @param switchDPID
+     *            the attachment point switch DPID (can be null)
+     * @param switchPort
+     *            the attachment point switch port (can be null)
+     * @return the device, either new or not
+     */
+    public IDevice learnEntity(long macAddress, Short vlan,
+            Integer ipv4Address, NodeConnector port) {
+        return learnEntity(macAddress, vlan, ipv4Address, port, true);
+    }
+
+    @Override
+    protected Device allocateDevice(Long deviceKey, Entity entity,
+            IEntityClass entityClass) {
+        return new MockDevice(this, deviceKey, entity, entityClass);
+    }
+
+    @Override
+    protected Device allocateDevice(Long deviceKey, String dhcpClientName,
+            List<AttachmentPoint> aps, List<AttachmentPoint> trueAPs,
+            Collection<Entity> entities, IEntityClass entityClass) {
+        return new MockDevice(this, deviceKey, aps, trueAPs, entities,
+                entityClass);
+    }
+
+    @Override
+    protected Device allocateDevice(Device device, Entity entity,
+            int insertionpoint) {
+        return new MockDevice(device, entity, insertionpoint);
+    }
+}
diff --git a/opendaylight/hosttracker_new/implementation/src/test/java/org/opendaylight/controller/hosttracker/test/MockEntityClassifier.java b/opendaylight/hosttracker_new/implementation/src/test/java/org/opendaylight/controller/hosttracker/test/MockEntityClassifier.java
new file mode 100644 (file)
index 0000000..e9f919e
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2013 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.hosttracker.test;
+
+import static org.opendaylight.controller.hosttracker.IDeviceService.DeviceField.MAC;
+import static org.opendaylight.controller.hosttracker.IDeviceService.DeviceField.SWITCHPORT;
+import static org.opendaylight.controller.hosttracker.IDeviceService.DeviceField.VLAN;
+
+import java.util.EnumSet;
+
+import org.opendaylight.controller.hosttracker.Entity;
+import org.opendaylight.controller.hosttracker.IDeviceService;
+import org.opendaylight.controller.hosttracker.IDeviceService.DeviceField;
+import org.opendaylight.controller.hosttracker.IEntityClass;
+import org.opendaylight.controller.hosttracker.internal.DefaultEntityClassifier;
+
+/**
+ * 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, SWITCHPORT);
+        }
+
+        @Override
+        public String getName() {
+            return "TestEntityClass";
+        }
+    }
+
+    public static IEntityClass testEC = new MockEntityClassifier.TestEntityClass();
+
+    @Override
+    public IEntityClass classifyEntity(Entity entity) {
+        if (((Long) entity.getPort().getNode().getID()) >= 10L) {
+            return testEC;
+        }
+        return DefaultEntityClassifier.entityClass;
+    }
+
+    @Override
+    public EnumSet<IDeviceService.DeviceField> getKeyFields() {
+        return EnumSet.of(MAC, VLAN, SWITCHPORT);
+    }
+
+}
\ No newline at end of file
diff --git a/opendaylight/hosttracker_new/implementation/src/test/java/org/opendaylight/controller/hosttracker/test/MockEntityClassifierMac.java b/opendaylight/hosttracker_new/implementation/src/test/java/org/opendaylight/controller/hosttracker/test/MockEntityClassifierMac.java
new file mode 100644 (file)
index 0000000..436e6fa
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2013 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.hosttracker.test;
+
+import static org.opendaylight.controller.hosttracker.IDeviceService.DeviceField.MAC;
+import static org.opendaylight.controller.hosttracker.IDeviceService.DeviceField.SWITCHPORT;
+import static org.opendaylight.controller.hosttracker.IDeviceService.DeviceField.VLAN;
+
+import java.util.EnumSet;
+
+import org.opendaylight.controller.hosttracker.Entity;
+import org.opendaylight.controller.hosttracker.IDeviceService;
+import org.opendaylight.controller.hosttracker.IDeviceService.DeviceField;
+import org.opendaylight.controller.hosttracker.IEntityClass;
+import org.opendaylight.controller.hosttracker.internal.DefaultEntityClassifier;
+
+/**
+ * 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 (((Long) entity.getPort().getNode().getID()) == null) {
+            throw new IllegalArgumentException("Not all key fields specified."
+                    + " Required fields: " + getKeyFields());
+        } else if (((Long) entity.getPort().getNode().getID()) == 1L) {
+            return testECMac1;
+        } else if (((Long) entity.getPort().getNode().getID()) == 2L) {
+            return testECMac2;
+        } else if (((Long) entity.getPort().getNode().getID()) == -1L) {
+            return null;
+        }
+        return DefaultEntityClassifier.entityClass;
+    }
+
+    @Override
+    public EnumSet<IDeviceService.DeviceField> getKeyFields() {
+        return EnumSet.of(MAC, VLAN, SWITCHPORT);
+    }
+}
\ No newline at end of file
diff --git a/opendaylight/hosttracker_new/implementation/src/test/java/org/opendaylight/controller/hosttracker/test/MockFlexEntityClassifier.java b/opendaylight/hosttracker_new/implementation/src/test/java/org/opendaylight/controller/hosttracker/test/MockFlexEntityClassifier.java
new file mode 100644 (file)
index 0000000..fe2dd30
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2013 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.hosttracker.test;
+
+import static org.opendaylight.controller.hosttracker.IDeviceService.DeviceField.MAC;
+import static org.opendaylight.controller.hosttracker.IDeviceService.DeviceField.SWITCHPORT;
+import static org.opendaylight.controller.hosttracker.IDeviceService.DeviceField.VLAN;
+
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.opendaylight.controller.hosttracker.Entity;
+import org.opendaylight.controller.hosttracker.IDeviceService;
+import org.opendaylight.controller.hosttracker.IDeviceService.DeviceField;
+import org.opendaylight.controller.hosttracker.IEntityClass;
+import org.opendaylight.controller.hosttracker.internal.DefaultEntityClassifier;
+
+/**
+ * Extension to simple entity classifier to help in unit tests to provide table
+ * based multiple entity classification mock for reclassification tests
+ *
+ */
+public class MockFlexEntityClassifier extends DefaultEntityClassifier {
+    Map<Long, IEntityClass> switchEntities;
+    Map<Short, IEntityClass> vlanEntities;
+
+    public static class TestEntityClass implements IEntityClass {
+        String name;
+
+        public TestEntityClass(String name) {
+            this.name = name;
+        }
+
+        @Override
+        public EnumSet<DeviceField> getKeyFields() {
+            return EnumSet.of(MAC);
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+    }
+
+    public static IEntityClass defaultClass = new TestEntityClass("default");
+
+    public MockFlexEntityClassifier() {
+        switchEntities = new HashMap<Long, IEntityClass>();
+        vlanEntities = new HashMap<Short, IEntityClass>();
+    }
+
+    public IEntityClass createTestEntityClass(String name) {
+        return new TestEntityClass(name);
+    }
+
+    public void addSwitchEntity(Long dpid, IEntityClass entityClass) {
+        switchEntities.put(dpid, entityClass);
+    }
+
+    public void removeSwitchEntity(Long dpid) {
+        switchEntities.remove(dpid);
+    }
+
+    public void addVlanEntities(Short vlan, IEntityClass entityClass) {
+        vlanEntities.put(vlan, entityClass);
+    }
+
+    public void removeVlanEntities(Short vlan) {
+        vlanEntities.remove(vlan);
+    }
+
+    @Override
+    public IEntityClass classifyEntity(Entity entity) {
+        if (switchEntities.containsKey((Long) entity.getPort().getNode()
+                .getID()))
+            return switchEntities
+                    .get((Long) entity.getPort().getNode().getID());
+        if (vlanEntities.containsKey(entity.getVlan()))
+            return vlanEntities.get(entity.getVlan());
+        return defaultClass;
+    }
+
+    @Override
+    public EnumSet<IDeviceService.DeviceField> getKeyFields() {
+        return EnumSet.of(MAC, VLAN, SWITCHPORT);
+    }
+}
index dd51e85f20ee4bd9b6ba2d9e64ac0965a1fc33b2..8fc0d625ebe0dd5b08bff4df864efe6a2e3068bd 100644 (file)
@@ -37,6 +37,8 @@ public class ARP extends Packet {
     public static short REQUEST = (short) 0x1;
     public static short REPLY = (short) 0x2;
 
+    public static short PROTO_TYPE_IP = 0x800;
+
     private static Map<String, Pair<Integer, Integer>> fieldCoordinates = new LinkedHashMap<String, Pair<Integer, Integer>>() {
         private static final long serialVersionUID = 1L;
         {
diff --git a/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/utils/FilterIterator.java b/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/utils/FilterIterator.java
new file mode 100644 (file)
index 0000000..cdd8607
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2012 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * 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.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the
+ *    License. You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an "AS
+ *    IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ *    express or implied. See the License for the specific language
+ *    governing permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.sal.utils;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * An iterator that will filter values from an iterator and return only those
+ * values that match the predicate.
+ */
+public abstract class FilterIterator<T> implements Iterator<T> {
+    protected Iterator<T> subIterator;
+    protected T next;
+
+    /**
+     * Construct a filter iterator from the given sub iterator
+     *
+     * @param subIterator
+     *            the sub iterator over which we'll filter
+     */
+    public FilterIterator(Iterator<T> subIterator) {
+        super();
+        this.subIterator = subIterator;
+    }
+
+    /**
+     * Check whether the given value should be returned by the filter
+     *
+     * @param value
+     *            the value to check
+     * @return true if the value should be included
+     */
+    protected abstract boolean matches(T value);
+
+    // ***********
+    // Iterator<T>
+    // ***********
+
+    @Override
+    public boolean hasNext() {
+        if (next != null)
+            return true;
+
+        while (subIterator.hasNext()) {
+            next = subIterator.next();
+            if (matches(next))
+                return true;
+        }
+        next = null;
+        return false;
+    }
+
+    @Override
+    public T next() {
+        if (hasNext()) {
+            T cur = next;
+            next = null;
+            return cur;
+        }
+        throw new NoSuchElementException();
+    }
+
+    @Override
+    public void remove() {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/utils/IListener.java b/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/utils/IListener.java
new file mode 100644 (file)
index 0000000..4196025
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2011 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * 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.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the
+ *    License. You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an "AS
+ *    IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ *    express or implied. See the License for the specific language
+ *    governing permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.sal.utils;
+
+public interface IListener<T> {
+    public enum Command {
+        CONTINUE, STOP
+    }
+
+    /**
+     * The name assigned to this listener
+     *
+     * @return
+     */
+    public String getName();
+
+    /**
+     * Check if the module called name is a callback ordering prerequisite for
+     * this module. In other words, if this function returns true for the given
+     * name, then this listener will be called after that message listener.
+     *
+     * @param type
+     *            the object type to which this applies
+     * @param name
+     *            the name of the module
+     * @return whether name is a prerequisite.
+     */
+    public boolean isCallbackOrderingPrereq(T type, String name);
+
+    /**
+     * Check if the module called name is a callback ordering post-requisite for
+     * this module. In other words, if this function returns true for the given
+     * name, then this listener will be called before that message listener.
+     *
+     * @param type
+     *            the object type to which this applies
+     * @param name
+     *            the name of the module
+     * @return whether name is a post-requisite.
+     */
+    public boolean isCallbackOrderingPostreq(T type, String name);
+}
diff --git a/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/utils/IterableIterator.java b/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/utils/IterableIterator.java
new file mode 100644 (file)
index 0000000..6d68f42
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2012 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * 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.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the
+ *    License. You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an "AS
+ *    IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ *    express or implied. See the License for the specific language
+ *    governing permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.sal.utils;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Iterator over all values in an iterator of iterators
+ *
+ * @param <T>
+ *            the type of elements returned by this iterator
+ */
+public class IterableIterator<T> implements Iterator<T> {
+    Iterator<? extends Iterable<T>> subIterator;
+    Iterator<T> current = null;
+
+    public IterableIterator(Iterator<? extends Iterable<T>> subIterator) {
+        super();
+        this.subIterator = subIterator;
+    }
+
+    @Override
+    public boolean hasNext() {
+        if (current == null) {
+            if (subIterator.hasNext()) {
+                current = subIterator.next().iterator();
+            } else {
+                return false;
+            }
+        }
+        while (!current.hasNext() && subIterator.hasNext()) {
+            current = subIterator.next().iterator();
+        }
+
+        return current.hasNext();
+    }
+
+    @Override
+    public T next() {
+        if (hasNext())
+            return current.next();
+        throw new NoSuchElementException();
+    }
+
+    @Override
+    public void remove() {
+        if (hasNext())
+            current.remove();
+        throw new NoSuchElementException();
+    }
+}
diff --git a/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/utils/ListenerDispatcher.java b/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/utils/ListenerDispatcher.java
new file mode 100644 (file)
index 0000000..282bac6
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2011 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * 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.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the
+ *    License. You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an "AS
+ *    IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ *    express or implied. See the License for the specific language
+ *    governing permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.sal.utils;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Maintain lists of listeners ordered by dependency.
+ *
+ * @author readams
+ *
+ */
+public class ListenerDispatcher<U, T extends IListener<U>> {
+    protected static Logger logger = LoggerFactory
+            .getLogger(ListenerDispatcher.class);
+    volatile List<T> listeners = new ArrayList<T>();
+
+    private void visit(List<T> newlisteners, U type, HashSet<T> visited,
+            List<T> ordering, T listener) {
+        if (!visited.contains(listener)) {
+            visited.add(listener);
+
+            for (T i : newlisteners) {
+                if (ispre(type, i, listener)) {
+                    visit(newlisteners, type, visited, ordering, i);
+                }
+            }
+            ordering.add(listener);
+        }
+    }
+
+    private boolean ispre(U type, T l1, T l2) {
+        return (l2.isCallbackOrderingPrereq(type, l1.getName()) || l1
+                .isCallbackOrderingPostreq(type, l2.getName()));
+    }
+
+    /**
+     * Add a listener to the list of listeners
+     *
+     * @param listener
+     */
+    public void addListener(U type, T listener) {
+        List<T> newlisteners = new ArrayList<T>();
+        if (listeners != null)
+            newlisteners.addAll(listeners);
+
+        newlisteners.add(listener);
+        // Find nodes without outgoing edges
+        List<T> terminals = new ArrayList<T>();
+        for (T i : newlisteners) {
+            boolean isterm = true;
+            for (T j : newlisteners) {
+                if (ispre(type, i, j)) {
+                    isterm = false;
+                    break;
+                }
+            }
+            if (isterm) {
+                terminals.add(i);
+            }
+        }
+
+        if (terminals.size() == 0) {
+            logger.error("No listener dependency solution: "
+                    + "No listeners without incoming dependencies");
+            listeners = newlisteners;
+            return;
+        }
+
+        // visit depth-first traversing in the opposite order from
+        // the dependencies. Note we will not generally detect cycles
+        HashSet<T> visited = new HashSet<T>();
+        List<T> ordering = new ArrayList<T>();
+        for (T term : terminals) {
+            visit(newlisteners, type, visited, ordering, term);
+        }
+        listeners = ordering;
+    }
+
+    /**
+     * Remove the given listener
+     *
+     * @param listener
+     *            the listener to remove
+     */
+    public void removeListener(T listener) {
+        if (listeners != null) {
+            List<T> newlisteners = new ArrayList<T>();
+            newlisteners.addAll(listeners);
+            newlisteners.remove(listener);
+            listeners = newlisteners;
+        }
+    }
+
+    /**
+     * Clear all listeners
+     */
+    public void clearListeners() {
+        listeners = new ArrayList<T>();
+    }
+
+    /**
+     * Get the ordered list of listeners ordered by dependencies
+     *
+     * @return
+     */
+    public List<T> getOrderedListeners() {
+        return listeners;
+    }
+}
diff --git a/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/utils/MultiIterator.java b/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/utils/MultiIterator.java
new file mode 100644 (file)
index 0000000..42d080d
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2012 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * 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.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the
+ *    License. You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an "AS
+ *    IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ *    express or implied. See the License for the specific language
+ *    governing permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.sal.utils;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Iterator over all values in an iterator of iterators
+ *
+ * @param <T>
+ *            the type of elements returned by this iterator
+ */
+public class MultiIterator<T> implements Iterator<T> {
+    Iterator<Iterator<T>> subIterator;
+    Iterator<T> current = null;
+
+    public MultiIterator(Iterator<Iterator<T>> subIterator) {
+        super();
+        this.subIterator = subIterator;
+    }
+
+    @Override
+    public boolean hasNext() {
+        if (current == null) {
+            if (subIterator.hasNext()) {
+                current = subIterator.next();
+            } else {
+                return false;
+            }
+        }
+        while (!current.hasNext() && subIterator.hasNext()) {
+            current = subIterator.next();
+        }
+
+        return current.hasNext();
+    }
+
+    @Override
+    public T next() {
+        if (hasNext())
+            return current.next();
+        throw new NoSuchElementException();
+    }
+
+    @Override
+    public void remove() {
+        if (hasNext())
+            current.remove();
+        throw new NoSuchElementException();
+    }
+}
diff --git a/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/utils/SingletonTask.java b/opendaylight/sal/api/src/main/java/org/opendaylight/controller/sal/utils/SingletonTask.java
new file mode 100644 (file)
index 0000000..634caac
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2011,2013 Big Switch Networks, Inc.
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * 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.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ *    Originally created by David Erickson, Stanford University
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the
+ *    License. You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an "AS
+ *    IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ *    express or implied. See the License for the specific language
+ *    governing permissions and limitations under the License.
+ */
+
+package org.opendaylight.controller.sal.utils;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This allows you to represent a task that should be queued for future
+ * execution but where you only want the task to complete once in response to
+ * some sequence of events. For example, if you get a change notification and
+ * want to reload state, you only want to reload the state once, at the end, and
+ * don't want to queue an update for every notification that might come in.
+ *
+ * The semantics are as follows: * If the task hasn't begun yet, do not queue a
+ * new task * If the task has begun, set a bit to restart it after the current
+ * task finishes
+ */
+public class SingletonTask {
+    protected static Logger logger = LoggerFactory
+            .getLogger(SingletonTask.class);
+
+    protected static class SingletonTaskContext {
+        protected boolean taskShouldRun = false;
+        protected boolean taskRunning = false;
+
+        protected SingletonTaskWorker waitingTask = null;
+    }
+
+    protected static class SingletonTaskWorker implements Runnable {
+        SingletonTask parent;
+        boolean canceled = false;
+        long nextschedule = 0;
+
+        public SingletonTaskWorker(SingletonTask parent) {
+            super();
+            this.parent = parent;
+        }
+
+        @Override
+        public void run() {
+            synchronized (parent.context) {
+                if (canceled || !parent.context.taskShouldRun)
+                    return;
+
+                parent.context.taskRunning = true;
+                parent.context.taskShouldRun = false;
+            }
+
+            try {
+                parent.task.run();
+            } catch (Exception e) {
+                logger.error("Exception while executing task", e);
+            }
+
+            synchronized (parent.context) {
+                parent.context.taskRunning = false;
+
+                if (parent.context.taskShouldRun) {
+                    long now = System.nanoTime();
+                    if ((nextschedule <= 0 || (nextschedule - now) <= 0)) {
+                        parent.ses.execute(this);
+                    } else {
+                        parent.ses.schedule(this, nextschedule - now,
+                                TimeUnit.NANOSECONDS);
+                    }
+                }
+            }
+        }
+    }
+
+    protected SingletonTaskContext context = new SingletonTaskContext();
+    protected Runnable task;
+    protected ScheduledExecutorService ses;
+
+    /**
+     * Construct a new SingletonTask for the given runnable. The context is used
+     * to manage the state of the task execution and can be shared by more than
+     * one instance of the runnable.
+     *
+     * @param context
+     * @param Task
+     */
+    public SingletonTask(ScheduledExecutorService ses, Runnable task) {
+        super();
+        this.task = task;
+        this.ses = ses;
+    }
+
+    /**
+     * Schedule the task to run if there's not already a task scheduled If there
+     * is such a task waiting that has not already started, it cancel that task
+     * and reschedule it to run at the given time. If the task is already
+     * started, it will cause the task to be rescheduled once it completes to
+     * run after delay from the time of reschedule.
+     *
+     * @param delay
+     *            the delay in scheduling
+     * @param unit
+     *            the timeunit of the delay
+     */
+    public void reschedule(long delay, TimeUnit unit) {
+        boolean needQueue = true;
+        SingletonTaskWorker stw = null;
+
+        synchronized (context) {
+            if (context.taskRunning || context.taskShouldRun) {
+                if (context.taskRunning) {
+                    // schedule to restart at the right time
+                    if (delay > 0) {
+                        long now = System.nanoTime();
+                        long then = now
+                                + TimeUnit.NANOSECONDS.convert(delay, unit);
+                        context.waitingTask.nextschedule = then;
+                    } else {
+                        context.waitingTask.nextschedule = 0;
+                    }
+                    needQueue = false;
+                } else {
+                    // cancel and requeue
+                    context.waitingTask.canceled = true;
+                    context.waitingTask = null;
+                }
+            }
+
+            context.taskShouldRun = true;
+
+            if (needQueue) {
+                stw = context.waitingTask = new SingletonTaskWorker(this);
+            }
+        }
+
+        if (needQueue) {
+            if (delay <= 0)
+                ses.execute(stw);
+            else
+                ses.schedule(stw, delay, unit);
+        }
+    }
+}
\ No newline at end of file