From c5061230cdddcfef56d1845334f3c8d75116d6e7 Mon Sep 17 00:00:00 2001
From: Gregor Maier <gregor.maier@bigswitch.com>
Date: Fri, 9 Nov 2012 15:23:29 -0800
Subject: [PATCH] Several improvements and bug fixes for Device Manager:

BSC-2837: When returning early from learnDeviceByEntity: process
	deleteQueue
BSC-2836: Do not learn entities on internal ports
BSC-2834: Some device updates get queued before check for concurrent
	modification
BSC-2824: Remove potential incorrect Corrupted Device Index exception
---
 .../internal/DeviceManagerImpl.java           | 133 ++++++++-------
 .../devicemanager/internal/Entity.java        |   6 +
 .../internal/DeviceManagerImplTest.java       | 160 ++++++++++++++++--
 3 files changed, 222 insertions(+), 77 deletions(-)

diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java
index 6c6aa4cef..db04452f0 100755
--- a/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java
+++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImpl.java
@@ -1052,7 +1052,8 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
                 entityClass = entityClassifier.classifyEntity(entity);
                 if (entityClass == null) {
                     // could not classify entity. No device
-                    return null;
+                    device = null;
+                    break;
                 }
                 ClassState classState = getClassState(entityClass);
 
@@ -1066,17 +1067,36 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
                 // 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)
-                    throw new IllegalStateException("Corrupted device index");
+                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);
+                    }
+                }
             } else {
                 // If the secondary index does not contain the entity,
                 // create a new Device object containing the entity, and
-                // generate a new device ID. However, we first check if 
+                // generate a new device ID if the the entity is on an 
+                // attachment point port. Otherwise ignore. 
+                if (entity.hasSwitchPort() &&
+                        !this.isValidAttachmentPoint(entity.getSwitchDPID(), 
+                                                 entity.getSwitchPort())) {
+                    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)) {
                     logger.info("PacketIn is not allowed {} {}", 
                                 entityClass.getName(), entity);
-                    return null;
+                    device = null;
+                    break;
                 }
                 synchronized (deviceKeyLock) {
                     deviceKey = Long.valueOf(deviceKeyCounter++);
@@ -1113,59 +1133,36 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
                             device.getEntityClass().getName(), entity);
                 return null;
             }
+            // If this is an internal port we don't learn the new entity
+            // and don't update indexes. We only learn on attachment point
+            // ports.
+            if (entity.hasSwitchPort() &&
+                    !this.isValidAttachmentPoint(entity.getSwitchDPID(), 
+                                                 entity.getSwitchPort())) {
+                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();
-                device.entities[entityindex].setLastSeenTimestamp(lastSeen);
-                if (device.entities[entityindex].getSwitchDPID() != null &&
-                        device.entities[entityindex].getSwitchPort() != null) {
-                    long sw = device.entities[entityindex].getSwitchDPID();
-                    short port = device.entities[entityindex].getSwitchPort().shortValue();
-
-                    boolean moved =
-                            device.updateAttachmentPoint(sw,
-                                                         port,
-                                                         lastSeen.getTime());
-
-                    if (moved) {
-                        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 NOT updated: " +
-                                         "attachment points {}," +
-                                         "entities {}", device.attachmentPoints,
-                                         device.entities);
-                        }
-                    }
+                if (lastSeen == null) {
+                    lastSeen = new Date();
+                    entity.setLastSeenTimestamp(lastSeen);
                 }
