Added suggest path to PointToPointIntent

Change-Id: Ie8ae3af6bd97af3628334d37482e63196d15b094
diff --git a/core/api/src/main/java/org/onosproject/net/intent/PointToPointIntent.java b/core/api/src/main/java/org/onosproject/net/intent/PointToPointIntent.java
index 1a38bfd..d0a9d54 100644
--- a/core/api/src/main/java/org/onosproject/net/intent/PointToPointIntent.java
+++ b/core/api/src/main/java/org/onosproject/net/intent/PointToPointIntent.java
@@ -19,6 +19,7 @@
 import com.google.common.base.MoreObjects;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.net.FilteredConnectPoint;
+import org.onosproject.net.Link;
 import org.onosproject.net.ResourceGroup;
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.flow.TrafficTreatment;
@@ -38,6 +39,8 @@
     private final FilteredConnectPoint ingressPoint;
     private final FilteredConnectPoint egressPoint;
 
+    private final List<Link> suggestedPath;
+
     /**
      * Returns a new point to point intent builder. The application id,
      * ingress point and egress point are required fields.  If they are
@@ -57,6 +60,8 @@
         FilteredConnectPoint ingressPoint;
         FilteredConnectPoint egressPoint;
 
+        List<Link> suggestedPath;
+
         private Builder() {
             // Hide constructor
         }
@@ -121,12 +126,36 @@
         }
 
         /**
+         * Sets the suggested path as list of links.
+         *
+         * @param links list of suggested links
+         * @return this builder
+         */
+        public Builder suggestedPath(List<Link> links) {
+            this.suggestedPath = links;
+            return this;
+        }
+
+        /**
          * Builds a point to point intent from the accumulated parameters.
          *
          * @return point to point intent
          */
         public PointToPointIntent build() {
-
+            if (suggestedPath != null &&
+                    suggestedPath.size() > 0 &&
+                    (!suggestedPath.get(0)
+                            .src()
+                            .deviceId()
+                            .equals(ingressPoint.connectPoint().deviceId()) ||
+                     !suggestedPath.get(suggestedPath.size() - 1)
+                             .dst()
+                             .deviceId()
+                             .equals(egressPoint.connectPoint().deviceId()))
+                    ) {
+                throw new IllegalArgumentException(
+                        "Suggested path not compatible with ingress and egress connect points");
+            }
             return new PointToPointIntent(
                     appId,
                     key,
@@ -136,6 +165,7 @@
                     egressPoint,
                     constraints,
                     priority,
+                    suggestedPath,
                     resourceGroup
             );
         }
@@ -147,16 +177,17 @@
      * Creates a new point-to-point intent with the supplied ingress/egress
      * ports and constraints.
      *
-     * @param appId        application identifier
-     * @param key          key of the intent
-     * @param selector     traffic selector
-     * @param treatment    treatment
-     * @param ingressPoint filtered ingress port
-     * @param egressPoint  filtered egress port
-     * @param constraints  optional list of constraints
-     * @param priority     priority to use for flows generated by this intent
+     * @param appId             application identifier
+     * @param key               key of the intent
+     * @param selector          traffic selector
+     * @param treatment         treatment
+     * @param ingressPoint      filtered ingress port
+     * @param egressPoint       filtered egress port
+     * @param constraints       optional list of constraints
+     * @param priority          priority to use for flows generated by this intent
+     * @param suggestedPath     suggested path
      * @throws NullPointerException if {@code ingressPoint} or
