ONOS-2197 Adding some logic to keep track of expired IP Lease Assignments

Change-Id: I330e5fbf790f0a4fe72f1fbce272617837225e64
diff --git a/onos-app-dhcpserver/src/main/java/org/onosproject/dhcpserver/DHCPStore.java b/onos-app-dhcpserver/src/main/java/org/onosproject/dhcpserver/DHCPStore.java
index b6c60a2..29cb277 100644
--- a/onos-app-dhcpserver/src/main/java/org/onosproject/dhcpserver/DHCPStore.java
+++ b/onos-app-dhcpserver/src/main/java/org/onosproject/dhcpserver/DHCPStore.java
@@ -31,7 +31,7 @@
      * @param macID Mac ID of the client requesting an IP
      * @return IP address assigned to the Mac ID
      */
-    Ip4Address suggestIP(MacAddress macID);
+    Ip4Address suggestIP(MacAddress macID, Ip4Address requestedIP);
 
     /**
      * Assigns the requested IP to the Mac ID, in response to a DHCP REQUEST message.
diff --git a/onos-app-dhcpserver/src/main/java/org/onosproject/dhcpserver/IPAssignment.java b/onos-app-dhcpserver/src/main/java/org/onosproject/dhcpserver/IPAssignment.java
index 56cf3da..1129d2a 100644
--- a/onos-app-dhcpserver/src/main/java/org/onosproject/dhcpserver/IPAssignment.java
+++ b/onos-app-dhcpserver/src/main/java/org/onosproject/dhcpserver/IPAssignment.java
@@ -126,6 +126,15 @@
     }
 
     /**
+     * Creates and returns a new builder instance that clones an existing IPAssignment.
+     *
+     * @return new builder
+     */
+    public static Builder builder(IPAssignment assignment) {
+        return new Builder(assignment);
+    }
+
+    /**
      * IPAssignment Builder.
      */
     public static final class Builder {
diff --git a/onos-app-dhcpserver/src/main/java/org/onosproject/dhcpserver/impl/DHCPManager.java b/onos-app-dhcpserver/src/main/java/org/onosproject/dhcpserver/impl/DHCPManager.java
index 3190a54..ee995d0 100644
--- a/onos-app-dhcpserver/src/main/java/org/onosproject/dhcpserver/impl/DHCPManager.java
+++ b/onos-app-dhcpserver/src/main/java/org/onosproject/dhcpserver/impl/DHCPManager.java
@@ -365,7 +365,7 @@
                 if (incomingPacketType == DHCP.DHCPMessageType.MessageType_Discover.getValue()) {
 
                     outgoingPacketType = DHCP.DHCPMessageType.MessageType_Offer;
-                    ipOffered = dhcpStore.suggestIP(clientMAC).toString();
+                    ipOffered = dhcpStore.suggestIP(clientMAC, requestedIP).toString();
 
                     Ethernet ethReply = buildReply(packet, ipOffered, outgoingPacketType.getValue());
                     sendReply(context, ethReply);
diff --git a/onos-app-dhcpserver/src/main/java/org/onosproject/dhcpserver/impl/DistributedDHCPStore.java b/onos-app-dhcpserver/src/main/java/org/onosproject/dhcpserver/impl/DistributedDHCPStore.java
index 1ad4bc5..7344e6c 100644
--- a/onos-app-dhcpserver/src/main/java/org/onosproject/dhcpserver/impl/DistributedDHCPStore.java
+++ b/onos-app-dhcpserver/src/main/java/org/onosproject/dhcpserver/impl/DistributedDHCPStore.java
@@ -107,26 +107,64 @@
     }
 
     @Override
-    public Ip4Address suggestIP(MacAddress macID) {
+    public Ip4Address suggestIP(MacAddress macID, Ip4Address requestedIP) {
 
         IPAssignment assignmentInfo;
         if (allocationMap.containsKey(macID)) {
             assignmentInfo = allocationMap.get(macID).value();
+            IPAssignment.AssignmentStatus status = assignmentInfo.assignmentStatus();
+            Ip4Address ipAddr = assignmentInfo.ipAddress();
+
+            if (status == IPAssignment.AssignmentStatus.Option_Assigned ||
+                    status == IPAssignment.AssignmentStatus.Option_Requested) {
+                // Client has a currently Active Binding.
+                return ipAddr;
+
+            } else if (status == IPAssignment.AssignmentStatus.Option_Expired) {
+                // Client has a Released or Expired Binding.
+                if (freeIPPool.contains(ipAddr)) {
+                    assignmentInfo = IPAssignment.builder()
+                            .ipAddress(ipAddr)
+                            .timestamp(new Date())
+                            .leasePeriod(timeoutForPendingAssignments)
+                            .assignmentStatus(IPAssignment.AssignmentStatus.Option_Requested)
+                            .build();
+                    if (freeIPPool.remove(ipAddr)) {
+                        allocationMap.put(macID, assignmentInfo);
+                        return ipAddr;
+                    }
+                }
+            }
             return assignmentInfo.ipAddress();
-        } else {
-            Ip4Address nextIPAddr = fetchNextIP();
 
-
-            assignmentInfo = IPAssignment.builder()
-                                        .ipAddress(nextIPAddr)
-                                        .timestamp(new Date())
-                                        .leasePeriod(timeoutForPendingAssignments)
-                                        .assignmentStatus(IPAssignment.AssignmentStatus.Option_Requested)
-                                        .build();
-
-            allocationMap.put(macID, assignmentInfo);
-            return nextIPAddr;
+        } else if (requestedIP.toInt() != 0) {
+            // Client has requested an IP.
+            if (freeIPPool.contains(requestedIP)) {
+                assignmentInfo = IPAssignment.builder()
+                        .ipAddress(requestedIP)
+                        .timestamp(new Date())
+                        .leasePeriod(timeoutForPendingAssignments)
+                        .assignmentStatus(IPAssignment.AssignmentStatus.Option_Requested)
+                        .build();
+                if (freeIPPool.remove(requestedIP)) {
+                    allocationMap.put(macID, assignmentInfo);
+                    return requestedIP;
+                }
+            }
         }
+
+        // Allocate a new IP from the server's pool of available IP.
+        Ip4Address nextIPAddr = fetchNextIP();
+        assignmentInfo = IPAssignment.builder()
+                                    .ipAddress(nextIPAddr)
+                                    .timestamp(new Date())
+                                    .leasePeriod(timeoutForPendingAssignments)
+                                    .assignmentStatus(IPAssignment.AssignmentStatus.Option_Requested)
+                                    .build();
+
+        allocationMap.put(macID, assignmentInfo);
+        return nextIPAddr;
+
     }
 
     @Override
@@ -164,8 +202,11 @@
     @Override
     public void releaseIP(MacAddress macID) {
         if (allocationMap.containsKey(macID)) {
-            Ip4Address freeIP = allocationMap.get(macID).value().ipAddress();
-            allocationMap.remove(macID);
+            IPAssignment newAssignment = IPAssignment.builder(allocationMap.get(macID).value())
+                                                    .assignmentStatus(IPAssignment.AssignmentStatus.Option_Expired)
+                                                    .build();
+            Ip4Address freeIP = newAssignment.ipAddress();
+            allocationMap.put(macID, newAssignment);
             freeIPPool.add(freeIP);
         }
     }
@@ -249,14 +290,19 @@
 
         @Override
         public void run(Timeout to) {
-            IPAssignment ipAssignment;
+            IPAssignment ipAssignment, newAssignment;
             Date dateNow = new Date();
             for (Map.Entry<MacAddress, Versioned<IPAssignment>> entry: allocationMap.entrySet()) {
                 ipAssignment = entry.getValue().value();
                 long timeLapsed = dateNow.getTime() - ipAssignment.timestamp().getTime();
-                if ((ipAssignment.leasePeriod() > 0) && (timeLapsed > (ipAssignment.leasePeriod()))) {
+                if ((ipAssignment.assignmentStatus() != IPAssignment.AssignmentStatus.Option_Expired) &&
+                        (ipAssignment.leasePeriod() > 0) && (timeLapsed > (ipAssignment.leasePeriod()))) {
                     Ip4Address freeIP = ipAssignment.ipAddress();
-                    allocationMap.remove(entry.getKey());
+
+                    newAssignment = IPAssignment.builder(ipAssignment)
+                                                .assignmentStatus(IPAssignment.AssignmentStatus.Option_Expired)
+                                                .build();
+                    allocationMap.put(entry.getKey(), newAssignment);
                     freeIPPool.add(freeIP);
                 }
             }