ONOS-4971: Synthetic Link Data -- WIP

- Breaking out UiLink to subclasses for device links, host links, region links, region-device links,
    - (soon, also peer links).
- Augmenting UiLinkId to include regions as endpoints.
- Introduced UiSynthLink to encapsulate synthetic links bound to regions.
- Model Cache now computes synthetic links from the underlying link data.
- Added endPointA/B() and type() methods to UiLink.
- Updated topo2CurrentRegion response to include synth-links for the region.

Change-Id: Ifa62a15fbe0a58b134d92278b201fa7a72cbfa83
diff --git a/core/api/src/test/java/org/onosproject/ui/model/topo/UiEdgeLinkTest.java b/core/api/src/test/java/org/onosproject/ui/model/topo/UiEdgeLinkTest.java
new file mode 100644
index 0000000..d5b213a
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/ui/model/topo/UiEdgeLinkTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.ui.model.topo;
+
+import org.junit.Test;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultEdgeLink;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.EdgeLink;
+import org.onosproject.net.PortNumber;
+import org.onosproject.ui.model.AbstractUiModelTest;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit tests for {@link UiEdgeLink}.
+ */
+public class UiEdgeLinkTest extends AbstractUiModelTest {
+
+    private static final String PHANTOM_HOST_ID = "00:00:00:00:00:00/None";
+    private static final String D1_P8 = "dev-1/8";
+
+    private static final DeviceId DEV = DeviceId.deviceId("dev-1");
+    private static final PortNumber P8 = PortNumber.portNumber(8);
+    private static final ConnectPoint CP = new ConnectPoint(DEV, P8);
+
+    private static final EdgeLink EDGE_LINK =
+            DefaultEdgeLink.createEdgeLink(CP, true);
+
+    @Test
+    public void basic() {
+        title("basic");
+        UiLinkId id = UiLinkId.uiLinkId(EDGE_LINK);
+        UiEdgeLink link = new UiEdgeLink(null, id);
+        link.attachEdgeLink(EDGE_LINK);
+        print(link);
+        print(link.endPointA());
+        print(link.endPointB());
+
+        assertEquals("bad end point A", PHANTOM_HOST_ID, link.endPointA());
+        assertEquals("bad end point B", D1_P8, link.endPointB());
+    }
+}
diff --git a/core/api/src/test/java/org/onosproject/ui/model/topo/UiLinkIdTest.java b/core/api/src/test/java/org/onosproject/ui/model/topo/UiLinkIdTest.java
index e129e26..80666d6 100644
--- a/core/api/src/test/java/org/onosproject/ui/model/topo/UiLinkIdTest.java
+++ b/core/api/src/test/java/org/onosproject/ui/model/topo/UiLinkIdTest.java
@@ -17,17 +17,23 @@
 package org.onosproject.ui.model.topo;
 
 import org.junit.Test;
+import org.onlab.packet.MacAddress;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DefaultLink;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.HostId;
 import org.onosproject.net.Link;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.provider.ProviderId;
+import org.onosproject.net.region.RegionId;
 import org.onosproject.ui.model.AbstractUiModelTest;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
 import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.HostId.hostId;