-     *        {@code egressPoints} or {@code appId} is null.
+     *        {@code egressPoints} or {@code appId} is null or {@code path} is null.
      */
     private PointToPointIntent(ApplicationId appId,
                                Key key,
@@ -166,15 +197,24 @@
                                FilteredConnectPoint egressPoint,
                                List<Constraint> constraints,
                                int priority,
+                               List<Link> suggestedPath,
                                ResourceGroup resourceGroup) {
-        super(appId, key, Collections.emptyList(), selector, treatment, constraints,
-                priority, resourceGroup);
+        super(appId,
+              key,
+              suggestedPath != null ? resources(suggestedPath) : Collections.emptyList(),
+              selector,
+              treatment,
+              constraints,
+              priority,
+              resourceGroup);
 
         checkArgument(!ingressPoint.equals(egressPoint),
                 "ingress and egress should be different (ingress: %s, egress: %s)", ingressPoint, egressPoint);
 
         this.ingressPoint = checkNotNull(ingressPoint);
         this.egressPoint = checkNotNull(egressPoint);
+        this.suggestedPath = suggestedPath;
+
     }
 
     /**
@@ -184,6 +224,7 @@
         super();
         this.ingressPoint = null;
         this.egressPoint = null;
+        this.suggestedPath = null;
     }
 
     /**
@@ -205,6 +246,16 @@
         return egressPoint;
     }
 
+
+    /**
+     * Return the suggested path (as a list of links) that the compiler should use.
+     *
+     * @return suggested path
+     */
+    public List<Link> suggestedPath() {
+        return suggestedPath;
+    }
+
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(getClass())
@@ -218,6 +269,7 @@
                 .add("ingress", filteredIngressPoint())
                 .add("egress", filteredEgressPoint())
                 .add("constraints", constraints())
+                .add("links", suggestedPath())
                 .add("resourceGroup", resourceGroup())
                 .toString();
     }
diff --git a/core/api/src/test/java/org/onosproject/net/intent/IntentTestsMocks.java b/core/api/src/test/java/org/onosproject/net/intent/IntentTestsMocks.java
index 48b4a87..2b8c6d7 100644
--- a/core/api/src/test/java/org/onosproject/net/intent/IntentTestsMocks.java
+++ b/core/api/src/test/java/org/onosproject/net/intent/IntentTestsMocks.java
@@ -20,6 +20,8 @@
 import org.onlab.graph.ScalarWeight;
 import org.onlab.graph.Weight;
 import org.onosproject.core.GroupId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultLink;
 import org.onosproject.net.DefaultPath;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.ElementId;
@@ -41,6 +43,7 @@
 import org.onosproject.net.flow.instructions.Instruction;
 import org.onosproject.net.flow.instructions.Instructions;
 import org.onosproject.net.flow.instructions.Instructions.MetadataInstruction;
+import org.onosproject.net.link.LinkServiceAdapter;
 import org.onosproject.net.provider.ProviderId;
 import org.onosproject.net.topology.DefaultTopologyEdge;
 import org.onosproject.net.topology.DefaultTopologyVertex;
@@ -58,7 +61,10 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
+import static org.onosproject.net.Link.Type.DIRECT;
 import static org.onosproject.net.NetTestTools.*;
 
 /**
@@ -189,6 +195,71 @@
     }
 
     /**
+     * Mock path service for creating paths within the test with multiple possible paths.
+     */
+    public static class MockMultiplePathService extends PathServiceAdapter {
+
+        final String[][] pathsHops;
+
+        /**
+         * Constructor that provides a set of hops to mock.
+         *
+         * @param pathHops multiple path hops to mock
+         */
+        public MockMultiplePathService(String[][] pathHops) {
+            this.pathsHops = pathHops;
+        }
+
+        @Override
+        public Set<Path> getPaths(ElementId src, ElementId dst) {
+
+            //Extracts all the paths that goes from src to dst
+            Set<Path> allPaths = new HashSet<>();
+            allPaths.addAll(IntStream.range(0, pathsHops.length)
+                    .filter(i -> src.toString().endsWith(pathsHops[i][0])
+                            && dst.toString().endsWith(pathsHops[i][pathsHops[i].length - 1]))
+                    .mapToObj(i -> createPath(src instanceof HostId,
+                                              dst instanceof HostId,
+                                              pathsHops[i]))
+                    .collect(Collectors.toSet()));
+
+            // Maintain only the shortest paths
+            int minPathLength = allPaths.stream()
+                    .mapToInt(o -> o.links().size())
+                    .min()
+                    .orElse(Integer.MAX_VALUE);
+            Set<Path> shortestPaths = allPaths.stream()
+                    .filter(path -> path.links().size() <= minPathLength)
+                    .collect(Collectors.toSet());
+
+            return shortestPaths;
+        }
+
+
+        @Override
+        public Set<Path> getPaths(ElementId src, ElementId dst, LinkWeigher weigher) {
+            Set<Path> paths = getPaths(src, dst);
+
+            for (Path path : paths) {
+                DeviceId srcDevice = path.src().elementId() instanceof DeviceId ? path.src().deviceId() : null;
+                DeviceId dstDevice = path.dst().elementId() instanceof DeviceId ? path.dst().deviceId() : null;
+                if (srcDevice != null && dstDevice != null) {
+                    TopologyVertex srcVertex = new DefaultTopologyVertex(srcDevice);
+                    TopologyVertex dstVertex = new DefaultTopologyVertex(dstDevice);
+                    Link link = link(src.toString(), 1, dst.toString(), 1);
+
+                    Weight weightValue = weigher.weight(new DefaultTopologyEdge(srcVertex, dstVertex, link));
+                    if (weightValue.isNegative()) {
+                        return new HashSet<>();
+                    }
+                }
+            }
+            return paths;
+        }
+    }
+
+
+    /**
      * Mock path service for creating paths within the test.
      *
      */