-                break;
+                device.entities[entityindex].setLastSeenTimestamp(lastSeen);
+                // we break the loop after the else block and after checking
+                // for new AP
             } else {
-                boolean moved = false;
+                // New entity for this devce
                 // compute the insertion point for the entity.
                 // see Arrays.binarySearch()
                 entityindex = -(entityindex + 1);
                 Device newDevice = allocateDevice(device, entity, entityindex);
-                if (entity.getSwitchDPID() != null && entity.getSwitchPort() != null) {
-                    moved = newDevice.updateAttachmentPoint(entity.getSwitchDPID(),
-                                                            entity.getSwitchPort().shortValue(),
-                                                            entity.getLastSeenTimestamp().getTime());
-                }
-
+ 
                 // generate updates
                 EnumSet<DeviceField> changedFields =
                         findChangedFields(device, entity);
-                if (changedFields.size() > 0)
-                    deviceUpdates =
-                    updateUpdates(deviceUpdates,
-                                  new DeviceUpdate(newDevice, CHANGE,
-                                                   changedFields));
 
                 // update the device map with a replace call
                 boolean res = deviceMap.replace(deviceKey, device, newDevice);
@@ -1176,7 +1173,6 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
                     continue;
 
                 device = newDevice;
-
                 // update indices
                 if (!updateIndices(device, deviceKey)) {
                     continue;
@@ -1184,36 +1180,45 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
                 updateSecondaryIndices(entity,
                                        device.getEntityClass(),
                                        deviceKey);
-
+                
+                if (changedFields.size() > 0) {
+                    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.getSwitchDPID(),
+                                entity.getSwitchPort().shortValue(),
+                                entity.getLastSeenTimestamp().getTime());
                 if (moved) {
                     sendDeviceMovedNotification(device);
-                    if (logger.isDebugEnabled()) {
-                        logger.debug("Device moved: attachment points {}," +
+                    if (logger.isTraceEnabled()) {
+                        logger.trace("Device moved: attachment points {}," +
                                 "entities {}", device.attachmentPoints,
                                 device.entities);
                     }
                 } else {
-                    if (logger.isDebugEnabled()) {
-                        logger.debug("Device attachment point updated: " +
+                    if (logger.isTraceEnabled()) {
+                        logger.trace("Device attachment point updated: " +
                                      "attachment points {}," +
                                      "entities {}", device.attachmentPoints,
                                      device.entities);
                     }
                 }
-                break;
             }
+            break; 
         }
 
         if (deleteQueue != null) {
             for (Long l : deleteQueue) {
                 Device dev = deviceMap.get(l);
                 this.deleteDevice(dev);
-                
-
-                // generate new device update
-                deviceUpdates =
-                        updateUpdates(deviceUpdates,
-                                      new DeviceUpdate(dev, DELETE, null));
             }
         }
 
@@ -1482,9 +1487,9 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
                     for (Entity e : toRemove) {
                         changedFields.addAll(findChangedFields(newDevice, e));
                     }
+                    DeviceUpdate update = null;
                     if (changedFields.size() > 0)
-                        deviceUpdates.add(new DeviceUpdate(d, CHANGE,
-                                                           changedFields));
+                        update = new DeviceUpdate(d, CHANGE, changedFields);
 
                     if (!deviceMap.replace(newDevice.getDeviceKey(),
                                            d,
@@ -1496,15 +1501,19 @@ IFlowReconcileListener, IInfoProvider, IHAListener {
                         if (null != d)
                             continue;
                     }
+                    if (update != null)
+                        deviceUpdates.add(update);
                 } else {
-                    deviceUpdates.add(new DeviceUpdate(d, DELETE, null));
-                    if (!deviceMap.remove(d.getDeviceKey(), d))
+                    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;
+                    }
+                    deviceUpdates.add(update);
                 }
                 processUpdates(deviceUpdates);
                 break;
diff --git a/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java b/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java
index 36c5471fb..5949a19e2 100644
--- a/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java
+++ b/src/main/java/net/floodlightcontroller/devicemanager/internal/Entity.java
@@ -24,6 +24,7 @@ import net.floodlightcontroller.core.web.serializers.MACSerializer;
 import net.floodlightcontroller.core.web.serializers.DPIDSerializer;
 import net.floodlightcontroller.packet.IPv4;
 
+import org.codehaus.jackson.annotate.JsonIgnore;
 import org.codehaus.jackson.map.annotate.JsonSerialize;
 import org.openflow.util.HexString;
 
@@ -145,6 +146,11 @@ public class Entity implements Comparable<Entity> {
     public Integer getSwitchPort() {
         return switchPort;
     }
+    
+    @JsonIgnore
+    public boolean hasSwitchPort() {
+        return (switchDPID != null && switchPort != null);
+    }
 
     public Date getLastSeenTimestamp() {
         return lastSeenTimestamp;
diff --git a/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java b/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java
index 855a23ffd..5ac0a9c3b 100644
--- a/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java
+++ b/src/test/java/net/floodlightcontroller/devicemanager/internal/DeviceManagerImplTest.java
@@ -567,11 +567,131 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
         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);
+
+        deviceManager.addListener(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 =
-                createStrictMock(IDeviceListener.class);
+                createMock(IDeviceListener.class);
 
         deviceManager.addListener(mockListener);
 
@@ -584,15 +704,16 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
         andReturn(10L).anyTimes();
         expect(mockTopology.getL2DomainId(50L)).
         andReturn(10L).anyTimes();
-        expect(mockTopology.isBroadcastDomainPort(anyLong(), anyShort())).
-        andReturn(false).anyTimes();
+        expect(mockTopology.isBroadcastDomainPort(anyLong(), anyShort()))
+                .andReturn(false).anyTimes();
         expect(mockTopology.isInSameBroadcastDomain(anyLong(), anyShort(),
-                                                    anyLong(), anyShort())).andReturn(false).anyTimes();
+                                                    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();
+        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).
@@ -606,10 +727,10 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
         deviceManager.addSuppressAPs(10L, (short)1);
 
         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());
+        Entity entity1 = new Entity(1L, null, 1, 1L, 1, c.getTime());
         c.add(Calendar.SECOND, 1);
-        Entity entity2 = new Entity(1L, null, null, 5L, 1, c.getTime());
+        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);
@@ -621,39 +742,47 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
 
         mockListener.deviceAdded(isA(IDevice.class));
         replay(mockListener);
+        
+        // cannot learn device on suppressed AP
+        d = deviceManager.learnDeviceByEntity(entity1);
+        assertNull(d);
 
-        deviceManager.learnDeviceByEntity(entity1);
-        d = deviceManager.learnDeviceByEntity(entity0);
+        deviceManager.learnDeviceByEntity(entity0);
+        d = deviceManager.learnDeviceByEntity(entity1);
         assertEquals(1, deviceManager.getAllDevices().size());
         aps = d.getAttachmentPoints();
         assertEquals(aps.length, 0);
+        // entities from suppressed switchPorts will not be learned 
+        verifyEntityArray(new Entity[] { entity0} , (Device)d);
         ips = d.getIPv4Addresses();
-        assertArrayEquals(new Integer[] { 1 }, ips);
+        assertArrayEquals(new Integer[] { }, 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, entity2} , (Device)d);
         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) }, aps);
+        verifyEntityArray(new Entity[] { entity0, entity2} , (Device)d);
         ips = d.getIPv4Addresses();
         assertArrayEquals(new Integer[] { 1 }, ips);
-        //verify(mockListener);  // There is no device movement here; no not needed.
+        verify(mockListener);  
 
         reset(mockListener);
         mockListener.deviceMoved((isA(IDevice.class)));
@@ -664,6 +793,7 @@ public class DeviceManagerImplTest extends FloodlightTestCase {
         aps = d.getAttachmentPoints();
         assertArrayEquals(new SwitchPort[] { new SwitchPort(5L, 1),
                                              new SwitchPort(50L, 1) }, aps);
+        verifyEntityArray(new Entity[] { entity0, entity2, entity4} , (Device)d);
         ips = d.getIPv4Addresses();
         assertArrayEquals(new Integer[] { 1 }, ips);
         verify(mockListener);
-- 
GitLab