ONOS-1334 - Allow multi to single intent across a single switch

Change-Id: I8be3dbc403ea1202fd496e666955402247f71bf1
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompiler.java b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompiler.java
index e9e319e..72077f9 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompiler.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/LinkCollectionIntentCompiler.java
@@ -27,6 +27,7 @@
 import org.onosproject.core.DefaultGroupId;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.EdgeLink;
 import org.onosproject.net.Link;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.flow.DefaultFlowRule;
@@ -77,8 +78,18 @@
         SetMultimap<DeviceId, PortNumber> outputPorts = HashMultimap.create();
 
         for (Link link : intent.links()) {
-            inputPorts.put(link.dst().deviceId(), link.dst().port());
-            outputPorts.put(link.src().deviceId(), link.src().port());
+            DeviceId srcDeviceId;
+            DeviceId dstDeviceId;
+
+            if (link instanceof EdgeLink) {
+                EdgeLink edgeLink = (EdgeLink) link;
+                dstDeviceId = edgeLink.hostLocation().deviceId();
+                srcDeviceId = dstDeviceId;
+            } else {
+                inputPorts.put(link.dst().deviceId(), link.dst().port());
+                srcDeviceId = link.src().deviceId();
+            }
+            outputPorts.put(srcDeviceId, link.src().port());
         }
 
         for (ConnectPoint ingressPoint : intent.ingressPoints()) {
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 a20b533..d63d379 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
@@ -43,6 +43,8 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 
+import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
+
 /**
  * An intent compiler for
  * {@link org.onosproject.net.intent.MultiPointToSinglePointIntent}.
@@ -71,28 +73,37 @@
     public List<Intent> compile(MultiPointToSinglePointIntent intent, List<Intent> installable,
                                 Set<LinkResourceAllocations> resources) {
         Map<DeviceId, Link> links = new HashMap<>();
+        Map<DeviceId, Link> edgeLinks = new HashMap<>();
+        ConnectPoint egressPoint = intent.egressPoint();
 
         for (ConnectPoint ingressPoint : intent.ingressPoints()) {
-            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 (ingressPoint.deviceId().equals(egressPoint.deviceId())) {
+                edgeLinks.put(ingressPoint.deviceId(), createEdgeLink(ingressPoint, true));
+                edgeLinks.put(egressPoint.deviceId(), createEdgeLink(egressPoint, false));
+            } else {
+                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;
+                    }
 
-                links.put(link.src().deviceId(), link);
+                    links.put(link.src().deviceId(), link);
+                }
             }
         }
 
+        Set<Link> allLinks = Sets.newHashSet(links.values());
+        allLinks.addAll(edgeLinks.values());
         Intent result = LinkCollectionIntent.builder()
                 .appId(intent.appId())
                 .selector(intent.selector())
                 .treatment(intent.treatment())
-                .links(Sets.newHashSet(links.values()))
+                .links(Sets.newHashSet(allLinks))
                 .ingressPoints(intent.ingressPoints())
                 .egressPoints(ImmutableSet.of(intent.egressPoint()))
                 .priority(intent.priority())
diff --git a/core/net/src/test/java/org/onosproject/net/intent/LinksHaveEntryWithSourceDestinationPairMatcher.java b/core/net/src/test/java/org/onosproject/net/intent/LinksHaveEntryWithSourceDestinationPairMatcher.java
index 48a86c4..d34143c 100644
--- a/core/net/src/test/java/org/onosproject/net/intent/LinksHaveEntryWithSourceDestinationPairMatcher.java
+++ b/core/net/src/test/java/org/onosproject/net/intent/LinksHaveEntryWithSourceDestinationPairMatcher.java
@@ -19,6 +19,7 @@
 
 import org.hamcrest.Description;
 import org.hamcrest.TypeSafeMatcher;
+import org.onosproject.net.EdgeLink;
 import org.onosproject.net.Link;
 
 /**
@@ -46,6 +47,14 @@
     @Override
     public boolean matchesSafely(Collection<Link> links) {
         for (Link link : links) {
+            if (source.equals(destination) && link instanceof EdgeLink) {
+                EdgeLink edgeLink = (EdgeLink) link;
+                if (edgeLink.hostLocation().elementId()
+                        .toString().endsWith(source)) {
+                    return true;
+                }
+            }
+
             if (link.src().elementId().toString().endsWith(source) &&
                     link.dst().elementId().toString().endsWith(destination)) {
                 return true;
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 0d681a7..a3b4525 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
@@ -15,10 +15,14 @@
  */
 package org.onosproject.net.intent.impl.compiler;
 
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
 import org.hamcrest.Matchers;
 import org.junit.Test;
-import org.onosproject.core.ApplicationId;
 import org.onosproject.TestApplicationId;
+import org.onosproject.core.ApplicationId;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.ElementId;
 import org.onosproject.net.Path;
@@ -32,10 +36,7 @@
 import org.onosproject.net.topology.LinkWeight;
 import org.onosproject.net.topology.PathService;
 
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
+import static org.hamcrest.CoreMatchers.instanceOf;
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.hasSize;
@@ -76,9 +77,11 @@
 
             String[] allHops = new String[pathHops.length + 1];
             allHops[0] = src.toString();
-            System.arraycopy(pathHops, 0, allHops, 1, pathHops.length);
-
+            if (pathHops.length != 0) {
+                System.arraycopy(pathHops, 0, allHops, 1, pathHops.length);
+            }
             result.add(createPath(allHops));
+
             return result;
         }
 
@@ -98,7 +101,7 @@
      */
     private MultiPointToSinglePointIntent makeIntent(String[] ingressIds, String egressId) {
         Set<ConnectPoint> ingressPoints = new HashSet<>();
-        ConnectPoint egressPoint = connectPoint(egressId, 1);
+        ConnectPoint egressPoint = connectPoint(egressId, 2);
 
         for (String ingressId : ingressIds) {
             ingressPoints.add(connectPoint(ingressId, 1));
@@ -228,4 +231,35 @@
             assertThat(linkIntent.links(), linksHasPath("n1", egress));
         }
     }
+
+    /**
+     * Tests ingress and egress on the same device.
+     */
+    @Test
+    public void testSameDeviceCompilation() {
+        String[] ingress = {"i1", "i2"};
+        String egress = "i1";
+
+        MultiPointToSinglePointIntent intent = makeIntent(ingress, egress);
+        assertThat(intent, is(notNullValue()));
+
+        final String[] hops = {"i1", "i2"};
+        MultiPointToSinglePointIntentCompiler compiler = makeCompiler(hops);
+        assertThat(compiler, is(notNullValue()));
+
+        List<Intent> result = compiler.compile(intent, null, null);
+        assertThat(result, is(notNullValue()));
+        assertThat(result, hasSize(1));
+        Intent resultIntent = result.get(0);
+        assertThat(resultIntent, instanceOf(LinkCollectionIntent.class));
+
+        if (resultIntent instanceof LinkCollectionIntent) {
+            LinkCollectionIntent linkIntent = (LinkCollectionIntent) resultIntent;
+            assertThat(linkIntent.links(), hasSize(ingress.length + 1));
+
+            assertThat(linkIntent.links(), linksHasPath("i2", "i1"));
+
+            assertThat(linkIntent.links(), linksHasPath("i1", "i1"));
+        }
+    }
 }