@@ -249,6 +320,22 @@
     }
 
     /**
+     * Mock active and direct link.
+     */
+    public static class FakeLink extends DefaultLink {
+
+        /**
+         * Constructor that provides source and destination of the fake link.
+         *
+         * @param src Source connect point of the fake link
+         * @param dst Destination connect point of the fake link
+         */
+        public FakeLink(ConnectPoint src, ConnectPoint dst) {
+            super(null, src, dst, DIRECT, Link.State.ACTIVE);
+        }
+    }
+
+    /**
      * Mock path service for creating paths for MP2SP intent tests, returning
      * pre-determined paths.
      */
@@ -319,6 +406,38 @@
         }
     }
 
+    /**
+     * Mock link service for getting links to check path availability
+     * when a suggested path is submitted.
+     */
+    public static class MockLinkService extends LinkServiceAdapter {
+        final String[][] linksHops;
+
+        /**
+         * Constructor that provides a set of links (as a list of hops).
+         *
+         * @param linksHops links to to mock (link as a set of hops)
+         */
+        public MockLinkService(String[][] linksHops) {
+            this.linksHops = linksHops;
+        }
+
+        @Override
+        public Set<Link> getLinks() {
+            return Arrays.asList(linksHops).stream()
+                    .map(path -> createPath(path).links())
+                    .flatMap(List::stream)
+                    .collect(Collectors.toSet());
+        }
+        @Override
+        public Set<Link> getLinks(ConnectPoint connectPoint) {
+            return getLinks().stream()
+                    .filter(link -> link.src().deviceId().equals(connectPoint.deviceId())
+                    || link.dst().deviceId().equals(connectPoint.deviceId()))
+                    .collect(Collectors.toSet());
+        }
+    }
+
     private static final IntentTestsMocks.MockSelector SELECTOR =
             new IntentTestsMocks.MockSelector();
     private static final IntentTestsMocks.MockTreatment TREATMENT =
diff --git a/core/api/src/test/java/org/onosproject/net/intent/PointToPointIntentTest.java b/core/api/src/test/java/org/onosproject/net/intent/PointToPointIntentTest.java
index 3b170a0..c0257c0 100644
--- a/core/api/src/test/java/org/onosproject/net/intent/PointToPointIntentTest.java
+++ b/core/api/src/test/java/org/onosproject/net/intent/PointToPointIntentTest.java
@@ -17,8 +17,15 @@
 
 import org.junit.Test;
 import org.onosproject.net.FilteredConnectPoint;
+import org.onosproject.net.Link;
 
