HostTracker Bundle Separation 66/466/6
authorKalvin Hom <kahom@cisco.com>
Wed, 12 Jun 2013 16:56:03 +0000 (09:56 -0700)
committerKalvin Hom <kahom@cisco.com>
Wed, 12 Jun 2013 18:46:14 +0000 (11:46 -0700)
Moved the new HostTracker into
hostracker_new bundles.

Added exclusion to bin.xml so it doesn't
get assembled into distribution.

Added original hosttracker back.

Goal is to allow collaboration and
incremental check-ins for this
module without breaking anything else.

Original information from Rob Adams:
    Author: Rob Adams <rob.adams@bigswitch.com>
    Date:   Wed Jun 5 14:58:02 2013 -0700

    Initial port of device manager to cisco controller.  Currently capable
    of tracking devices but still a long list of TODOs:
    - No VLAN support - Ethernet frame parser can't parse VLAN tags and
      would be a bit of work to add it since current code can't handle
      anything but fixed-format header field which doesn't cover 802.1Q
    - No shared framework for asynchronous tasks.  For now device manager
      just allocates its own scheduled thread pool but a shared thread
      pool would be much better.
    - No clustering support in this code.  There is code that uses an
      eventually-consistent data store to support this but it's commented
      out since this is not available in daylight.  It would be possible
      to port this to the consistent store but this would lose valuable
      semantics and a component in the core should handle eventual
      consistency.
    - No web API support.  Would need to write a new implementation of the
      API; shouldn't be too hard presumably
    - Debug counters not ported
    - No mechanism for documenting log messages.  This doesn't exist in
      ODL.
    - DHCP parsing not supported; disable pulling hostname from DHCP.
    - Node/Port IDs are derived from Object and have no useful semantics.
      Device manager needs them to be comparable in order to get
      attachment points correct and consistent in all cases.  At the
      moment it just blindly casts them
    - Topology module doesn't implement the needed semantics.  First need
      to port net-virt-platform topology code, but there are impedance
      mismatches in the APIs to resolve first
    - Need to merge host tracker and device manager APIs to a new unified
      API and port all existing code to the new API, or adopt the device
      manager API.
    - Need to replace ARP handler to just handle ARPs and not be
      responsible for trying to discover devices
    - Unit tests need to be ported as well; currently commented out

Change-Id: If65e6582599a9c6fafaabfeb4b93b1d931965ab1
Signed-off-by: Kalvin Hom <kahom@cisco.com>
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