Added error count for IntentData (ONOS-1060)

Change-Id: Ida6313603c15fb6c1c1793c298206587b370a13e
diff --git a/core/api/src/main/java/org/onosproject/net/intent/IntentData.java b/core/api/src/main/java/org/onosproject/net/intent/IntentData.java
index 6a9c9ae..c54c5bc 100644
--- a/core/api/src/main/java/org/onosproject/net/intent/IntentData.java
+++ b/core/api/src/main/java/org/onosproject/net/intent/IntentData.java
@@ -44,6 +44,7 @@
     private IntentState state;
     private Timestamp version;
     private NodeId origin;
+    private int errorCount;
 
     private List<Intent> installables;
 
@@ -75,6 +76,7 @@
         version = intentData.version;
         origin = intentData.origin;
         installables = intentData.installables;
+        errorCount = intentData.errorCount;
     }
 
     // kryo constructor
@@ -165,6 +167,32 @@
     }
 
     /**
+     * Increments the error count for this intent.
+     */
+    public void incrementErrorCount() {
+        errorCount++;
+    }
+
+    /**
+     * Sets the error count for this intent.
+     *
+     * @param newCount new count
+     */
+    public void setErrorCount(int newCount) {
+        errorCount = newCount;
+    }
+
+    /**
+     * Returns the number of times that this intent has encountered an error
+     * during installation or withdrawal.
+     *
+     * @return error count
+     */
+    public int errorCount() {
+        return errorCount;
+    }
+
+    /**
      * Sets the intent installables to the given list of intents.
      *
      * @param installables list of installables for this intent
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 de57781..d84105c 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
@@ -59,12 +59,17 @@
     private static final Logger log = getLogger(IntentManager.class);
 
     private static final int DEFAULT_PERIOD = 5; //seconds
+    private static final int DEFAULT_THRESHOLD = 5; //tries
 
     @Property(name = "period", intValue = DEFAULT_PERIOD,
               label = "Frequency in ms between cleanup runs")
     protected int period = DEFAULT_PERIOD;
     private long periodMs;
 
+    @Property(name = "retryThreshold", intValue = DEFAULT_THRESHOLD,
+            label = "Number of times to retry CORRUPT intent without delay")
+    private int retryThreshold = DEFAULT_THRESHOLD;
+
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected IntentService service;
 
@@ -106,6 +111,9 @@
         try {
             String s = get(properties, "period");
             newPeriod = isNullOrEmpty(s) ? period : Integer.parseInt(s.trim());
+
+            s = get(properties, "retryThreshold");
+            retryThreshold = isNullOrEmpty(s) ? period : Integer.parseInt(s.trim());
         } catch (NumberFormatException e) {
             log.warn(e.getMessage());
             newPeriod = period;
@@ -147,9 +155,9 @@
     }
 
     private void resubmitCorrupt(IntentData intentData, boolean checkThreshold) {
-        //TODO we might want to give up when retry count exceeds a threshold
-        // FIXME drop this if we exceed retry threshold
-
+        if (checkThreshold && intentData.errorCount() >= retryThreshold) {
+            return; // threshold met or exceeded
+        }
 
         switch (intentData.request()) {
             case INSTALL_REQ:
@@ -211,7 +219,7 @@
 
     @Override
     public void event(IntentEvent event) {
-        // fast path for CORRUPT intents, retry on event notification
+        // this is the fast path for CORRUPT intents, retry on event notification.
         //TODO we might consider using the timer to back off for subsequent retries
         if (event.type() == IntentEvent.Type.CORRUPT) {
             Key key = event.subject().key();
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 bc7e405..70f4c85 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
@@ -448,6 +448,7 @@
                     log.warn("Failed installation: {} {} on {}",
                              installData.key(), installData.intent(), ops);
                     installData.setState(CORRUPT);
+                    installData.incrementErrorCount();
                     store.write(installData);
                 }
                 // if toUninstall was cause of error, then CORRUPT (another job will clean this up)
@@ -456,6 +457,7 @@
                     log.warn("Failed withdrawal: {} {} on {}",
                              uninstallData.key(), uninstallData.intent(), ops);
                     uninstallData.setState(CORRUPT);
+                    uninstallData.incrementErrorCount();
                     store.write(uninstallData);
                 }
             }
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/phase/InstallRequest.java b/core/net/src/main/java/org/onosproject/net/intent/impl/phase/InstallRequest.java
index 03f73eb..a75d7cc 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/phase/InstallRequest.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/phase/InstallRequest.java
@@ -21,6 +21,7 @@
 import java.util.Optional;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.intent.impl.phase.IntentProcessPhase.transferErrorCount;
 
 /**
  * Represents a phase where intent installation has been requested.
@@ -47,6 +48,8 @@
 
     @Override
     public Optional<IntentProcessPhase> execute() {
+        transferErrorCount(data, stored);
+
         return Optional.of(new Compiling(processor, data, stored));
     }
 }
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/phase/IntentProcessPhase.java b/core/net/src/main/java/org/onosproject/net/intent/impl/phase/IntentProcessPhase.java
index 399a307..bce572c 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/phase/IntentProcessPhase.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/phase/IntentProcessPhase.java
@@ -18,6 +18,7 @@
 import org.onosproject.net.intent.IntentData;
 import org.onosproject.net.intent.impl.IntentProcessor;
 
+import java.util.Objects;
 import java.util.Optional;
 
 /**
@@ -57,4 +58,16 @@
         }
     }
 
+    static void transferErrorCount(IntentData data, Optional<IntentData> stored) {
+        if (stored.isPresent()) {
+            IntentData storedData = stored.get();
+            if (Objects.equals(data.intent(), storedData.intent()) &&
+                    Objects.equals(data.request(), storedData.request())) {
+                data.setErrorCount(storedData.errorCount());
+            } else {
+                data.setErrorCount(0);
+            }
+        }
+    }
+
 }
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/phase/WithdrawRequest.java b/core/net/src/main/java/org/onosproject/net/intent/impl/phase/WithdrawRequest.java
index 73a6510..8a0709e 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/phase/WithdrawRequest.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/phase/WithdrawRequest.java
@@ -21,6 +21,7 @@
 import java.util.Optional;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.intent.impl.phase.IntentProcessPhase.transferErrorCount;
 
 /**
  * Represents a phase of requesting a withdraw of an intent.
@@ -51,6 +52,8 @@
         // same version i.e. they are the same
         // Note: this call is not just the symmetric version of submit
 
+        transferErrorCount(data, stored);
+
         if (!stored.isPresent() || stored.get().installables().isEmpty()) {
             switch (data.request()) {
                 case INSTALL_REQ: