[ONOS-6098] Port number used for SNAT does not return to the available port number pool after the SNAT rule expired

Change-Id: I718c3460d4076b8084d3a338401d3ab394c1eccb
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingSnatHandler.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingSnatHandler.java
index 5b6868b..a1a1899 100644
--- a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingSnatHandler.java
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingSnatHandler.java
@@ -53,6 +53,7 @@
 import org.onosproject.scalablegateway.api.ScalableGatewayService;
 import org.onosproject.store.serializers.KryoNamespaces;
 import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.DistributedSet;
 import org.onosproject.store.service.Serializer;
 import org.onosproject.store.service.StorageService;
 import org.openstack4j.model.network.ExternalGateway;
@@ -83,7 +84,7 @@
 
     private static final String ERR_PACKETIN = "Failed to handle packet in: ";
     private static final int TIME_OUT_SNAT_RULE = 120;
-    private static final int TIME_OUT_SNAT_PORT = 1200 * 1000;
+    private static final long TIME_OUT_SNAT_PORT_MS = 120 * 1000;
     private static final int TP_PORT_MINIMUM_NUM = 1024;
     private static final int TP_PORT_MAXIMUM_NUM = 65535;
 
@@ -124,22 +125,42 @@
             groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
     private final InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
 
-    private ConsistentMap<Integer, String> tpPortNumMap;
+    private ConsistentMap<Integer, Long> allocatedPortNumMap;
+    private DistributedSet<Integer> unUsedPortNumSet;
     private ApplicationId appId;
 
     @Activate
     protected void activate() {
         appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
-        tpPortNumMap = storageService.<Integer, String>consistentMapBuilder()
+
+        allocatedPortNumMap = storageService.<Integer, Long>consistentMapBuilder()
                 .withSerializer(Serializer.using(NUMBER_SERIALIZER.build()))
-                .withName("openstackrouting-tpportnum")
+                .withName("openstackrouting-allocatedportnummap")
                 .withApplicationId(appId)
                 .build();
 
+        unUsedPortNumSet = storageService.<Integer>setBuilder()
+                .withName("openstackrouting-unusedportnumset")
+                .withSerializer(Serializer.using(KryoNamespaces.API))
+                .build()
+                .asDistributedSet();
+
+        initializeUnusedPortNumSet();
+
         packetService.addProcessor(packetProcessor, PacketProcessor.director(1));
         log.info("Started");
     }
 
+    private void initializeUnusedPortNumSet() {
+        for (int i = TP_PORT_MINIMUM_NUM; i < TP_PORT_MAXIMUM_NUM; i++) {
+            if (!allocatedPortNumMap.containsKey(Integer.valueOf(i))) {
+                unUsedPortNumSet.add(Integer.valueOf(i));
+            }
+        }
+
+        clearPortNumMap();
+    }
+
     @Deactivate
     protected void deactivate() {
         packetService.removeProcessor(packetProcessor);
@@ -166,6 +187,11 @@
         if (externalGatewayIp == null) {
             return;
         }
+        if (patPort == 0) {
+            log.error("There's no unused port for external ip {}... Drop this packet",
+                    getExternalIp(srcSubnet));
+            return;
+        }
 
         populateSnatFlowRules(context.inPacket(),
                 srcInstPort,
@@ -399,28 +425,30 @@
     }
 
     private int getPortNum(MacAddress sourceMac, int destinationAddress) {
-        int portNum = findUnusedPortNum();
-        if (portNum == 0) {
+        if (unUsedPortNumSet.isEmpty()) {
             clearPortNumMap();
-            portNum = findUnusedPortNum();
         }
-        tpPortNumMap.put(portNum, sourceMac.toString().concat(":").concat(String.valueOf(destinationAddress)));
+
+        int portNum = findUnusedPortNum();
+
+        if (portNum != 0) {
+            unUsedPortNumSet.remove(Integer.valueOf(portNum));
+            allocatedPortNumMap
+                    .put(Integer.valueOf(portNum), Long.valueOf(System.currentTimeMillis()));
+        }
+
         return portNum;
     }
 
     private int findUnusedPortNum() {
-        for (int i = TP_PORT_MINIMUM_NUM; i < TP_PORT_MAXIMUM_NUM; i++) {
-            if (!tpPortNumMap.containsKey(i)) {
-                return i;
-            }
-        }
-        return 0;
+        return unUsedPortNumSet.stream().findAny().orElse(Integer.valueOf(0)).intValue();
     }
 
     private void clearPortNumMap() {
-        tpPortNumMap.entrySet().forEach(e -> {
-            if (System.currentTimeMillis() - e.getValue().creationTime() > TIME_OUT_SNAT_PORT) {
-                tpPortNumMap.remove(e.getKey());
+        allocatedPortNumMap.entrySet().forEach(e -> {
+            if (System.currentTimeMillis() - e.getValue().value().longValue() > TIME_OUT_SNAT_PORT_MS) {
+                allocatedPortNumMap.remove(e.getKey());
+                unUsedPortNumSet.add(e.getKey());
             }
         });
     }