CORD-803 Introduce configuration for leaf pairs

Introduce 'pairDeviceId' and 'pairLocalPort'

Change-Id: I60dff15cbbc5a32a581db99f1ede61f630615283
diff --git a/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingDeviceConfig.java b/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingDeviceConfig.java
index bc0d329..42fd93e 100644
--- a/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingDeviceConfig.java
+++ b/src/main/java/org/onosproject/segmentrouting/config/SegmentRoutingDeviceConfig.java
@@ -24,6 +24,7 @@
 import org.onlab.packet.Ip6Address;
 import org.onlab.packet.MacAddress;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
 import org.onosproject.net.config.Config;
 
 import java.util.HashMap;
@@ -43,21 +44,47 @@
     private static final String IP4_SID = "ipv4NodeSid";
     private static final String IP6_SID = "ipv6NodeSid";
     private static final String EDGE = "isEdgeRouter";
+    /**
+     * Adjancency SIDs config.
+     *
+     * @deprecated in Loon (1.11). We are not using and do not plan to use it.
+     */
+    @Deprecated
     private static final String ADJSIDS = "adjacencySids";
+
+    /**
+     * Adjancency SID config.
+     *
+     * @deprecated in Loon (1.11). We are not using and do not plan to use it.
+     */
+    @Deprecated
     private static final String ADJSID = "adjSid";
+
+    /**
+     * Adjancency port config.
+     *
+     * @deprecated in Loon (1.11). We are not using and do not plan to use it.
+     */
+    @Deprecated
     private static final String PORTS = "ports";
 
+    private static final String PAIR_DEVICE_ID = "pairDeviceId";
+    private static final String PAIR_LOCAL_PORT = "pairLocalPort";
+
     @Override
     public boolean isValid() {
-        return hasOnlyFields(NAME, IP4, IP6, MAC,
-                             IP4_SID, IP6_SID, EDGE,
-                             ADJSIDS, ADJSID, PORTS) &&
+        return hasOnlyFields(NAME, IP4, IP6, MAC, IP4_SID, IP6_SID, EDGE, ADJSIDS, ADJSID, PORTS,
+                             PAIR_DEVICE_ID, PAIR_LOCAL_PORT) &&
                 name() != null &&
-                routerIpv4() != null &&
+                routerIpv4() != null && (!hasField(IP6) || routerIpv6() != null) &&
                 routerMac() != null &&
-                nodeSidIPv4() != -1 &&
+                nodeSidIPv4() != -1 && (!hasField(IP6_SID) || nodeSidIPv6() != -1) &&
                 isEdgeRouter() != null &&
-                adjacencySids() != null;
+                adjacencySids() != null &&
+                // pairDeviceId and pairLocalPort must be both configured or both omitted
+                !(hasField(PAIR_DEVICE_ID) ^ hasField(PAIR_LOCAL_PORT)) &&
+                (!hasField(PAIR_DEVICE_ID) || pairDeviceId() != null) &&
+                (!hasField(PAIR_LOCAL_PORT) || pairLocalPort() != null);
     }
 
     /**
@@ -266,4 +293,44 @@
 
         return this;
     }
+
+    /**
+     * Gets the pair device id.
+     *
+     * @return pair device id; Or null if not configured.
+     */
+    public DeviceId pairDeviceId() {
+        String pairDeviceId = get(PAIR_DEVICE_ID, null);
+        return pairDeviceId != null ? DeviceId.deviceId(pairDeviceId) : null;
+    }
+
+    /**
+     * Sets the pair device id.
+     *
+     * @param deviceId pair device id
+     * @return this configuration
+     */
+    public SegmentRoutingDeviceConfig setPairDeviceId(DeviceId deviceId) {
+        return (SegmentRoutingDeviceConfig) setOrClear(PAIR_DEVICE_ID, deviceId.toString());
+    }
+
+    /**
+     * Gets the pair local port.
+     *
+     * @return pair local port; Or null if not configured.
+     */
+    public PortNumber pairLocalPort() {
+        long pairLocalPort = get(PAIR_LOCAL_PORT, -1L);
+        return pairLocalPort != -1L ? PortNumber.portNumber(pairLocalPort) : null;
+    }
+
+    /**
+     * Sets the pair local port.
+     *
+     * @param portNumber pair local port
+     * @return this configuration
+     */
+    public SegmentRoutingDeviceConfig setPairLocalPort(PortNumber portNumber) {
+        return (SegmentRoutingDeviceConfig) setOrClear(PAIR_LOCAL_PORT, portNumber.toLong());
+    }
 }
diff --git a/src/test/java/org/onosproject/segmentrouting/config/SegmentRoutingDeviceConfigTest.java b/src/test/java/org/onosproject/segmentrouting/config/SegmentRoutingDeviceConfigTest.java
index 7f165d8..1ca6d51 100644
--- a/src/test/java/org/onosproject/segmentrouting/config/SegmentRoutingDeviceConfigTest.java
+++ b/src/test/java/org/onosproject/segmentrouting/config/SegmentRoutingDeviceConfigTest.java
@@ -23,6 +23,7 @@
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
 import org.onosproject.net.config.Config;
 import org.onosproject.net.config.ConfigApplyDelegate;
 
@@ -32,6 +33,8 @@
 import java.util.Map;
 import java.util.Set;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
 import static org.hamcrest.Matchers.is;
 import static org.junit.Assert.assertTrue;
