Juniper driver - fix NETCONF discovery of JUNOS v19 devices regarding chassis-mac-addresses RPC

Added UT based on XML responses from v15 and v19 devices

Change-Id: Id4e0fe34b12dea6034e75f2276495282ea2e6aa2
diff --git a/drivers/juniper/BUILD b/drivers/juniper/BUILD
index 9bc897d..efc11d3 100644
--- a/drivers/juniper/BUILD
+++ b/drivers/juniper/BUILD
@@ -8,9 +8,12 @@
     "//drivers/utilities:onos-drivers-utilities",
 ]
 
+TEST_DEPS = TEST_ADAPTERS
+
 osgi_jar_with_tests(
     resources = glob(["src/main/resources/**"]),
     resources_root = "src/main/resources",
+    test_deps = TEST_DEPS,
     deps = COMPILE_DEPS,
 )
 
diff --git a/drivers/juniper/src/main/java/org/onosproject/drivers/juniper/JuniperUtils.java b/drivers/juniper/src/main/java/org/onosproject/drivers/juniper/JuniperUtils.java
index 14fb38a..1fc4f6c 100644
--- a/drivers/juniper/src/main/java/org/onosproject/drivers/juniper/JuniperUtils.java
+++ b/drivers/juniper/src/main/java/org/onosproject/drivers/juniper/JuniperUtils.java
@@ -16,6 +16,7 @@
 
 package org.onosproject.drivers.juniper;
 
+import com.google.common.base.MoreObjects;
 import org.apache.commons.configuration.HierarchicalConfiguration;
 import org.apache.commons.lang.StringUtils;
 import org.onlab.packet.ChassisId;
@@ -54,6 +55,7 @@
 
 import static org.onosproject.drivers.juniper.StaticRoute.DEFAULT_METRIC_STATIC_ROUTE;
 import static org.onosproject.drivers.juniper.StaticRoute.toFlowRulePriority;
+import static org.onosproject.drivers.utilities.XmlConfigParser.loadXmlString;
 import static org.onosproject.net.Device.Type.ROUTER;
 import static org.onosproject.net.PortNumber.portNumber;
 import static org.slf4j.LoggerFactory.getLogger;
@@ -113,10 +115,10 @@
     private static final String LLDP_REM_PORT_DES = "lldp-remote-port-description";
     private static final String LLDP_SUBTYPE_MAC = "Mac address";
     private static final String LLDP_SUBTYPE_INTERFACE_NAME = "Interface name";
-    private static final String REGEX_ADD =
-            ".*Private base address\\s*([:,0-9,a-f,A-F]*).*";
-    private static final Pattern ADD_PATTERN =
-            Pattern.compile(REGEX_ADD, Pattern.DOTALL);
+    // For older JUNOS e.g. 15.1
+    private static final Pattern ADD_PATTERN_JUNOS15_1 =
+            Pattern.compile(".*Private base address\\s*([:,0-9,a-f,A-F]*).*", Pattern.DOTALL);
+
 
     public static final String PROTOCOL_NAME = "protocol-name";
 
@@ -263,12 +265,12 @@
      *
      * @param deviceId    the id of the device
      * @param sysInfoCfg  system configuration
-     * @param chassisText chassis string
+     * @param chassisMacAddresses chassis MAC addresses response. Its format depends on JUNOS version of device.
      * @return device description
      */
     public static DeviceDescription parseJuniperDescription(DeviceId deviceId,
                                                             HierarchicalConfiguration sysInfoCfg,
-                                                            String chassisText) {
+                                                            String chassisMacAddresses) {
         HierarchicalConfiguration info = sysInfoCfg.configurationAt(SYS_INFO);
 
         String hw = info.getString(HW_MODEL) == null ? UNKNOWN : info.getString(HW_MODEL);
@@ -278,18 +280,36 @@
         }
         String serial = info.getString(SER_NUM) == null ? UNKNOWN : info.getString(SER_NUM);
 