+import java.util.LinkedList;
+import java.util.List;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutableBaseClass;
 
 /**
@@ -59,6 +66,34 @@
         assertEquals("incorrect egress", FP2, intent.filteredEgressPoint());
     }
 
+    @Test
+    public void suggestedPath() {
+        List<Link> suggestedPath = new LinkedList<>();
+        suggestedPath.add(new IntentTestsMocks.FakeLink(FP1.connectPoint(), FP2.connectPoint()));
+
+        PointToPointIntent intent = createWithSuggestedPath(suggestedPath);
+        assertEquals("incorrect id", APPID, intent.appId());
+        assertEquals("incorrect match", MATCH, intent.selector());
+        assertEquals("incorrect ingress", FP1, intent.filteredIngressPoint());
+        assertEquals("incorrect egress", FP2, intent.filteredEgressPoint());
+        assertEquals("incorrect suggested path", suggestedPath, intent.suggestedPath());
+
+    }
+
+    @Test
+    public void failSuggestedPath() {
+        List<Link> suggestedPath = new LinkedList<>();
+        try {
+            suggestedPath.add(new IntentTestsMocks.FakeLink(FP3.connectPoint(), FP2.connectPoint()));
+
+            createWithSuggestedPath(suggestedPath);
+            fail("Point to Point intent building with incompatible suggested path "
+                         + "not throw exception.");
+        } catch (IllegalArgumentException exception) {
+            assertThat(exception.getMessage(), containsString("Suggested path not compatible"));
+        }
+    }
+
     @Override
     protected PointToPointIntent createOne() {
         return PointToPointIntent.builder()
@@ -101,4 +136,15 @@
                 .filteredEgressPoint(FP2)
                 .build();
     }
+
+    protected PointToPointIntent createWithSuggestedPath(List<Link> suggestedPath) {
+        return PointToPointIntent.builder()
+                .appId(APPID)
+                .selector(MATCH)
+                .treatment(NOP)
+                .filteredIngressPoint(FP1)
+                .filteredEgressPoint(FP2)
+                .suggestedPath(suggestedPath)
+                .build();
+    }
 }
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 a8ab9fa..6ae262c 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
@@ -163,6 +163,28 @@
     }
 
     /**
+     * Computes all the paths 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 Paths between the two, or null if no path can be found
+     */
+    protected List<Path> getPaths(ConnectivityIntent intent,
+                           ElementId one, ElementId two) {
+        Set<Path> paths = pathService.getPaths(one, two, weigher(intent.constraints()));
+        final List<Constraint> constraints = intent.constraints();
+        ImmutableList<Path> filtered = FluentIterable.from(paths)
+                .filter(path -> checkPath(path, constraints))
+                .toList();
+        if (filtered.isEmpty()) {
+            return null;
+        }
+
+        return filtered;
+    }
+
+    /**
      * Computes a disjoint path between two ConnectPoints.
      *
      * @param intent intent on which behalf path is being computed
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 acdaddd..759202f 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
@@ -16,6 +16,7 @@
 package org.onosproject.net.intent.impl.compiler;
 
 import com.google.common.collect.ImmutableSet;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -75,6 +76,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 import java.util.stream.Stream;
 
 import static java.util.Arrays.asList;
@@ -128,6 +130,18 @@
         ConnectPoint ingressPoint = intent.filteredIngressPoint().connectPoint();
         ConnectPoint egressPoint = intent.filteredEgressPoint().connectPoint();
 
+        //TODO: handle protected path case with suggested path!!
+        //Idea: use suggested path as primary and another path from path service as protection
+        if (intent.suggestedPath() != null && intent.suggestedPath().size() > 0) {
+            Path path = new DefaultPath(PID, intent.suggestedPath(), new ScalarWeight(1));
+            //Check intent constraints against suggested path and suggested path availability
+            if (checkPath(path, intent.constraints()) && pathAvailable(intent)) {
+                allocateIntentBandwidth(intent, path);
+                return asList(createLinkCollectionIntent(ImmutableSet.copyOf(intent.suggestedPath()),
+                                                         DEFAULT_COST, intent));
+            }
+        }
+
         if (ingressPoint.deviceId().equals(egressPoint.deviceId())) {
             return createZeroHopLinkCollectionIntent(intent);
         }
@@ -147,6 +161,21 @@
         }
     }
 
+    private void allocateIntentBandwidth(PointToPointIntent intent, Path path) {
+        ConnectPoint ingressCP = intent.filteredIngressPoint().connectPoint();
+        ConnectPoint egressCP = intent.filteredEgressPoint().connectPoint();
+
+        List<ConnectPoint> pathCPs =
+                path.links().stream()
+                        .flatMap(l -> Stream.of(l.src(), l.dst()))
+                        .collect(Collectors.toList());
+
+        pathCPs.add(ingressCP);
+        pathCPs.add(egressCP);
+
+        allocateBandwidth(intent, pathCPs);
+    }
+
     private List<Intent> createZeroHopIntent(ConnectPoint ingressPoint,
                                              ConnectPoint egressPoint,
                                              PointToPointIntent intent) {
@@ -165,18 +194,7 @@
                                        intent.filteredEgressPoint().connectPoint().deviceId());
 
         // Allocate bandwidth if a bandwidth constraint is set
-        ConnectPoint ingressCP = intent.filteredIngressPoint().connectPoint();
-        ConnectPoint egressCP = intent.filteredEgressPoint().connectPoint();
-
-        List<ConnectPoint> pathCPs =
-                path.links().stream()
-                            .flatMap(l -> Stream.of(l.src(), l.dst()))
-                            .collect(Collectors.toList());
-
-        pathCPs.add(ingressCP);
-        pathCPs.add(egressCP);
-
-        allocateBandwidth(intent, pathCPs);
+        allocateIntentBandwidth(intent, path);
 
         return asList(createLinkCollectionIntent(ImmutableSet.copyOf(path.links()),
                                                  path.cost(),
@@ -295,19 +313,7 @@
             return reusableIntents;
         } else {
             // Allocate bandwidth if a bandwidth constraint is set
-            ConnectPoint ingressCP = intent.filteredIngressPoint().connectPoint();
-            ConnectPoint egressCP = intent.filteredEgressPoint().connectPoint();
-
-            List<ConnectPoint> pathCPs =
-                    onlyPath.links().stream()
-                            .flatMap(l -> Stream.of(l.src(), l.dst()))
-                            .collect(Collectors.toList());
-
-            pathCPs.add(ingressCP);
-            pathCPs.add(egressCP);
-
-            // Allocate bandwidth if a bandwidth constraint is set
-            allocateBandwidth(intent, pathCPs);
+            allocateIntentBandwidth(intent, onlyPath);
 
             links.add(createEdgeLink(ingressPoint, true));
             links.addAll(onlyPath.links());
@@ -698,4 +704,47 @@
 
         groupService.addBucketsToGroup(src.deviceId(), groupKey, addBuckets, groupKey, intent.appId());
     }
+
+    /**
+     * Checks suggested path availability.
+     * It checks:
+     * - single links availability;
+     * - that first and last device of the path are coherent with ingress and egress devices;
+     * - links contiguity.
+     *
+     * @param intent    Intent with suggested path to check
+     * @return true if the suggested path is available
+     */
+    private boolean pathAvailable(PointToPointIntent intent) {
+        // Check links availability
+        List<Link> suggestedPath = intent.suggestedPath();
+        for (Link link : suggestedPath) {
+            if (!(link instanceof EdgeLink) && !linkService.getLinks(link.src()).contains(link)) {
+                return false;
+            }
+        }
+
+        //Check that first and last device of the path are intent ingress and egress devices
+        if (!suggestedPath.get(0).src()
+                .deviceId().equals(intent.filteredIngressPoint().connectPoint().deviceId())) {
+            return false;
+        }
+        if (!suggestedPath.get(suggestedPath.size() - 1).dst()
+                .deviceId().equals(intent.filteredEgressPoint().connectPoint().deviceId())) {
+            return false;
+        }
+
+        // Check contiguity
+        List<Pair<Link, Link>> linkPairs = IntStream.
+            range(0, suggestedPath.size() - 1)
+            .mapToObj(i -> Pair.of(suggestedPath.get(i), suggestedPath.get(i + 1)))
+            .collect(Collectors.toList());
+
+        for (Pair<Link, Link> linkPair : linkPairs) {
+            if (!linkPair.getKey().dst().deviceId().equals(linkPair.getValue().src().deviceId())) {
+                return false;
+            }
+        }
+        return true;
+    }
 }