+import static org.onosproject.net.PortNumber.P0;
 import static org.onosproject.net.PortNumber.portNumber;
 
 /**
@@ -35,8 +41,15 @@
  */
 public class UiLinkIdTest extends AbstractUiModelTest {
 
+    private static final RegionId REG_1 = RegionId.regionId("Region-1");
+    private static final RegionId REG_2 = RegionId.regionId("Region-2");
+
+    private static final MacAddress MAC_A = MacAddress.valueOf(0x123456L);
+    private static final HostId HOST_A = hostId(MAC_A);
+
     private static final DeviceId DEV_X = deviceId("device-X");
     private static final DeviceId DEV_Y = deviceId("device-Y");
+
     private static final PortNumber P1 = portNumber(1);
     private static final PortNumber P2 = portNumber(2);
     private static final PortNumber P3 = portNumber(3);
@@ -45,6 +58,8 @@
     private static final ConnectPoint CP_Y2 = new ConnectPoint(DEV_Y, P2);
     private static final ConnectPoint CP_Y3 = new ConnectPoint(DEV_Y, P3);
 
+    private static final ConnectPoint CP_HA = new ConnectPoint(HOST_A, P0);
+
     private static final Link LINK_X1_TO_Y2 = DefaultLink.builder()
             .providerId(ProviderId.NONE)
             .src(CP_X1)
@@ -66,6 +81,12 @@
             .type(Link.Type.DIRECT)
             .build();
 
+    private static final Link LINK_HA_TO_X1 = DefaultLink.builder()
+            .providerId(ProviderId.NONE)
+            .src(CP_HA)
+            .dst(CP_X1)
+            .type(Link.Type.EDGE)
+            .build();
 
     @Test
     public void canonical() {
@@ -86,4 +107,61 @@
         print("link other: %s", other);
         assertNotEquals("equiv?", one, other);
     }
+
+    @Test
+    public void edgeLink() {
+        title("edgeLink");
+        UiLinkId id = UiLinkId.uiLinkId(LINK_HA_TO_X1);
+        print("link: %s", id);
+        assertEquals("wrong port A", P0, id.portA());
+        assertEquals("wrong element A", HOST_A, id.elementA());
+        assertEquals("wrong port B", P1, id.portB());
+        assertEquals("wrong element B", DEV_X, id.elementB());
+        assertNull("region A?", id.regionA());
+        assertNull("region B?", id.regionB());
+    }
+
+    @Test
+    public void deviceLink() {
+        title("deviceLink");
+        UiLinkId id = UiLinkId.uiLinkId(LINK_X1_TO_Y2);
+        print("link: %s", id);
+        assertEquals("wrong port A", P1, id.portA());
+        assertEquals("wrong element A", DEV_X, id.elementA());
+        assertEquals("wrong port B", P2, id.portB());
+        assertEquals("wrong element B", DEV_Y, id.elementB());
+        assertNull("region A?", id.regionA());
+        assertNull("region B?", id.regionB());
+    }
+
+    @Test
+    public void regionLink() {
+        title("regionLink");
+        UiLinkId idFirst = UiLinkId.uiLinkId(REG_1, REG_2);
+        UiLinkId idSecond = UiLinkId.uiLinkId(REG_2, REG_1);
+        print(" first: %s", idFirst);
+        print("second: %s", idSecond);
+        assertEquals("Not same ID", idFirst, idSecond);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void identicalRegionBad() {
+        UiLinkId.uiLinkId(REG_1, REG_1);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void nullRegionBad() {
+        UiLinkId.uiLinkId(REG_1, (RegionId) null);
+    }
+
+    @Test
+    public void regionDeviceLink() {
+        title("regionDeviceLink");
+        UiLinkId id = UiLinkId.uiLinkId(REG_1, DEV_X, P1);
+        print("id: %s", id);
+        assertEquals("region ID", REG_1, id.regionA());
+        assertEquals("device ID", DEV_X, id.elementB());
+        assertEquals("port", P1, id.portB());
+    }
+
 }
diff --git a/core/api/src/test/java/org/onosproject/ui/model/topo/UiRegionDeviceLinkTest.java b/core/api/src/test/java/org/onosproject/ui/model/topo/UiRegionDeviceLinkTest.java
new file mode 100644
index 0000000..3fd70d1
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/ui/model/topo/UiRegionDeviceLinkTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.ui.model.topo;
+
+import org.junit.Test;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.region.RegionId;
+import org.onosproject.ui.model.AbstractUiModelTest;
+
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.PortNumber.P0;
+import static org.onosproject.net.region.RegionId.regionId;
+
+/**
+ * Unit tests for {@link UiRegionDeviceLink}.
+ */
+public class UiRegionDeviceLinkTest extends AbstractUiModelTest {
+
+    private static final RegionId R1 = regionId("r1");
+    private static final DeviceId DEV_X = deviceId("device-X");
+
+    @Test
+    public void basic() {
+        title("basic");
+        UiLinkId id = UiLinkId.uiLinkId(R1, DEV_X, P0);
+        UiRegionDeviceLink link = new UiRegionDeviceLink(null, id);
+        print(link);
+        assertEquals("region", R1, link.region());
+        assertEquals("device", DEV_X, link.device());
+        assertEquals("port", P0, link.port());
+    }
+}
diff --git a/core/api/src/test/java/org/onosproject/ui/model/topo/UiRegionLinkTest.java b/core/api/src/test/java/org/onosproject/ui/model/topo/UiRegionLinkTest.java
new file mode 100644
index 0000000..de00bd2
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/ui/model/topo/UiRegionLinkTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.ui.model.topo;
+
+import org.junit.Test;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.net.region.RegionId;
+import org.onosproject.ui.model.AbstractUiModelTest;
+
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.PortNumber.P0;
+import static org.onosproject.net.region.RegionId.regionId;
+
+/**
+ * Unit tests for {@link UiRegionLink}.
+ */
+public class UiRegionLinkTest extends AbstractUiModelTest {
+
+    private static final RegionId R1 = regionId("r1");
+    private static final RegionId R2 = regionId("r2");
+
+    private static final DeviceId DEV_X = deviceId("device-X");
+    private static final DeviceId DEV_Y = deviceId("device-Y");
+
+    private static final ConnectPoint CP_X = new ConnectPoint(DEV_X, P0);
+    private static final ConnectPoint CP_Y = new ConnectPoint(DEV_Y, P0);
+
+    private static final Link LINK_X_TO_Y = DefaultLink.builder()
+            .providerId(ProviderId.NONE)
+            .src(CP_X)
+            .dst(CP_Y)
+            .type(Link.Type.DIRECT)
+            .build();
+
+
+    @Test(expected = NullPointerException.class)
+    public void nullPointerRegion() {
+        title("nullPointerRegion");
+        new UiRegionLink(null, null);
+    }
+
+    @Test
+    public void regionToRegion() {
+        title("regionToRegion");
+        UiLinkId id = UiLinkId.uiLinkId(R1, R2);
+        UiRegionLink link = new UiRegionLink(null, id);
+        print("link: %s", link);
+        assertEquals("bad first region", R1, link.regionA());
+        assertEquals("bad second region", R2, link.regionB());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void wrongLinkType() {
+        title("wrongLinkType");
+        UiLinkId id = UiLinkId.uiLinkId(LINK_X_TO_Y);
+        new UiRegionLink(null, id);
+    }
+}
diff --git a/core/api/src/test/java/org/onosproject/ui/model/topo/UiTopologyTest.java b/core/api/src/test/java/org/onosproject/ui/model/topo/UiTopologyTest.java
index 17d2de7..5df0a9f 100644
--- a/core/api/src/test/java/org/onosproject/ui/model/topo/UiTopologyTest.java
+++ b/core/api/src/test/java/org/onosproject/ui/model/topo/UiTopologyTest.java
@@ -16,20 +16,165 @@
 
 package org.onosproject.ui.model.topo;
 
+import org.junit.Before;
 import org.junit.Test;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.net.region.RegionId;
 import org.onosproject.ui.AbstractUiTest;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.PortNumber.portNumber;
+
 /**
  * Unit tests for {@link UiTopology}.
  */
 public class UiTopologyTest extends AbstractUiTest {
 
+    private static final DeviceId DEV_X = deviceId("dev-X");
+    private static final DeviceId DEV_Y = deviceId("dev-Y");
+    private static final PortNumber P1 = portNumber(1);
+    private static final PortNumber P2 = portNumber(2);
+
+    private static final String DEV_X_ID = "dev-x/1";
+    private static final String DEV_Y_ID = "dev-y/2";
+
+    private static final ConnectPoint CP_X1 = new ConnectPoint(DEV_X, P1);
+    private static final ConnectPoint CP_Y2 = new ConnectPoint(DEV_Y, P2);
+
+    private static final Link LINK_X1_TO_Y2 = DefaultLink.builder()
+            .providerId(ProviderId.NONE)
+            .src(CP_X1)
+            .dst(CP_Y2)
+            .type(Link.Type.DIRECT)
+            .build();
+
+    private static final UiLinkId DX1_DY2 = UiLinkId.uiLinkId(LINK_X1_TO_Y2);
+
+    private static final RegionId ROOT = UiRegion.NULL_ID;
+    private static final RegionId R1 = RegionId.regionId("R1");
+    private static final RegionId R2 = RegionId.regionId("R2");
+    private static final RegionId R3 = RegionId.regionId("R3");
+
+    private static final String DEV_LINK_CLASS = "UiDeviceLink";
+    private static final String REG_LINK_CLASS = "UiRegionLink";
+    private static final String REG_DEV_LINK_CLASS = "UiRegionDeviceLink";
+
+
     private UiTopology topo;
+    private UiDeviceLink devLink;
+
+    private List<RegionId> xBranch;
+    private List<RegionId> yBranch;
+    private UiSynthLink synth;
+
+    @Before
+    public void setUp() {
+        topo = new UiTopology();
+        devLink = new UiDeviceLink(null, DX1_DY2);
+        devLink.attachBackingLink(LINK_X1_TO_Y2);
+    }
 
     @Test
     public void basic() {
         title("basic");
-        topo = new UiTopology();
         print(topo);
     }
+
+    private List<RegionId> branch(RegionId... ids) {
+        List<RegionId> result = new ArrayList<>(ids.length);
+        Collections.addAll(result, ids);
+        return result;
+    }
+
+    private void verifySynth(RegionId id, String cls, String epA, String epB) {
+        synth = topo.makeSynthLink(devLink, xBranch, yBranch);
+        UiLink ulink = synth.link();
+        print(synth);
+        print("EpA{%s}  EpB{%s}", ulink.endPointA(), ulink.endPointB());
+
+        assertEquals("wrong region", id, synth.regionId());
+        assertEquals("wrong link class", cls, ulink.type());
+        assertEquals("wrong EP A", epA, ulink.endPointA());
+        assertEquals("wrong EP B", epB, ulink.endPointB());
+    }
+
+    @Test
+    public void makeSynthDevToDevRoot() {
+        title("makeSynthDevToDevRoot");
+        xBranch = branch(ROOT);
+        yBranch = branch(ROOT);
+        verifySynth(ROOT, DEV_LINK_CLASS, DEV_X_ID, DEV_Y_ID);
+    }
+
+    @Test
+    public void makeSynthDevToDevR1() {
+        title("makeSynthDevToDevR1");
+        xBranch = branch(ROOT, R1);
+        yBranch = branch(ROOT, R1);
+        verifySynth(R1, DEV_LINK_CLASS, DEV_X_ID, DEV_Y_ID);
+    }
+
+    @Test
+    public void makeSynthDevToDevR2() {
+        title("makeSynthDevToDevR2");
+        xBranch = branch(ROOT, R1, R2);
+        yBranch = branch(ROOT, R1, R2);
+        verifySynth(R2, DEV_LINK_CLASS, DEV_X_ID, DEV_Y_ID);
+    }
+
+    @Test
+    public void makeSynthRegToRegRoot() {
+        title("makeSynthRegToRegRoot");
+        xBranch = branch(ROOT, R1);
+        yBranch = branch(ROOT, R2);
+        verifySynth(ROOT, REG_LINK_CLASS, R1.id(), R2.id());
+    }
+
+    @Test
+    public void makeSynthRegToRegR1() {
+        title("makeSynthRegToRegR1");
+        xBranch = branch(ROOT, R1, R2);
+        yBranch = branch(ROOT, R1, R3);
+        verifySynth(R1, REG_LINK_CLASS, R2.id(), R3.id());
+    }
+
+    @Test
+    public void makeSynthRegToDevRoot() {
+        title("makeSynthRegToDevRoot");
+
+        // Note: link is canonicalized to region--device order
+
+        xBranch = branch(ROOT);
+        yBranch = branch(ROOT, R1);
+        verifySynth(ROOT, REG_DEV_LINK_CLASS, R1.id(), DEV_X_ID);
+
+        xBranch = branch(ROOT, R1);
+        yBranch = branch(ROOT);
+        verifySynth(ROOT, REG_DEV_LINK_CLASS, R1.id(), DEV_Y_ID);
+    }
+
+    @Test
+    public void makeSynthRegToDevR3() {
+        title("makeSynthRegToDevR3");
+
+        // Note: link is canonicalized to region--device order
+
+        xBranch = branch(ROOT, R3);
+        yBranch = branch(ROOT, R3, R1);
+        verifySynth(R3, REG_DEV_LINK_CLASS, R1.id(), DEV_X_ID);
+
+        xBranch = branch(ROOT, R3, R1);
+        yBranch = branch(ROOT, R3);
+        verifySynth(R3, REG_DEV_LINK_CLASS, R1.id(), DEV_Y_ID);
+    }
 }