Fix ONOS-151 to support installation of PointToPointIntent

- Now support installation of PointToPointIntent that specifies the same
  switches as source element ID and destination element ID

Change-Id: If206f3fde77ca198fe1df078e7292a05e5bd7424
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java
index 7a0e365..3873611 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java
@@ -19,11 +19,16 @@
 import java.util.List;
 
 import com.google.common.base.MoreObjects;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
 import org.onlab.onos.core.ApplicationId;
+import org.onlab.onos.net.Link;
 import org.onlab.onos.net.Path;
 import org.onlab.onos.net.flow.TrafficSelector;
 import org.onlab.onos.net.flow.TrafficTreatment;
 
+import static com.google.common.base.Preconditions.checkArgument;
+
 /**
  * Abstraction of explicitly path specified connectivity intent.
  */
@@ -61,6 +66,7 @@
                       TrafficTreatment treatment, Path path, List<Constraint> constraints) {
         super(id(PathIntent.class, selector, treatment, path, constraints), appId,
                 resources(path.links()), selector, treatment, constraints);
+        PathIntent.validate(path.links());
         this.path = path;
     }
 
@@ -72,6 +78,32 @@
         this.path = null;
     }
 
+    // NOTE: This methods takes linear time with the number of links.
+    /**
+     * Validates that source element ID and destination element ID of a link are
+     * different for the specified all links and that destination element ID of a link and source
+     * element ID of the next adjacent source element ID are same for the specified all links.
+     *
+     * @param links
+     */
+    public static void validate(List<Link> links) {
+        checkArgument(Iterables.all(links, new Predicate<Link>() {
+            @Override
+            public boolean apply(Link link) {
+                return !link.src().elementId().equals(link.dst().elementId());
+            }
+        }), "element of src and dst in a link must be different: {}", links);
+
+        boolean adjacentSame = true;
+        for (int i = 0; i < links.size() - 1; i++) {
+            if (!links.get(i).dst().elementId().equals(links.get(i + 1).src().elementId())) {
+                adjacentSame = false;
+                break;
+            }
+        }
+        checkArgument(adjacentSame, "adjacent links must share the same element: {}", links);
+    }
+
     /**
      * Returns the links which the traffic goes along.
      *
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/PathIntentTest.java b/core/api/src/test/java/org/onlab/onos/net/intent/PathIntentTest.java
index 5fce240..4451e66 100644
--- a/core/api/src/test/java/org/onlab/onos/net/intent/PathIntentTest.java
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/PathIntentTest.java
@@ -16,10 +16,21 @@
 package org.onlab.onos.net.intent;
 
 import org.junit.Test;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DefaultLink;
+import org.onlab.onos.net.DefaultPath;
+import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.net.NetTestTools;
 import org.onlab.onos.net.Path;
+import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.provider.ProviderId;
+
+import java.util.Arrays;
 
 import static org.junit.Assert.assertEquals;
+import static org.onlab.onos.net.DeviceId.deviceId;
+import static org.onlab.onos.net.Link.Type.DIRECT;
+import static org.onlab.onos.net.PortNumber.portNumber;
 
 public class PathIntentTest extends ConnectivityIntentTest {
     // 111:11 --> 222:22
@@ -28,6 +39,21 @@
     // 111:11 --> 333:33
     private static final Path PATH2 = NetTestTools.createPath("222", "333");
 
+    private final ProviderId provider1 = new ProviderId("of", "1");
+    private final DeviceId device1 = deviceId("1");
+    private final DeviceId device2 = deviceId("2");
+    private final PortNumber port1 = portNumber(1);
+    private final PortNumber port2 = portNumber(2);
+    private final PortNumber port3 = portNumber(3);
+    private final PortNumber port4 = portNumber(4);
+    private final ConnectPoint cp1 = new ConnectPoint(device1, port1);
+    private final ConnectPoint cp2 = new ConnectPoint(device1, port2);
+    private final ConnectPoint cp3 = new ConnectPoint(device2, port3);
+    private final ConnectPoint cp4 = new ConnectPoint(device2, port4);
+    private final DefaultLink link1 = new DefaultLink(provider1, cp1, cp2, DIRECT);
+    private final DefaultLink link2 = new DefaultLink(provider1, cp1, cp2, DIRECT);
+    private final double cost = 1;
+
     @Test
     public void basics() {
         PathIntent intent = createOne();
@@ -46,4 +72,23 @@
     protected PathIntent createAnother() {
         return new PathIntent(APPID, MATCH, NOP, PATH2);
     }
+
+    /**
+     * Tests the constructor raises IllegalArgumentException when the same device is specified in
+     * source and destination of a link.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testRaiseExceptionWhenSameDevices() {
+        new PathIntent(APPID, MATCH, NOP, new DefaultPath(provider1, Arrays.asList(link1), cost));
+    }
+
+    /**
+     * Tests the constructor raises IllegalArgumentException when the different elements are specified
+     * in source element of the first link and destination element of the second link.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testRaiseExceptionWhenDifferentDevice() {
+        new PathIntent(APPID, MATCH, NOP, new DefaultPath(provider1, Arrays.asList(link1, link2), cost));
+    }
+
 }
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/PointToPointIntentCompiler.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PointToPointIntentCompiler.java
index a4c8d58..b124602 100644
--- a/core/net/src/main/java/org/onlab/onos/net/intent/impl/PointToPointIntentCompiler.java
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/PointToPointIntentCompiler.java
@@ -19,8 +19,6 @@
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
 import org.onlab.onos.net.ConnectPoint;
-import org.onlab.onos.net.DefaultEdgeLink;
-import org.onlab.onos.net.DefaultLink;
 import org.onlab.onos.net.DefaultPath;
 import org.onlab.onos.net.Link;
 import org.onlab.onos.net.Path;
@@ -35,7 +33,7 @@
 import java.util.Set;
 
 import static java.util.Arrays.asList;
-import static org.onlab.onos.net.Link.Type.DIRECT;
+import static org.onlab.onos.net.DefaultEdgeLink.createEdgeLink;
 
 /**
  * An intent compiler for {@link org.onlab.onos.net.intent.PointToPointIntent}.
@@ -47,6 +45,8 @@
     // TODO: use off-the-shell core provider ID
     private static final ProviderId PID =
             new ProviderId("core", "org.onlab.onos.core", true);
+    // TODO: consider whether the default cost is appropriate or not
+    public static final int DEFAULT_COST = 1;
 
     @Activate
     public void activate() {
@@ -66,17 +66,17 @@
         ConnectPoint egressPoint = intent.egressPoint();
 
         if (ingressPoint.deviceId().equals(egressPoint.deviceId())) {
-            List<Link> links = asList(new DefaultLink(PID, ingressPoint, egressPoint, DIRECT));
-            return asList(createPathIntent(new DefaultPath(PID, links, 1), intent));
+            List<Link> links = asList(createEdgeLink(ingressPoint, true), createEdgeLink(egressPoint, false));
+            return asList(createPathIntent(new DefaultPath(PID, links, DEFAULT_COST), intent));
         }
 
         List<Link> links = new ArrayList<>();
         Path path = getPath(intent, ingressPoint.deviceId(),
                 egressPoint.deviceId());
 
-        links.add(DefaultEdgeLink.createEdgeLink(ingressPoint, true));
+        links.add(createEdgeLink(ingressPoint, true));
         links.addAll(path.links());
-        links.add(DefaultEdgeLink.createEdgeLink(egressPoint, false));
+        links.add(createEdgeLink(egressPoint, false));
 
         return asList(createPathIntent(new DefaultPath(PID, links, path.cost(),
                                                        path.annotations()), intent));
diff --git a/core/net/src/test/java/org/onlab/onos/net/intent/impl/TestPointToPointIntentCompiler.java b/core/net/src/test/java/org/onlab/onos/net/intent/impl/TestPointToPointIntentCompiler.java
index 6521f24..f57d6d9 100644
--- a/core/net/src/test/java/org/onlab/onos/net/intent/impl/TestPointToPointIntentCompiler.java
+++ b/core/net/src/test/java/org/onlab/onos/net/intent/impl/TestPointToPointIntentCompiler.java
@@ -36,6 +36,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.is;
+import static org.onlab.onos.net.DefaultEdgeLink.createEdgeLink;
 import static org.onlab.onos.net.DeviceId.deviceId;
 import static org.onlab.onos.net.NetTestTools.APP_ID;
 import static org.onlab.onos.net.NetTestTools.connectPoint;
@@ -163,9 +164,10 @@
         assertThat(compiled.get(0), is(instanceOf(PathIntent.class)));
         Path path = ((PathIntent) compiled.get(0)).path();
 
-        assertThat(path.links(), hasSize(1));
-        Link link = path.links().get(0);
-        assertThat(link.src(), is(src));
-        assertThat(link.dst(), is(dst));
+        assertThat(path.links(), hasSize(2));
+        Link firstLink = path.links().get(0);
+        assertThat(firstLink, is(createEdgeLink(src, true)));
+        Link secondLink = path.links().get(1);
+        assertThat(secondLink, is(createEdgeLink(dst, false)));
     }
 }