ONOS-4858 Resubmit pending intents with same timestamp

This will avoid needless recomputation if the intent processing is delayed.

Change-Id: I851c4ce65271a250da89f919886a3f26a774d20c
diff --git a/core/api/src/main/java/org/onosproject/net/intent/IntentService.java b/core/api/src/main/java/org/onosproject/net/intent/IntentService.java
index 9d947ea..d8143bd 100644
--- a/core/api/src/main/java/org/onosproject/net/intent/IntentService.java
+++ b/core/api/src/main/java/org/onosproject/net/intent/IntentService.java
@@ -72,6 +72,17 @@
     Iterable<Intent> getIntents();
 
     /**
+     * Adds an intent data object to the pending map for processing.
+     * <p>
+     * This method is intended to only be called by core components, not
+     * applications.
+     * </p>
+     *
+     * @param intentData intent data to be added to pending map
+     */
+    void addPending(IntentData intentData);
+
+    /**
      * Returns an iterable of intent data objects currently in the system.
      *
      * @return set of intent data objects
diff --git a/core/api/src/test/java/org/onosproject/net/intent/FakeIntentManager.java b/core/api/src/test/java/org/onosproject/net/intent/FakeIntentManager.java
index 1ceb3b2..02e8975 100644
--- a/core/api/src/test/java/org/onosproject/net/intent/FakeIntentManager.java
+++ b/core/api/src/test/java/org/onosproject/net/intent/FakeIntentManager.java
@@ -176,6 +176,11 @@
     }
 
     @Override
+    public void addPending(IntentData intentData) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public Iterable<IntentData> getIntentData() {
         throw new UnsupportedOperationException();
     }
diff --git a/core/api/src/test/java/org/onosproject/net/intent/IntentServiceAdapter.java b/core/api/src/test/java/org/onosproject/net/intent/IntentServiceAdapter.java
index 73d0321..9867d04 100644
--- a/core/api/src/test/java/org/onosproject/net/intent/IntentServiceAdapter.java
+++ b/core/api/src/test/java/org/onosproject/net/intent/IntentServiceAdapter.java
@@ -43,6 +43,11 @@
     }
 
     @Override
+    public void addPending(IntentData intentData) {
+
+    }
+
+    @Override
     public Iterable<IntentData> getIntentData() {
         return null;
     }
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/IntentCleanup.java b/core/net/src/main/java/org/onosproject/net/intent/impl/IntentCleanup.java
index d51fe3a..56ee709 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/IntentCleanup.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/IntentCleanup.java
@@ -170,8 +170,9 @@
 
     private void resubmitCorrupt(IntentData intentData, boolean checkThreshold) {
         if (checkThreshold && intentData.errorCount() >= retryThreshold) {
+            //FIXME trace or debug statement?
             return; // threshold met or exceeded
-        }
+        } // FIXME should we backoff here?
 
         switch (intentData.request()) {
             case INSTALL_REQ:
@@ -188,15 +189,12 @@
     }
 
     private void resubmitPendingRequest(IntentData intentData) {
+        // FIXME should we back off here?
         switch (intentData.request()) {
             case INSTALL_REQ:
-                service.submit(intentData.intent());
-                break;
             case WITHDRAW_REQ:
-                service.withdraw(intentData.intent());
-                break;
             case PURGE_REQ:
-                service.purge(intentData.intent());
+                service.addPending(intentData);
                 break;
             default:
                 log.warn("Failed to resubmit pending intent {} in state {} with request {}",
@@ -235,7 +233,7 @@
 
         for (IntentData intentData : store.getPendingData(true, periodMs)) {
             resubmitPendingRequest(intentData);
-            stuckCount++;
+            pendingCount++;
         }
 
         if (corruptCount + failedCount + stuckCount + pendingCount > 0) {
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/IntentManager.java b/core/net/src/main/java/org/onosproject/net/intent/impl/IntentManager.java
index 07759e3..12c636c 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/IntentManager.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/IntentManager.java
@@ -266,6 +266,14 @@
     }
 
     @Override
+    public void addPending(IntentData intentData) {
+        checkPermission(INTENT_WRITE);
+        checkNotNull(intentData, INTENT_NULL);
+        //TODO we might consider further checking / assertions
+        store.addPending(intentData);
+    }
+
+    @Override
     public Iterable<IntentData> getIntentData() {
         checkPermission(INTENT_READ);
         return store.getIntentData(false, 0);
diff --git a/core/net/src/test/java/org/onosproject/net/intent/impl/IntentCleanupTest.java b/core/net/src/test/java/org/onosproject/net/intent/impl/IntentCleanupTest.java
index 8716e302..a6e6067 100644
--- a/core/net/src/test/java/org/onosproject/net/intent/impl/IntentCleanupTest.java
+++ b/core/net/src/test/java/org/onosproject/net/intent/impl/IntentCleanupTest.java
@@ -50,15 +50,26 @@
     private static class MockIntentService extends IntentServiceAdapter {
 
         private int submitCounter = 0;
+        private int pendingCounter = 0;
 
         @Override
         public void submit(Intent intent) {
             submitCounter++;
         }
 
+        @Override
+        public void addPending(IntentData intentData) {
+            pendingCounter++;
+        }
+
         public int submitCounter() {
             return submitCounter;
         }
+
+        public int pendingCounter() {
+            return pendingCounter;
+        }
+
     }
 
     @Before
@@ -138,7 +149,7 @@
 
         cleanup.run();
         assertEquals("Expect number of submits incorrect",
-                     1, service.submitCounter());
+                     1, service.pendingCounter());
 
     }
 
@@ -168,7 +179,7 @@
 
         cleanup.run();
         assertEquals("Expect number of submits incorrect",
-                     1, service.submitCounter());
+                     1, service.pendingCounter());
 
     }
 
diff --git a/core/net/src/test/java/org/onosproject/net/intent/impl/IntentCleanupTestMock.java b/core/net/src/test/java/org/onosproject/net/intent/impl/IntentCleanupTestMock.java
index d5d2af0..164aa9d 100644
--- a/core/net/src/test/java/org/onosproject/net/intent/impl/IntentCleanupTestMock.java
+++ b/core/net/src/test/java/org/onosproject/net/intent/impl/IntentCleanupTestMock.java
@@ -144,7 +144,7 @@
         IntentData data = new IntentData(intent, INSTALL_REQ, version);
         store.addPending(data);
 
-        service.submit(intent);
+        service.addPending(data);
         expectLastCall().once();
         replay(service);
 
@@ -177,7 +177,7 @@
         IntentData data = new IntentData(intent, INSTALL_REQ, version);
         store.addPending(data);
 
-        service.submit(intent);
+        service.addPending(data);
         expectLastCall().once();
         replay(service);
 
diff --git a/core/store/dist/src/main/java/org/onosproject/store/intent/impl/GossipIntentStore.java b/core/store/dist/src/main/java/org/onosproject/store/intent/impl/GossipIntentStore.java
index 2eb1d2e..c52b35e 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/intent/impl/GossipIntentStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/intent/impl/GossipIntentStore.java
@@ -38,6 +38,7 @@
 import org.onosproject.net.intent.IntentStoreDelegate;
 import org.onosproject.net.intent.Key;
 import org.onosproject.store.AbstractStore;
+import org.onosproject.store.Timestamp;
 import org.onosproject.store.serializers.KryoNamespaces;
 import org.onosproject.store.service.EventuallyConsistentMap;
 import org.onosproject.store.service.EventuallyConsistentMapEvent;
@@ -272,13 +273,15 @@
     public void addPending(IntentData data) {
         checkNotNull(data);
 
-        if (data.version() == null) {
-            pendingMap.put(data.key(), new IntentData(data.intent(), data.state(),
-                                                      new WallClockTimestamp(), clusterService.getLocalNode().id()));
-        } else {
-            pendingMap.put(data.key(), new IntentData(data.intent(), data.state(),
-                                                      data.version(), clusterService.getLocalNode().id()));
-        }
+        Timestamp version = data.version() != null ? data.version() : new WallClockTimestamp();
+        pendingMap.compute(data.key(), (key, existingValue) -> {
+            if (existingValue == null || existingValue.version().isOlderThan(version)) {
+                return new IntentData(data.intent(), data.state(),
+                                      version, clusterService.getLocalNode().id());
+            } else {
+                return existingValue;
+            }
+        });
     }
 
     @Override
diff --git a/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkIntentService.java b/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkIntentService.java
index cebc4a4..dc75b04 100644
--- a/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkIntentService.java
+++ b/incubator/net/src/main/java/org/onosproject/incubator/net/virtual/impl/VirtualNetworkIntentService.java
@@ -192,6 +192,11 @@
     }
 
     @Override
+    public void addPending(IntentData intentData) {
+        intentService.addPending(intentData);
+    }
+
+    @Override
     public Iterable<IntentData> getIntentData() {
         return store.getIntentData();
     }