diff --git a/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompilerTest.java b/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompilerTest.java
index a811ad6..31c15ee 100644
--- a/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompilerTest.java
+++ b/core/net/src/test/java/org/onosproject/net/intent/impl/compiler/PointToPointIntentCompilerTest.java
@@ -25,6 +25,7 @@
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.FilteredConnectPoint;
 import org.onosproject.net.Link;
+import org.onosproject.net.NetTestTools;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.ResourceGroup;
 import org.onosproject.net.flow.TrafficSelector;
@@ -200,6 +201,54 @@
     }
 
     /**
+     * Creates a PointToPoint intent based on ingress and egress deviceIds
+     * and a suggested path.
+     * @param ingress           the ingress connect point
+     * @param egress            the egress connect point
+     * @param suggestedPath     the suggested path
+     * @return the PointToPointIntent connecting the two connect points with
+     * the suggested path (if available)
+     */
+    private PointToPointIntent makeIntentSuggestedPath(ConnectPoint ingress,
+                                                       ConnectPoint egress,
+                                                       List<Link> suggestedPath) {
+        return PointToPointIntent.builder()
+                .appId(APPID)
+                .selector(selector)
+                .treatment(treatment)
+                .filteredIngressPoint(new FilteredConnectPoint(ingress))
+                .filteredEgressPoint(new FilteredConnectPoint(egress))
+                .suggestedPath(suggestedPath)
+                .build();
+    }
+
+    /**
+     * Creates a PointToPoint intent based on ingress and egress deviceIds and
+     * constraints with a suggested path.
+     *
+     * @param ingress         the ingress connect point
+     * @param egress          the egress connect point
+     * @param suggestedPath   the suggested path
+     * @param constraints     constraints
+     * @return the PointToPointIntent connecting the two connect points with
+     * constraints and a suggested path
+     */
+    private PointToPointIntent makeIntentSuggestedPath(ConnectPoint ingress,
+                                          ConnectPoint egress,
+                                          List<Link> suggestedPath,
+                                          List<Constraint> constraints) {
+        return PointToPointIntent.builder()
+                .appId(APPID)
+                .selector(selector)
+                .treatment(treatment)
+                .filteredIngressPoint(new FilteredConnectPoint(ingress))
+                .filteredEgressPoint(new FilteredConnectPoint(egress))
+                .constraints(constraints)
+                .suggestedPath(suggestedPath)
+                .build();
+    }
+
+    /**
      * Creates a compiler for HostToHost intents.
      *
      * @param hops string array describing the path hops to use when compiling
@@ -210,6 +259,41 @@
     }
 
     /**
+     * Creates a compiler for PointToPoint intents with suggested paths.
+     *
+     * @param paths all the possible paths in the network
+     * @return PointToPoint intent compiler
+     */
+    private PointToPointIntentCompiler makeCompilerSuggestedPath(String[][] paths) {
+        final PointToPointIntentCompiler compiler = new PointToPointIntentCompiler();
+        compiler.pathService = new IntentTestsMocks.MockMultiplePathService(paths);
+        compiler.linkService = new IntentTestsMocks.MockLinkService(paths);
+        return compiler;
+    }
+
+    /**
+     * Creates a point to point intent compiler for suggested path case.
+     *
+     * @param paths             all the possible paths in the network
+     * @param resourceService   service to use for resource allocation requests
+     * @return point to point compiler
+     */
+    private PointToPointIntentCompiler makeCompilerSuggestedPath(String[][] paths,
+                                                    ResourceService resourceService) {
+        final PointToPointIntentCompiler compiler = new PointToPointIntentCompiler();
+        compiler.pathService = new IntentTestsMocks.MockMultiplePathService(paths);
+        compiler.linkService = new IntentTestsMocks.MockLinkService(paths);
+
+        if (resourceService == null) {
+            compiler.resourceService = new MockResourceService();
+        } else {
+            compiler.resourceService = resourceService;
+        }
+
+        return compiler;
+    }
+
+    /**
      * Creates a point to point intent compiler for a three switch linear
      * topology.
      *
@@ -582,4 +666,151 @@
         assertThat(resourceAllocations, hasSize(6));
         assertEquals(expectedresourceAllocations, resourceAllocations);
     }
+
+    /**
+     * Test if a suggested path is correctly applied.
+     */
+    @Test
+    public void testSuggestedPath() {
+        String[] suggestedPathHops = {S1, S3, S4, S5, S6, S8};
+        List<Link> suggestedPath = NetTestTools.createPath(suggestedPathHops).links();
+
+        PointToPointIntent intent = makeIntentSuggestedPath(new ConnectPoint(DID_1, PORT_1),
+                                                            new ConnectPoint(DID_8, PORT_2),
+                                                            suggestedPath);
+
+        String[][] paths = {{S1, S2, S8}, suggestedPathHops};
+        PointToPointIntentCompiler compiler = makeCompilerSuggestedPath(paths);
+
+        List<Intent> result = compiler.compile(intent, null);
+        assertThat(result, is(Matchers.notNullValue()));
+        assertThat(result, hasSize(1));
+        Intent resultIntent = result.get(0);
+        assertThat(resultIntent instanceof LinkCollectionIntent, is(true));
+
+        if (resultIntent instanceof LinkCollectionIntent) {
+            LinkCollectionIntent resultLinkIntent = (LinkCollectionIntent) resultIntent;
+            FilteredConnectPoint ingressPoint = new FilteredConnectPoint(new ConnectPoint(DID_1, PORT_1));
+            FilteredConnectPoint egressPoint = new FilteredConnectPoint(new ConnectPoint(DID_8, PORT_2));
+            // 5 links for the hops, plus one default link on ingress and egress
+            assertThat(resultLinkIntent.links(), hasSize(suggestedPathHops.length - 1));
+            assertThat(resultLinkIntent.links(), linksHasPath(S1, S3));
+            assertThat(resultLinkIntent.links(), linksHasPath(S3, S4));
+            assertThat(resultLinkIntent.links(), linksHasPath(S4, S5));
+            assertThat(resultLinkIntent.links(), linksHasPath(S5, S6));
+            assertThat(resultLinkIntent.links(), linksHasPath(S6, S8));
+            assertThat(resultLinkIntent.filteredIngressPoints(), is(ImmutableSet.of(ingressPoint)));
+            assertThat(resultLinkIntent.filteredEgressPoints(), is(ImmutableSet.of(egressPoint)));
+        }
+        assertThat("key is inherited", resultIntent.key(), is(intent.key()));
+    }
+
+    /**
+     * Test that if a suggested path isn't available it applies another available path.
+     */
+    @Test
+    public void testSuggestedPathNotAvailable() {
+        String[] suggestedPathHops = {S1, S3, S8};
+        String[] shortestPath = {S1, S2, S8};
+        List<Link> suggestedPath = NetTestTools.createPath(suggestedPathHops).links();
+
+        PointToPointIntent intent = makeIntentSuggestedPath(new ConnectPoint(DID_1, PORT_1),
+                                                            new ConnectPoint(DID_8, PORT_2),
+                                                            suggestedPath);
+
+        String[][] path = {shortestPath};
+        PointToPointIntentCompiler compiler = makeCompilerSuggestedPath(path);
+
+        List<Intent> result = compiler.compile(intent, null);
+        assertThat(result, is(Matchers.notNullValue()));
+        assertThat(result, hasSize(1));
+        Intent resultIntent = result.get(0);
+        assertThat(resultIntent instanceof LinkCollectionIntent, is(true));
+
+        if (resultIntent instanceof LinkCollectionIntent) {
+            LinkCollectionIntent resultLinkIntent = (LinkCollectionIntent) resultIntent;
+            FilteredConnectPoint ingressPoint = new FilteredConnectPoint(new ConnectPoint(DID_1, PORT_1));
+            FilteredConnectPoint egressPoint = new FilteredConnectPoint(new ConnectPoint(DID_8, PORT_2));
+            // 5 links for the hops, plus one default link on ingress and egress
+            assertThat(resultLinkIntent.links(), hasSize(shortestPath.length - 1));
+            assertThat(resultLinkIntent.links(), linksHasPath(S1, S2));
+            assertThat(resultLinkIntent.links(), linksHasPath(S2, S8));
+            assertThat(resultLinkIntent.filteredIngressPoints(), is(ImmutableSet.of(ingressPoint)));
+            assertThat(resultLinkIntent.filteredEgressPoints(), is(ImmutableSet.of(egressPoint)));
+        }
+        assertThat("key is inherited", resultIntent.key(), is(intent.key()));
+    }
+
+    /**
+     * Tests that requests with suggested path
+     * and with sufficient available bandwidth succeed.
+     */
+    @Test
+    public void testSuggestedPathBandwidthConstrainedIntentSuccess() {
+        final double bpsTotal = 1000.0;
+        final double bpsToReserve = 100.0;
+
+        final ResourceService resourceService =
+                MockResourceService.makeCustomBandwidthResourceService(bpsTotal);
+        final List<Constraint> constraints =
+                Collections.singletonList(new BandwidthConstraint(Bandwidth.bps(bpsToReserve)));
+
+        String[] suggestedPathHops = {S1, S4, S5, S3};
+        List<Link> suggestedPath = NetTestTools.createPath(suggestedPathHops).links();
+
+        final PointToPointIntent intent = makeIntentSuggestedPath(
+                new ConnectPoint(DID_1, PORT_1),
+                new ConnectPoint(DID_3, PORT_2),
+                suggestedPath,
+                constraints);
+
+        String[][] hops = {{S1, S2, S3}, suggestedPathHops};
+        final PointToPointIntentCompiler compiler = makeCompilerSuggestedPath(hops,
+                                                                 resourceService);
+
+        final List<Intent> compiledIntents = compiler.compile(intent, null);
+
+        assertThat(compiledIntents, Matchers.notNullValue());
+        assertThat(compiledIntents, hasSize(1));
+
+        assertThat("key is inherited",
+                   compiledIntents.stream().map(Intent::key).collect(Collectors.toList()),
+                   everyItem(is(intent.key())));
+
+    }
+
+    /**
+     * Tests that requests with insufficient available bandwidth fail.
+     */
+    @Test
+    public void testSuggestedPathBandwidthConstrainedIntentFailure() {
+        final double bpsTotal = 10.0;
+
+        final ResourceService resourceService =
+                MockResourceService.makeCustomBandwidthResourceService(bpsTotal);
+        final List<Constraint> constraints =
+                Collections.singletonList(new BandwidthConstraint(Bandwidth.bps(BPS_TO_RESERVE)));
+
+        String[] suggestedPathHops = {S1, S4, S5, S3};
+        List<Link> suggestedPath = NetTestTools.createPath(suggestedPathHops).links();
+
+        try {
+            final PointToPointIntent intent = makeIntentSuggestedPath(
+                    new ConnectPoint(DID_1, PORT_1),
+                    new ConnectPoint(DID_3, PORT_2),
+                    suggestedPath,
+                    constraints);
+
+            String[][] paths = {{S1, S2, S3}, suggestedPathHops};
+            final PointToPointIntentCompiler compiler = makeCompilerSuggestedPath(paths,
+                                                                     resourceService);
+
+            compiler.compile(intent, null);
+
+            fail("Point to Point compilation with insufficient bandwidth does "
+                         + "not throw exception.");
+        } catch (PathNotFoundException noPath) {
+            assertThat(noPath.getMessage(), containsString("No path"));
+        }
+    }
 }