ONOS-5799: Make SP2MP compiler partial failure constraint aware

Change-Id: I741c3a22916e7e51e5882bd3993d425e78f18bda
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/ConnectivityIntentCompiler.java b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/ConnectivityIntentCompiler.java
index 62c027a..cb57aee 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/ConnectivityIntentCompiler.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/ConnectivityIntentCompiler.java
@@ -94,6 +94,25 @@
      * @return Path between the two
      * @throws PathNotFoundException if a path cannot be found
      */
+    @Deprecated
+    protected Path getPathOrException(ConnectivityIntent intent,
+                                      ElementId one, ElementId two) {
+        Path path = getPath(intent, one, two);
+        if (path == null) {
+            throw new PathNotFoundException(one, two);
+        }
+        // TODO: let's be more intelligent about this eventually
+        return path;
+    }
+
+    /**
+     * Computes a path between two ConnectPoints.
+     *
+     * @param intent intent on which behalf path is being computed
+     * @param one    start of the path
+     * @param two    end of the path
+     * @return Path between the two, or null if no path can be found
+     */
     protected Path getPath(ConnectivityIntent intent,
                            ElementId one, ElementId two) {
         Set<Path> paths = pathService.getPaths(one, two, weight(intent.constraints()));
@@ -102,7 +121,7 @@
                 .filter(path -> checkPath(path, constraints))
                 .toList();
         if (filtered.isEmpty()) {
-            throw new PathNotFoundException(one, two);
+            return null;
         }
         // TODO: let's be more intelligent about this eventually
         return filtered.iterator().next();
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/HostToHostIntentCompiler.java b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/HostToHostIntentCompiler.java
index dad8e3b..f0f0046 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/HostToHostIntentCompiler.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/HostToHostIntentCompiler.java
@@ -82,9 +82,9 @@
         }
 
         boolean isAsymmetric = intent.constraints().contains(new AsymmetricPathConstraint());
-        Path pathOne = getPath(intent, intent.one(), intent.two());
+        Path pathOne = getPathOrException(intent, intent.one(), intent.two());
         Path pathTwo = isAsymmetric ?
-                getPath(intent, intent.two(), intent.one()) : invertPath(pathOne);
+                getPathOrException(intent, intent.two(), intent.one()) : invertPath(pathOne);
 
         Host one = hostService.getHost(intent.one());
         Host two = hostService.getHost(intent.two());
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/MplsIntentCompiler.java b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/MplsIntentCompiler.java
index c8ccf68..4f466de 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/MplsIntentCompiler.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/MplsIntentCompiler.java
@@ -70,8 +70,8 @@
         }
 
         List<Link> links = new ArrayList<>();
-        Path path = getPath(intent, ingressPoint.deviceId(),
-                egressPoint.deviceId());
+        Path path = getPathOrException(intent, ingressPoint.deviceId(),
+                                       egressPoint.deviceId());
 
         links.add(createEdgeLink(ingressPoint, true));
         links.addAll(path.links());
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 cac5d68..758c21b5 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
@@ -28,19 +28,16 @@
 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.topology.PathService;
 
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 import static org.onosproject.net.intent.constraint.PartialFailureConstraint.intentAllowsPartialFailure;
 
@@ -51,15 +48,12 @@
  */
 @Component(immediate = true)
 public class MultiPointToSinglePointIntentCompiler