@@ -42,8 +45,12 @@
 public class SegmentRoutingDeviceConfigTest {
     private SegmentRoutingDeviceConfig config;
     private SegmentRoutingDeviceConfig ipv6Config;
+    private SegmentRoutingDeviceConfig pairConfig;
+    private SegmentRoutingDeviceConfig invalidConfig;
     private Map<Integer, Set<Integer>> adjacencySids1;
     private Map<Integer, Set<Integer>> adjacencySids2;
+    private static final DeviceId PAIR_DEVICE_ID = DeviceId.deviceId("of:123456789ABCDEF0");
+    private static final PortNumber PAIR_LOCAL_PORT = PortNumber.portNumber(10);
 
     @Before
     public void setUp() throws Exception {
@@ -51,6 +58,10 @@
                 .getResourceAsStream("/device.json");
         InputStream ipv6JsonStream = SegmentRoutingDeviceConfigTest.class
                 .getResourceAsStream("/device-ipv6.json");
+        InputStream pairJsonStream = SegmentRoutingDeviceConfigTest.class
+                .getResourceAsStream("/device-pair.json");
+        InputStream invalidJsonStream = SegmentRoutingDeviceConfigTest.class
+                .getResourceAsStream("/device-invalid.json");
 
         adjacencySids1 = new HashMap<>();
         Set<Integer> ports1 = new HashSet<>();
@@ -72,6 +83,8 @@
         ObjectMapper mapper = new ObjectMapper();
         JsonNode jsonNode = mapper.readTree(jsonStream);
         JsonNode ipv6JsonNode = mapper.readTree(ipv6JsonStream);
+        JsonNode pairJsonNode = mapper.readTree(pairJsonStream);
+        JsonNode invalidJsonNode = mapper.readTree(invalidJsonStream);
         ConfigApplyDelegate delegate = new MockDelegate();
 
         config = new SegmentRoutingDeviceConfig();
@@ -79,12 +92,20 @@
 
         ipv6Config = new SegmentRoutingDeviceConfig();
         ipv6Config.init(subject, key, ipv6JsonNode, mapper, delegate);
+
+        pairConfig = new SegmentRoutingDeviceConfig();
+        pairConfig.init(subject, key, pairJsonNode, mapper, delegate);
+
+        invalidConfig = new SegmentRoutingDeviceConfig();
+        invalidConfig.init(subject, key, invalidJsonNode, mapper, delegate);
     }
 
     @Test
     public void testIsValid() {
         assertTrue(config.isValid());
         assertTrue(ipv6Config.isValid());
+        assertTrue(pairConfig.isValid());
+        assertFalse(invalidConfig.isValid());
     }
 
     @Test
@@ -167,6 +188,32 @@
         assertThat(config.adjacencySids(), is(adjacencySids2));
     }
 
+    @Test
+    public void testPairDeviceId() throws Exception {
+        assertNull(config.pairDeviceId());
+        assertNull(ipv6Config.pairDeviceId());
+        assertThat(pairConfig.pairDeviceId(), is(PAIR_DEVICE_ID));
+    }
+
+    @Test
+    public void testSetPairDeviceId() throws Exception {
+        config.setPairDeviceId(PAIR_DEVICE_ID);
+        assertThat(config.pairDeviceId(), is(PAIR_DEVICE_ID));
+    }
+
+    @Test
+    public void testPairLocalPort() throws Exception {
+        assertNull(config.pairLocalPort());
+        assertNull(ipv6Config.pairLocalPort());
+        assertThat(pairConfig.pairLocalPort(), is(PAIR_LOCAL_PORT));
+    }
+
+    @Test
+    public void testSetPairLocalPort() throws Exception {
+        config.setPairLocalPort(PAIR_LOCAL_PORT);
+        assertThat(config.pairLocalPort(), is(PAIR_LOCAL_PORT));
+    }
+
     private class MockDelegate implements ConfigApplyDelegate {
         @Override
         public void onApply(Config configFile) {
diff --git a/src/test/resources/device-invalid.json b/src/test/resources/device-invalid.json
new file mode 100644
index 0000000..dfcbdb8
--- /dev/null
+++ b/src/test/resources/device-invalid.json
@@ -0,0 +1,12 @@
+{
+  "name" : "Leaf-R1",
+  "ipv4NodeSid" : 101,
+  "ipv4Loopback" : "10.0.1.254",
+  "routerMac" : "00:00:00:00:01:80",
+  "isEdgeRouter" : true,
+  "adjacencySids" : [
+    { "adjSid" : 100, "ports" : [2, 3] },
+    { "adjSid" : 200, "ports" : [4, 5] }
+  ],
+  "pairDeviceId" : "of:123456789ABCDEF0"
+}
diff --git a/src/test/resources/device-pair.json b/src/test/resources/device-pair.json
new file mode 100644
index 0000000..c699ff5
--- /dev/null
+++ b/src/test/resources/device-pair.json
@@ -0,0 +1,13 @@
+{
+  "name" : "Leaf-R1",
+  "ipv4NodeSid" : 101,
+  "ipv4Loopback" : "10.0.1.254",
+  "routerMac" : "00:00:00:00:01:80",
+  "isEdgeRouter" : true,
+  "adjacencySids" : [
+    { "adjSid" : 100, "ports" : [2, 3] },
+    { "adjSid" : 200, "ports" : [4, 5] }
+  ],
+  "pairDeviceId" : "of:123456789ABCDEF0",
+  "pairLocalPort" : "10"
+}