ONOS-2513 Fix entire MP2SP intent failing on partial connectivity loss

 * Added PartialFailureContraint to MP2SP intent to allow partial connectivity.
   This means the intent remains installed as long as at least one ingress point
   can reach the egress point.
 * Intents with this constraint are recompiled on ObjectiveTracker triggers
   even if not in FAILED state
 * MP2SP intent compiler can compute a partial tree if constraint is set
 * ObjectiveTracker recompiles intents on any link event
 * SDN-IP MP2SP intents now use PartialFailureConstraint

Ported from onos-1.2 branch.

Change-Id: I32eaa198fae1dfba021d9251c8f855573f0e1d7d
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 ded3924..d974009 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
@@ -63,6 +63,7 @@
 import static java.util.concurrent.Executors.newSingleThreadExecutor;
 import static org.onlab.util.Tools.groupedThreads;
 import static org.onosproject.net.intent.IntentState.*;
+import static org.onosproject.net.intent.constraint.PartialFailureConstraint.intentAllowsPartialFailure;
 import static org.onosproject.net.intent.impl.phase.IntentProcessPhase.newInitialPhase;
 import static org.onosproject.security.AppGuard.checkPermission;
 import static org.slf4j.LoggerFactory.getLogger;
@@ -85,6 +86,8 @@
 
     private static final EnumSet<IntentState> RECOMPILE
             = EnumSet.of(INSTALL_REQ, FAILED, WITHDRAW_REQ);
+    private static final EnumSet<IntentState> WITHDRAW
+            = EnumSet.of(WITHDRAW_REQ, WITHDRAWING, WITHDRAWN);
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected CoreService coreService;
@@ -252,16 +255,15 @@
             submit(intent);
         }
 