-        implements IntentCompiler<MultiPointToSinglePointIntent> {
+        extends ConnectivityIntentCompiler<MultiPointToSinglePointIntent> {
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected IntentExtensionService intentManager;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected PathService pathService;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected DeviceService deviceService;
 
     @Activate
@@ -78,22 +72,23 @@
         ConnectPoint egressPoint = intent.egressPoint();
 
         final boolean allowMissingPaths = intentAllowsPartialFailure(intent);
-        boolean partialTree = false;
-        boolean anyMissingPaths = false;
+        boolean hasPaths = false;
+        boolean missingSomePaths = false;
+
         for (ConnectPoint ingressPoint : intent.ingressPoints()) {
             if (ingressPoint.deviceId().equals(egressPoint.deviceId())) {
                 if (deviceService.isAvailable(ingressPoint.deviceId())) {
-                    partialTree = true;
+                    hasPaths = true;
                 } else {
-                    anyMissingPaths = true;
+                    missingSomePaths = true;
                 }
-
                 continue;
             }
 
-            Path path = getPath(ingressPoint, intent.egressPoint());
+            Path path = getPath(intent, ingressPoint.deviceId(), intent.egressPoint().deviceId());
+
             if (path != null) {
-                partialTree = true;
+                hasPaths = true;
 
                 for (Link link : path.links()) {
                     if (links.containsKey(link.dst().deviceId())) {
@@ -107,14 +102,14 @@
                     links.put(link.src().deviceId(), link);
                 }
             } else {
-                anyMissingPaths = true;
+                missingSomePaths = 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.");
+        if (!hasPaths) {
+            throw new IntentException("Cannot find any path between ingress and egress points.");
+        } else if (!allowMissingPaths && missingSomePaths) {
+            throw new IntentException("Missing some paths between ingress and egress points.");
         }
 
         Intent result = LinkCollectionIntent.builder()
@@ -131,20 +126,4 @@
 
         return Collections.singletonList(result);
     }
-
-    /**
-     * Computes a path between two ConnectPoints.
-     *
-     * @param one start of the path
-     * @param two end of the path
-     * @return Path between the two
-     */
-    private Path getPath(ConnectPoint one, ConnectPoint two) {
-        Set<Path> paths = pathService.getPaths(one.deviceId(), two.deviceId());
-        if (paths.isEmpty()) {
-            return null;
-        }
-        // TODO: let's be more intelligent about this eventually
-        return paths.iterator().next();
-    }
 }
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompiler.java b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompiler.java
index a6fac6f..c3fc348 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompiler.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompiler.java
@@ -161,8 +161,8 @@
                                                  ConnectPoint egressPoint,
                                                  PointToPointIntent intent) {
         List<Link> links = new ArrayList<>();
-        Path path = getPath(intent, ingressPoint.deviceId(),
-                            egressPoint.deviceId());
+        Path path = getPathOrException(intent, ingressPoint.deviceId(),
+                                       egressPoint.deviceId());
 
         links.add(createEdgeLink(ingressPoint, true));
         links.addAll(path.links());
@@ -174,8 +174,8 @@
     }
 
     private List<Intent> createUnprotectedLinkCollectionIntent(PointToPointIntent intent) {
-        Path path = getPath(intent, intent.filteredIngressPoint().connectPoint().deviceId(),
-                            intent.filteredEgressPoint().connectPoint().deviceId());
+        Path path = getPathOrException(intent, intent.filteredIngressPoint().connectPoint().deviceId(),
+                                       intent.filteredEgressPoint().connectPoint().deviceId());
 
         return asList(createLinkCollectionIntent(ImmutableSet.copyOf(path.links()),
                                                  path.cost(),
@@ -274,8 +274,8 @@
                                                 PointToPointIntent intent,
                                                 List<Intent> installable) {
         List<Link> links = new ArrayList<>();
-        Path onlyPath = getPath(intent, ingressPoint.deviceId(),
-                                egressPoint.deviceId());
+        Path onlyPath = getPathOrException(intent, ingressPoint.deviceId(),
+                                           egressPoint.deviceId());
 
         List<Intent> reusableIntents = null;
         if (installable != null) {
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/SinglePointToMultiPointIntentCompiler.java b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/SinglePointToMultiPointIntentCompiler.java
index 22c4e89..a3f7c49 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/SinglePointToMultiPointIntentCompiler.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/SinglePointToMultiPointIntentCompiler.java
@@ -19,31 +19,34 @@
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.onosproject.net.ConnectPoint;
 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.IntentException;
 import org.onosproject.net.intent.LinkCollectionIntent;
 import org.onosproject.net.intent.SinglePointToMultiPointIntent;
-import org.onosproject.net.provider.ProviderId;
 
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
+import static org.onosproject.net.intent.constraint.PartialFailureConstraint.intentAllowsPartialFailure;
+
 @Component(immediate = true)
 public class SinglePointToMultiPointIntentCompiler
         extends ConnectivityIntentCompiler<SinglePointToMultiPointIntent> {
 
-    // TODO: use off-the-shell core provider ID
-    private static final ProviderId PID =
-            new ProviderId("core", "org.onosproject.core", true);
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
 
     @Activate
     public void activate() {
-        intentManager.registerCompiler(SinglePointToMultiPointIntent.class,
-                                       this);
+        intentManager.registerCompiler(SinglePointToMultiPointIntent.class, this);
     }
 
     @Deactivate
@@ -51,19 +54,40 @@
         intentManager.unregisterCompiler(SinglePointToMultiPointIntent.class);
     }
 
-
     @Override
     public List<Intent> compile(SinglePointToMultiPointIntent intent,
                                 List<Intent> installable) {
         Set<Link> links = new HashSet<>();
 
+        final boolean allowMissingPaths = intentAllowsPartialFailure(intent);
+        boolean hasPaths = false;
+        boolean missingSomePaths = false;
+
         for (ConnectPoint egressPoint : intent.egressPoints()) {
             if (egressPoint.deviceId().equals(intent.ingressPoint().deviceId())) {
+                // Do not need to look for paths, since ingress and egress
+                // devices are the same.
+                if (deviceService.isAvailable(egressPoint.deviceId())) {
+                    hasPaths = true;
+                } else {
+                    missingSomePaths = true;
+                }
                 continue;
             }
 
             Path path = getPath(intent, intent.ingressPoint().deviceId(), egressPoint.deviceId());
-            links.addAll(path.links());
+            if (path != null) {
+                hasPaths = true;
+                links.addAll(path.links());
+            } else {
+                missingSomePaths = true;
+            }
+        }
+
+        if (!hasPaths) {
+            throw new IntentException("Cannot find any path between ingress and egress points.");
+        } else if (!allowMissingPaths && missingSomePaths) {
+            throw new IntentException("Missing some paths between ingress and egress points.");
         }
 
         Intent result = LinkCollectionIntent.builder()
diff --git a/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/MultiPointToSinglePointIntentCompilerTest.java b/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/MultiPointToSinglePointIntentCompilerTest.java
index ce5f3b7..2eb4d9f 100644
--- a/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/MultiPointToSinglePointIntentCompilerTest.java
+++ b/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/MultiPointToSinglePointIntentCompilerTest.java
@@ -293,7 +293,6 @@
      */
     @Test
     public void testNonTrivialSelectorsIntent() {
-
         Set<FilteredConnectPoint> ingress = ImmutableSet.of(
                 new FilteredConnectPoint(connectPoint("of1", 1),
                                          DefaultTrafficSelector.builder().matchVlanId(VlanId.vlanId("100")).build()),