-        Matcher matcher = ADD_PATTERN.matcher(chassisText);
-        if (matcher.lookingAt()) {
-            String chassis = matcher.group(1);
-            MacAddress chassisMac = MacAddress.valueOf(chassis);
-            return new DefaultDeviceDescription(deviceId.uri(), ROUTER,
-                                                JUNIPER, hw, sw, serial,
-                                                new ChassisId(chassisMac.toLong()),
-                                                DefaultAnnotations.EMPTY);
-        }
         return new DefaultDeviceDescription(deviceId.uri(), ROUTER,
-                                            JUNIPER, hw, sw, serial,
-                                            null, DefaultAnnotations.EMPTY);
+                JUNIPER, hw, sw, serial,
+                extractChassisId(chassisMacAddresses),
+                DefaultAnnotations.EMPTY);
+    }
+
+    /**
+     * Parses the chassisMacAddresses argument to find the private-base-address and maps it to a chassis id.
+     * @param chassisMacAddresses XML response
+     * @return the corresponding chassisId, or null if supplied chassisMacAddresses could not be parsed.
+     */
+    private static ChassisId extractChassisId(final String chassisMacAddresses) {
+
+        ChassisId result = null;
+
+        // Old JUNOS versions used CLI-style text for chassisMacAddresses, whereas recent versions provide a
+        // chassis-mac-addresses XML.
+        Matcher matcher = ADD_PATTERN_JUNOS15_1.matcher(chassisMacAddresses);
+        if (matcher.lookingAt()) {
+            result = new ChassisId(MacAddress.valueOf(matcher.group(1)).toLong());
+        } else {
+            String pba = loadXmlString(chassisMacAddresses)
+                    .configurationAt("chassis-mac-addresses")
+                    .configurationAt("mac-address-information")
+                    .getString("private-base-address");
+            if (StringUtils.isNotBlank(pba)) {
+                result = new ChassisId(MacAddress.valueOf(pba).toLong());
+            }
+        }
+        return result;
     }
 
     /**
@@ -598,6 +618,16 @@
             this.remotePortId = pPortId;
             this.remotePortDescription = pDescription;
         }
+        @Override
+        public String toString() {
+            return MoreObjects.toStringHelper(getClass()).omitNullValues()
+                    .add("localPortName", localPortName)
+                    .add("remoteChassisId", remoteChassisId)
+                    .add("remotePortIndex", remotePortIndex)
+                    .add("remotePortId", remotePortId)
+                    .add("remotePortDescription", remotePortDescription)
+                    .toString();
+        }
     }
 
     enum OperationType {
diff --git a/drivers/juniper/src/test/java/org/onosproject/drivers/juniper/JuniperUtilsTest.java b/drivers/juniper/src/test/java/org/onosproject/drivers/juniper/JuniperUtilsTest.java
new file mode 100644
index 0000000..5898612
--- /dev/null
+++ b/drivers/juniper/src/test/java/org/onosproject/drivers/juniper/JuniperUtilsTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * 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.drivers.juniper;
+
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.onosproject.net.Device.Type.ROUTER;
+
+import org.apache.commons.configuration.HierarchicalConfiguration;
+import org.junit.Test;
+import org.onlab.packet.ChassisId;
+import org.onosproject.drivers.utilities.XmlConfigParser;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DefaultDeviceDescription;
+import org.onosproject.net.device.DeviceDescription;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URI;
+
+import com.google.common.io.CharStreams;
+
+public class JuniperUtilsTest {
+
+
+    private static final String DEVICE_ID = "netconf:1.2.3.4:830";
+    private final DeviceId deviceId =  DeviceId.deviceId(DEVICE_ID);
+
+    @Test
+    public void testDeviceDescriptionParsedFromJunos15() throws IOException {
+
+        HierarchicalConfiguration getSystemInfoResp = XmlConfigParser.loadXml(
+                getClass().getResourceAsStream("/Junos_get-system-information_response_15.1.xml"));
+        String chassisText = CharStreams.toString(
+                new InputStreamReader(
+                        getClass().getResourceAsStream("/Junos_get-chassis-mac-addresses_response_15.1.xml")));
+
+        DeviceDescription actual = JuniperUtils.parseJuniperDescription(deviceId, getSystemInfoResp, chassisText);
+        DeviceDescription expected =
+                new DefaultDeviceDescription(URI.create(DEVICE_ID), ROUTER, "JUNIPER", "mx240",
+                        "junos 15.1R5.5", "JN11AC665AFC", new ChassisId("8418889983c0"));
+
+        assertEquals(expected, actual);
+
+    }
+
+
+    @Test
+    public void testDeviceDescriptionParsedFromJunos19() throws IOException {
+
+        HierarchicalConfiguration getSystemInfoResp = XmlConfigParser.loadXml(
+                getClass().getResourceAsStream("/Junos_get-system-information_response_19.2.xml"));
+        String chassisText = CharStreams.toString(
+                new InputStreamReader(
+                        getClass().getResourceAsStream("/Junos_get-chassis-mac-addresses_response_19.2.xml")));
+
+        DeviceDescription actual = JuniperUtils.parseJuniperDescription(deviceId, getSystemInfoResp, chassisText);
+        DeviceDescription expected =
+                new DefaultDeviceDescription(URI.create(DEVICE_ID), ROUTER, "JUNIPER", "acx6360-or",
+                        "junos 19.2I-20190228_dev_common.0.2316", "DX004", new ChassisId("f4b52f1f81c0"));
+
+        assertEquals(expected, actual);
+
+    }
+
+
+    @Test
+    public void testLinkAbstractionToString() throws IOException {
+        final JuniperUtils.LinkAbstraction x = new JuniperUtils.LinkAbstraction("foo", 1, 2, null, null);
+        assertThat("Null attributes excluded", x.toString(), allOf(
+                containsString("LinkAbstraction"),
+                containsString("localPortName=foo"),
+                not(containsString("remotePortDescription"))));
+
+
+    }
+
+
+
+}
\ No newline at end of file
diff --git a/drivers/juniper/src/test/resources/Junos_get-chassis-mac-addresses_response_15.1.xml b/drivers/juniper/src/test/resources/Junos_get-chassis-mac-addresses_response_15.1.xml
new file mode 100644
index 0000000..8c6c8ff
--- /dev/null
+++ b/drivers/juniper/src/test/resources/Junos_get-chassis-mac-addresses_response_15.1.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0"?>
+<rpc-reply xmlns:junos="http://xml.juniper.net/junos/15.1R5/junos" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="1">
+    <output>
+        MAC address information:
+        Public base address     84:18:88:99:7c:00
+        Public count            1984
+        Private base address    84:18:88:99:83:c0
+        Private count           64
+    </output>
+</rpc-reply>
\ No newline at end of file
diff --git a/drivers/juniper/src/test/resources/Junos_get-chassis-mac-addresses_response_19.2.xml b/drivers/juniper/src/test/resources/Junos_get-chassis-mac-addresses_response_19.2.xml
new file mode 100644
index 0000000..1cf0c4d
--- /dev/null
+++ b/drivers/juniper/src/test/resources/Junos_get-chassis-mac-addresses_response_19.2.xml
@@ -0,0 +1,10 @@
+<rpc-reply xmlns:junos="http://xml.juniper.net/junos/19.2I0/junos" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="1">
+    <chassis-mac-addresses>
+        <mac-address-information>
+            <public-base-address>f4:b5:2f:1f:81:00</public-base-address>
+            <public-count>192</public-count>
+            <private-base-address>f4:b5:2f:1f:81:c0</private-base-address>
+            <private-count>1088</private-count>
+        </mac-address-information>
+    </chassis-mac-addresses>
+</rpc-reply>
\ No newline at end of file
diff --git a/drivers/juniper/src/test/resources/Junos_get-system-information_response_15.1.xml b/drivers/juniper/src/test/resources/Junos_get-system-information_response_15.1.xml
new file mode 100644
index 0000000..26ba181
--- /dev/null
+++ b/drivers/juniper/src/test/resources/Junos_get-system-information_response_15.1.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<rpc-reply xmlns:junos="http://xml.juniper.net/junos/15.1R5/junos" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="1">
+    <system-information>
+        <hardware-model>mx240</hardware-model>
+        <os-name>junos</os-name>
+        <os-version>15.1R5.5</os-version>
+        <serial-number>JN11AC665AFC</serial-number>
+        <host-name>Mx35</host-name>
+    </system-information>
+</rpc-reply>
+
diff --git a/drivers/juniper/src/test/resources/Junos_get-system-information_response_19.2.xml b/drivers/juniper/src/test/resources/Junos_get-system-information_response_19.2.xml
new file mode 100644
index 0000000..99e8a5d
--- /dev/null
+++ b/drivers/juniper/src/test/resources/Junos_get-system-information_response_19.2.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0"?>
+<rpc-reply xmlns:junos="http://xml.juniper.net/junos/19.2I0/junos" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="1">
+    <system-information>
+        <hardware-model>acx6360-or</hardware-model>
+        <os-name>junos</os-name>
+        <os-version>19.2I-20190228_dev_common.0.2316</os-version>
+        <serial-number>DX004</serial-number>
+        <host-name>capella39</host-name>
+    </system-information>
+</rpc-reply>
\ No newline at end of file