-        if (compileAllFailed) {
-            // If required, compile all currently failed intents.
-            for (Intent intent : getIntents()) {
-                IntentState state = getIntentState(intent.key());
-                if (RECOMPILE.contains(state)) {
-                    if (state == WITHDRAW_REQ) {
-                        withdraw(intent);
-                    } else {
-                        submit(intent);
-                    }
+        // If required, compile all currently failed intents.
+        for (Intent intent : getIntents()) {
+            IntentState state = getIntentState(intent.key());
+            if ((compileAllFailed && RECOMPILE.contains(state))
+                    || intentAllowsPartialFailure(intent)) {
+                if (WITHDRAW.contains(state)) {
+                    withdraw(intent);
+                } else {
+                    submit(intent);
                 }
             }
         }
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/ObjectiveTracker.java b/core/net/src/main/java/org/onosproject/net/intent/impl/ObjectiveTracker.java
index 6ec60d7..02d9146 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/ObjectiveTracker.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/ObjectiveTracker.java
@@ -276,31 +276,28 @@
                 delegate.triggerCompile(Collections.emptySet(), true);
 
             } else {
-                Set<Key> toBeRecompiled = new HashSet<>();
-                boolean recompileOnly = true;
+                Set<Key> intentsToRecompile = new HashSet<>();
+                boolean dontRecompileAllFailedIntents = true;
 
                 // Scan through the list of reasons and keep accruing all
                 // intents that need to be recompiled.
                 for (Event reason : event.reasons()) {
                     if (reason instanceof LinkEvent) {
                         LinkEvent linkEvent = (LinkEvent) reason;
-                        if (linkEvent.type() == LINK_REMOVED
-                                || (linkEvent.type() == LINK_UPDATED &&
-                                        linkEvent.subject().isDurable())) {
-                            final LinkKey linkKey = linkKey(linkEvent.subject());
-                            synchronized (intentsByLink) {
-                                Set<Key> intentKeys = intentsByLink.get(linkKey);
-                                log.debug("recompile triggered by LinkDown {} {}", linkKey, intentKeys);
-                                toBeRecompiled.addAll(intentKeys);
-                            }
+                        final LinkKey linkKey = linkKey(linkEvent.subject());
+                        synchronized (intentsByLink) {
+                            Set<Key> intentKeys = intentsByLink.get(linkKey);
+                            log.debug("recompile triggered by LinkEvent {} ({}) for {}",
+                                    linkKey, linkEvent.type(), intentKeys);
+                            intentsToRecompile.addAll(intentKeys);
                         }
-                        recompileOnly = recompileOnly &&
+                        dontRecompileAllFailedIntents = dontRecompileAllFailedIntents &&
                                 (linkEvent.type() == LINK_REMOVED ||
                                 (linkEvent.type() == LINK_UPDATED &&
                                 linkEvent.subject().isDurable()));
                     }
                 }
-                delegate.triggerCompile(toBeRecompiled, !recompileOnly);
+                delegate.triggerCompile(intentsToRecompile, !dontRecompileAllFailedIntents);
             }
         }
     }
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/MultiPointToSinglePointIntentCompiler.java b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/MultiPointToSinglePointIntentCompiler.java
index 5c158e0..8fad769 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/MultiPointToSinglePointIntentCompiler.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/MultiPointToSinglePointIntentCompiler.java
@@ -26,13 +26,14 @@
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Link;
 import org.onosproject.net.Path;
+import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.intent.Intent;
 import org.onosproject.net.intent.IntentCompiler;
+import org.onosproject.net.intent.IntentException;
 import org.onosproject.net.intent.IntentExtensionService;
 import org.onosproject.net.intent.LinkCollectionIntent;
 import org.onosproject.net.intent.MultiPointToSinglePointIntent;
 import org.onosproject.net.intent.PointToPointIntent;
-import org.onosproject.net.intent.impl.PathNotFoundException;
 import org.onosproject.net.resource.link.LinkResourceAllocations;
 import org.onosproject.net.topology.PathService;
 
@@ -42,6 +43,9 @@
 import java.util.Map;
 import java.util.Set;
 
+import static org.onosproject.net.intent.constraint.PartialFailureConstraint.intentAllowsPartialFailure;
+
+
 /**
  * An intent compiler for
  * {@link org.onosproject.net.intent.MultiPointToSinglePointIntent}.
@@ -56,6 +60,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected PathService pathService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
     @Activate
     public void activate() {
         intentManager.registerCompiler(MultiPointToSinglePointIntent.class, this);
@@ -72,23 +79,44 @@
         Map<DeviceId, Link> links = new HashMap<>();
         ConnectPoint egressPoint = intent.egressPoint();
 
+        final boolean allowMissingPaths = intentAllowsPartialFailure(intent);
+        boolean partialTree = false;
+        boolean anyMissingPaths = false;
         for (ConnectPoint ingressPoint : intent.ingressPoints()) {
             if (ingressPoint.deviceId().equals(egressPoint.deviceId())) {
-                continue;
-            }
-            Path path = getPath(ingressPoint, intent.egressPoint());
-            for (Link link : path.links()) {
-                if (links.containsKey(link.src().deviceId())) {
-                    // We've already reached the existing tree with the first
-                    // part of this path. Add the merging point with different
-                    // incoming port, but don't add the remainder of the path
-                    // in case it differs from the path we already have.
-                    links.put(link.src().deviceId(), link);
-                    break;
+                if (deviceService.isAvailable(ingressPoint.deviceId())) {
+                    partialTree = true;
+                } else {
+                    anyMissingPaths = true;
                 }
 
-                links.put(link.src().deviceId(), link);
+                continue;
             }
+
+            Path path = getPath(ingressPoint, intent.egressPoint());
+            if (path != null) {
+                partialTree = true;
+
+                for (Link link : path.links()) {
+                    if (links.containsKey(link.src().deviceId())) {
+                        // We've already reached the existing tree with the first
+                        // part of this path. Add the merging point with different
+                        // incoming port, but don't add the remainder of the path
+                        // in case it differs from the path we already have.
+                        links.put(link.src().deviceId(), link);
+                        break;
+                    }
+                    links.put(link.src().deviceId(), link);
+                }
+            } else {
+                anyMissingPaths = true;
+            }
+        }
+
+        if (!partialTree) {
+            throw new IntentException("Could not find any paths between ingress and egress points.");
+        } else if (!allowMissingPaths && anyMissingPaths) {
+            throw new IntentException("Missing some paths between ingress and egress ports.");
         }
 
         Intent result = LinkCollectionIntent.builder()
@@ -111,12 +139,11 @@
      * @param one start of the path
      * @param two end of the path
      * @return Path between the two
-     * @throws org.onosproject.net.intent.impl.PathNotFoundException if a path cannot be found
      */
     private Path getPath(ConnectPoint one, ConnectPoint two) {
         Set<Path> paths = pathService.getPaths(one.deviceId(), two.deviceId());
         if (paths.isEmpty()) {
-            throw new PathNotFoundException(one.elementId(), two.elementId());
+            return null;
         }
         // TODO: let's be more intelligent about this eventually
         return paths.iterator().next();