Cherry-pick from https://gerrit.onos.onlab.us/#/c/342/

Cherry-pick from https://gerrit.onos.onlab.us/#/c/322/

Suppress device update for the same device.
Add function to clean devices.

Conflicts:

	src/main/java/net/onrc/onos/ofcontroller/devicemanager/OnosDeviceManager.java

NOTE: The above conflict has been resolved by hand.
The conflict was in the number of parameters when
calling deviceStorage.init() inside file OnosDeviceManager.java:
deviceStorage.init("") -> deviceStorage.init("","")

Conflicts:

	src/main/java/net/onrc/onos/datagrid/HazelcastDatagrid.java
	src/main/java/net/onrc/onos/datagrid/IDatagridService.java

NOTE: The above conflict has been resolved by hand.

Change-Id: I831e391efce9a795370be99238834b6bc7db2bee
diff --git a/src/main/java/net/onrc/onos/ofcontroller/devicemanager/OnosDeviceManager.java b/src/main/java/net/onrc/onos/ofcontroller/devicemanager/OnosDeviceManager.java
index 1b14875..e9ddfa7 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/devicemanager/OnosDeviceManager.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/devicemanager/OnosDeviceManager.java
@@ -4,8 +4,14 @@
 import java.util.Collection;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
 
 import net.floodlightcontroller.core.FloodlightContext;
 import net.floodlightcontroller.core.IFloodlightProviderService;
@@ -22,29 +28,51 @@
 import net.floodlightcontroller.packet.IPv4;
 import net.floodlightcontroller.packet.UDP;
 import net.floodlightcontroller.util.MACAddress;
+import net.onrc.onos.datagrid.IDatagridService;
 import net.onrc.onos.ofcontroller.core.IDeviceStorage;
 import net.onrc.onos.ofcontroller.core.internal.DeviceStorageImpl;
+import net.onrc.onos.ofcontroller.topology.TopologyElement;
+import net.onrc.onos.ofcontroller.topology.TopologyElement.Type;
 
 import org.openflow.protocol.OFMessage;
 import org.openflow.protocol.OFPacketIn;
 import org.openflow.protocol.OFType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class OnosDeviceManager implements IFloodlightModule, IOFMessageListener,
 										IOnosDeviceService {
-	private IDeviceStorage deviceStorage;
+	protected final static Logger log = LoggerFactory.getLogger(OnosDeviceManager.class);
+	private static final int CLEANUP_SECOND = 60*60;
+	private static final int AGEING_MILLSEC = 60*60*1000;
 	
+	private IDeviceStorage deviceStorage;
 	private IFloodlightProviderService floodlightProvider;
-
+	private final static ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
+	
+	private IDatagridService datagrid;
+	private Map<Long, OnosDevice> mapDevice = new ConcurrentHashMap<Long, OnosDevice>();
+	
+    public enum OnosDeviceUpdateType {
+        ADD, DELETE, UPDATE;
+    }
+	
 	private class OnosDeviceUpdate implements IUpdate {
 		private OnosDevice device;
+		private OnosDeviceUpdateType type;
 		
-		public OnosDeviceUpdate(OnosDevice device) {
+		public OnosDeviceUpdate(OnosDevice device, OnosDeviceUpdateType type) {
 			this.device = device;
+			this.type = type;
 		}
 		
 		@Override
 		public void dispatch() {
-			deviceStorage.addOnosDevice(device);
+			if(type == OnosDeviceUpdateType.ADD) {
+				deviceStorage.addOnosDevice(device);		
+			} else if (type == OnosDeviceUpdateType.DELETE){
+				deviceStorage.deleteOnosDevice(device);		
+			}
 		}
 	}
 	
@@ -81,18 +109,98 @@
 	}
 	
 	private Command processPacketIn(IOFSwitch sw, OFPacketIn pi, Ethernet eth) {
-		
-        // Extract source entity information
+        long dpid =sw.getId();
+        short portId = pi.getInPort();
+        Long mac = eth.getSourceMAC().toLong();
+
         OnosDevice srcDevice =
-                getSourceDeviceFromPacket(eth, sw.getId(), pi.getInPort());
-        if (srcDevice == null)
-            return Command.STOP;
+                getSourceDeviceFromPacket(eth, dpid, portId);
+
+        if (srcDevice == null){
+        	return Command.STOP;
+        }
         
-        floodlightProvider.publishUpdate(new OnosDeviceUpdate(srcDevice));
+        //We check if it is the same device in datagrid to suppress the device update
+        OnosDevice exDev = null;
+        if((exDev = mapDevice.get(mac)) != null ){
+	    	if(exDev.equals(srcDevice)) {
+	    		//There is the same existing device. Update only ActiveSince time.
+	        	exDev.setLastSeenTimestamp(new Date());
+	        	if(log.isTraceEnabled()) {
+			        log.debug("In the datagrid, there is the same device."
+							+ "Only update last seen time. dpid {}, port {}, mac {}, ip {}, lastSeenTime {}",
+			        		dpid, portId, srcDevice.getMacAddress(), srcDevice.getIpv4Address(), srcDevice.getLastSeenTimestamp().getTime());
+	        	}
+		        return Command.CONTINUE;
+	    	} else if (srcDevice.getIpv4Address() == null && 
+	    			exDev.getSwitchDPID() == srcDevice.getSwitchDPID() &&
+	    			exDev.getSwitchPort() == srcDevice.getSwitchPort() &&
+	    			exDev.getVlan() == srcDevice.getVlan()) {
+	    		//Device attachment point and mac address are the same 
+	    		//but the packet does not have an ip address.
+	        	exDev.setLastSeenTimestamp(new Date());
+	        	if(log.isTraceEnabled()) {
+		        	log.debug("In the datagrid, there is the same device with no ip."
+							+ "Keep ip and update last seen time. dpid {}, port {}, mac {}, ip {}, lastSeenTime {}",
+							dpid, portId, srcDevice.getMacAddress(), srcDevice.getIpv4Address(), srcDevice.getLastSeenTimestamp().getTime());
+	        	}
+	        	return Command.CONTINUE;
+	    	}
+        }
         
+        //If the switch port we try to attach a new device already has a link, then stop adding device
+        Collection<TopologyElement> list = datagrid.getAllTopologyElements();
+        for(TopologyElement elem: list) {
+        	if(elem.getType() == Type.ELEMENT_LINK) {
+        		if((elem.getFromPort() == portId && elem.getFromSwitch() == dpid) ||
+        				(elem.getToPort() == portId && elem.getToSwitch() == dpid)) {
+        			if(log.isTraceEnabled()) {
+	        			log.debug("Stop adding OnosDevice {} due to there is a link to: dpid {} port {}",
+	    						srcDevice.getMacAddress(), dpid, portId);
+        			}
+        			return Command.CONTINUE;
+        		}
+        	}
+        }
+        
+        addOnosDevice(mac, srcDevice);
+        
+        if(log.isTraceEnabled()) {
+	        log.debug("Add device info in the set. dpid {}, port {}, mac {}, ip {}, lastSeenTime {}",
+	       		dpid, portId, srcDevice.getMacAddress(), srcDevice.getIpv4Address(), srcDevice.getLastSeenTimestamp().getTime());
+        }
         return Command.CONTINUE;
 	}
 	
+     //Thread to delete devices periodically. 
+	 //Remove all devices from the map first and then finally delete devices from the DB.
+	private class CleanDevice implements Runnable {
+		@Override
+		public void run() {
+			log.debug("called CleanDevice");
+			try{
+		        	Set<OnosDevice> deleteSet = new HashSet<OnosDevice>();
+			        for (OnosDevice dev : mapDevice.values() ) {
+			        	long now = new Date().getTime();
+			        	if((now - dev.getLastSeenTimestamp().getTime() > AGEING_MILLSEC)) {
+			        		if(log.isTraceEnabled()) {
+			        			log.debug("Remove device info in the datagrid. dpid {}, port {}, mac {}, ip {}, lastSeenTime {}, diff {}",
+				        				dev.getSwitchDPID(), dev.getSwitchPort(), dev.getMacAddress(), dev.getIpv4Address(),
+				        				dev.getLastSeenTimestamp().getTime(), now - dev.getLastSeenTimestamp().getTime());
+			        		}
+			        		deleteSet.add(dev);
+			        	}
+			        }
+			     
+			        for(OnosDevice dev : deleteSet) {
+			        	deleteOnosDevice(dev);        	
+			        }
+			 } catch(Exception e) {
+		    	 log.error("Error:", e);
+		     }
+		}
+	}
+
     /**
      * Get IP address from packet if the packet is either an ARP 
      * or a DHCP packet
@@ -177,14 +285,50 @@
 	public void init(FloodlightModuleContext context)
 			throws FloodlightModuleException {
 		floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class);
-		
+		executor.scheduleAtFixedRate(new CleanDevice(), 30 ,CLEANUP_SECOND, TimeUnit.SECONDS);
 		deviceStorage = new DeviceStorageImpl();
 		deviceStorage.init("","");
+		
+		datagrid = context.getServiceImpl(IDatagridService.class);
 	}
 
 	@Override
 	public void startUp(FloodlightModuleContext context) {
 		floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
+		datagrid.registerMapDeviceEventHandler(new MapDevListener());
 	}
 
+	@Override
+	public void deleteOnosDevice(OnosDevice dev) {
+		datagrid.sendNotificationDeviceDeleted(dev);
+		floodlightProvider.publishUpdate(new OnosDeviceUpdate(dev, OnosDeviceUpdateType.DELETE));
+	}
+	
+	@Override
+	public void addOnosDevice(Long mac, OnosDevice dev) {
+        datagrid.sendNotificationDeviceAdded(mac, dev);
+        floodlightProvider.publishUpdate(new OnosDeviceUpdate(dev, OnosDeviceUpdateType.ADD));
+	}
+	
+	//This is listener for datagrid mapDevice change.
+    class MapDevListener implements IDeviceEventHandler {
+
+		@Override
+		public void addDeviceEvent(Long mac, OnosDevice dev) {	
+			mapDevice.put(mac, dev);
+			log.debug("addDeviceMap: device mac {}", mac);
+		}
+
+		@Override
+		public void deleteDeviceEvent(Long mac, OnosDevice dev) {	
+			mapDevice.remove(mac);
+			log.debug("deleteDeviceMap: device mac {}", mac);	
+		}
+
+		@Override
+		public void updateDeviceEvent(Long mac, OnosDevice dev) {	
+			mapDevice.put(mac, dev);
+			log.debug("updateDeviceMap: device mac {}", mac);
+		}
+    }
 }