ONOS-7806 - add support for path differentiator for netconf config

Change-Id: Ie4bdf4eb0348f9591b958bf89284026ed1c39074
diff --git a/core/api/src/main/java/org/onosproject/net/ConnectPoint.java b/core/api/src/main/java/org/onosproject/net/ConnectPoint.java
index c2959df..7a311aa 100644
--- a/core/api/src/main/java/org/onosproject/net/ConnectPoint.java
+++ b/core/api/src/main/java/org/onosproject/net/ConnectPoint.java
@@ -26,6 +26,14 @@
  */
 public class ConnectPoint implements Comparable<ConnectPoint> {
 
+    private static final String NO_SEP_SPECIFIED =
+        "Connect point not specified, Connect point must be in \"deviceUri/portNumber\" format";
+
+    private static final String SEP_NO_VALUE =
+        "Connect point separator specified, but no port number included, connect point must "
+            + " be in \"deviceUri/portNumber\" format";
+
+
     private final ElementId elementId;
     private final PortNumber portNumber;
 
@@ -116,13 +124,24 @@
      * @return a ConnectPoint based on the information in the string.
      */
     public static ConnectPoint deviceConnectPoint(String string) {
+        /*
+         * As device IDs may have a path component, we are expecting one
+         * of:
+         * - scheme:ip:port/cp
+         * - scheme:ip:port/path/cp
+         *
+         * The assumption is the last `/` will separate the device ID
+         * from the connection point number.
+         */
         checkNotNull(string);
-        String[] splitted = string.split("/");
-        checkArgument(splitted.length == 2,
-                      "Connect point must be in \"deviceUri/portNumber\" format");
+        int idx = string.lastIndexOf("/");
+        checkArgument(idx != -1, NO_SEP_SPECIFIED);
 
-        return new ConnectPoint(DeviceId.deviceId(splitted[0]),
-                                PortNumber.portNumber(splitted[1]));
+        String id = string.substring(0, idx);
+        String cp = string.substring(idx + 1);
+        checkArgument(!cp.isEmpty(), SEP_NO_VALUE);
+
+        return new ConnectPoint(DeviceId.deviceId(id), PortNumber.portNumber(cp));
     }
 
     /**
@@ -152,13 +171,24 @@
      * @return a ConnectPoint based on the information in the string.
      */
     public static ConnectPoint fromString(String string) {
+        /*
+         * As device IDs may have a path component, we are expecting one
+         * of:
+         * - scheme:ip:port/cp
+         * - scheme:ip:port/path/cp
+         *
+         * The assumption is the last `/` will separate the device ID
+         * from the connection point number.
+         */
         checkNotNull(string);
-        String[] splitted = string.split("/");
-        checkArgument(splitted.length == 2,
-                "Connect point must be in \"deviceUri/portNumber\" format");
+        int idx = string.lastIndexOf("/");
+        checkArgument(idx != -1, NO_SEP_SPECIFIED);
 
-        return new ConnectPoint(DeviceId.deviceId(splitted[0]),
-                PortNumber.fromString(splitted[1]));
+        String id = string.substring(0, idx);
+        String cp = string.substring(idx + 1);
+        checkArgument(!cp.isEmpty(), SEP_NO_VALUE);
+
+        return new ConnectPoint(DeviceId.deviceId(id), PortNumber.fromString(cp));
     }
 
     @Override
diff --git a/core/api/src/test/java/org/onosproject/net/ConnectPointTest.java b/core/api/src/test/java/org/onosproject/net/ConnectPointTest.java
index f823093..73dfe79 100644
--- a/core/api/src/test/java/org/onosproject/net/ConnectPointTest.java
+++ b/core/api/src/test/java/org/onosproject/net/ConnectPointTest.java
@@ -67,7 +67,6 @@
 
         expectDeviceParseException("");
         expectDeviceParseException("1/");
-        expectDeviceParseException("1/1/1");
         expectDeviceParseException("of:0011223344556677/word");
     }
 
@@ -90,7 +89,7 @@
     private static void expectDeviceParseException(String string) {
         try {
             ConnectPoint.deviceConnectPoint(string);
-            fail("Expected exception was not thrown");
+            fail(String.format("Expected exception was not thrown for '%s'", string));
         } catch (Exception e) {
             assertTrue(true);
         }
diff --git a/drivers/netconf/src/test/java/org/onosproject/drivers/netconf/MockNetconfController.java b/drivers/netconf/src/test/java/org/onosproject/drivers/netconf/MockNetconfController.java
index ce50558..0101b00 100644
--- a/drivers/netconf/src/test/java/org/onosproject/drivers/netconf/MockNetconfController.java
+++ b/drivers/netconf/src/test/java/org/onosproject/drivers/netconf/MockNetconfController.java
@@ -55,7 +55,7 @@
         String[] nameParts = deviceId.uri().toASCIIString().split(":");
         IpAddress ipAddress = Ip4Address.valueOf(nameParts[1]);
         int port = Integer.parseInt(nameParts[2]);
-        NetconfDeviceInfo ncdi = new NetconfDeviceInfo("mock", "mock", ipAddress, port);
+        NetconfDeviceInfo ncdi = new NetconfDeviceInfo("mock", "mock", ipAddress, port, null);
 
         mockNetconfDevice = new MockNetconfDevice(ncdi);
         devicesMap.put(deviceId, mockNetconfDevice);
@@ -96,4 +96,9 @@
         return null;
     }
 
+    @Override
+    public NetconfDevice getNetconfDevice(IpAddress ip, int port, String path) {
+        // TODO Auto-generated method stub
+        return null;
+    }
 }
diff --git a/protocols/netconf/api/src/main/java/org/onosproject/netconf/NetconfController.java b/protocols/netconf/api/src/main/java/org/onosproject/netconf/NetconfController.java
index 8f8fa00..041c379 100644
--- a/protocols/netconf/api/src/main/java/org/onosproject/netconf/NetconfController.java
+++ b/protocols/netconf/api/src/main/java/org/onosproject/netconf/NetconfController.java
@@ -97,4 +97,14 @@
      * @return NetconfDevice Netconf device
      */
     NetconfDevice getNetconfDevice(IpAddress ip, int port);
+
+    /**
+     * Gets a Netconf Device by node identifier.
+     *
+     * @param ip   device ip
+     * @param port device port
+     * @param path device path
+     * @return NetconfDevice Netconf device
+     */
+    NetconfDevice getNetconfDevice(IpAddress ip, int port, String path);
 }
diff --git a/protocols/netconf/api/src/main/java/org/onosproject/netconf/NetconfDeviceInfo.java b/protocols/netconf/api/src/main/java/org/onosproject/netconf/NetconfDeviceInfo.java
index 8d16873..930e7a5 100644
--- a/protocols/netconf/api/src/main/java/org/onosproject/netconf/NetconfDeviceInfo.java
+++ b/protocols/netconf/api/src/main/java/org/onosproject/netconf/NetconfDeviceInfo.java
@@ -16,6 +16,7 @@
 
 package org.onosproject.netconf;
 
+import org.apache.commons.lang3.tuple.Triple;
 import org.onlab.packet.IpAddress;
 import org.onosproject.net.DeviceId;
 import org.onosproject.netconf.config.NetconfDeviceConfig;
@@ -44,6 +45,7 @@
     private String password;
     private IpAddress ipAddress;
     private int port;
+    private Optional<String> path;
     private char[] key;
     private Optional<NetconfSshClientLib> sshClientLib;
     private OptionalInt connectTimeoutSec;
@@ -51,7 +53,6 @@
     private OptionalInt idleTimeoutSec;
     private DeviceId deviceId;
 
-
     /**
      * Information for contacting the controller.
      *
@@ -59,9 +60,10 @@
      * @param password  the password for the device
      * @param ipAddress the ip address
      * @param port      the tcp port
+     * @param path      the path part
      */
     public NetconfDeviceInfo(String name, String password, IpAddress ipAddress,
-                             int port) {
+                             int port, String path) {
         checkArgument(!name.equals(""), "Empty device username");
         checkArgument(port > 0, "Negative port");
         checkNotNull(ipAddress, "Null ip address");
@@ -69,6 +71,11 @@
         this.password = password;
         this.ipAddress = ipAddress;
         this.port = port;
+        if (path == null || path.isEmpty()) {
+            this.path = Optional.empty();
+        } else {
+            this.path = Optional.of(path);
+        }
         this.sshClientLib = Optional.empty();
         this.connectTimeoutSec = OptionalInt.empty();
         this.replyTimeoutSec = OptionalInt.empty();
@@ -82,11 +89,25 @@
      * @param password  the password for the device
      * @param ipAddress the ip address
      * @param port      the tcp port
+     */
+    public NetconfDeviceInfo(String name, String password, IpAddress ipAddress,
+                             int port) {
+        this(name, password, ipAddress, port, null);
+    }
+
+    /**
+     * Information for contacting the controller.
+     *
+     * @param name      the connection type
+     * @param password  the password for the device
+     * @param ipAddress the ip address
+     * @param port      the tcp port
+     * @param path      the path part
      * @param keyString the string containing a DSA or RSA private key
      *                  of the user in OpenSSH key format
      */
     public NetconfDeviceInfo(String name, String password, IpAddress ipAddress,
-                             int port, String keyString) {
+                             int port, String path, String keyString) {
         checkArgument(!name.equals(""), "Empty device name");
         checkArgument(port > 0, "Negative port");
         checkNotNull(ipAddress, "Null ip address");
@@ -94,6 +115,7 @@
         this.password = password;
         this.ipAddress = ipAddress;
         this.port = port;
+        this.path = Optional.ofNullable(path);
         this.key = keyString.toCharArray();
         this.sshClientLib = Optional.empty();
         this.connectTimeoutSec = OptionalInt.empty();
@@ -114,6 +136,7 @@
         this.password = netconfConfig.password();
         this.ipAddress = netconfConfig.ip();
         this.port = netconfConfig.port();
+        this.path = netconfConfig.path();
         if (netconfConfig.sshKey() != null && !netconfConfig.sshKey().isEmpty()) {
             this.key = netconfConfig.sshKey().toCharArray();
         }
@@ -164,6 +187,15 @@
     }
 
     /**
+     * Allows the path aspect of the device URI to be set.
+     *
+     * @param path path aspect value
+     */
+    public void setPath(Optional<String> path) {
+        this.path = path;
+    }
+
+    /**
      * Exposes the name of the controller.
      *
      * @return String name
@@ -199,6 +231,15 @@
         return port;
     }
 
+    /*
+     * Exposes the path of the aspect.
+     *
+     * @return path aspect
+     */
+    public Optional<String> path() {
+        return path;
+    }
+
     /**
      * Exposes the key of the controller.
      *
@@ -265,7 +306,8 @@
     public DeviceId getDeviceId() {
         if (deviceId == null) {
             try {
-                deviceId = DeviceId.deviceId(new URI("netconf", ipAddress.toString() + ":" + port, null));
+                deviceId = DeviceId.deviceId(new URI("netconf", ipAddress.toString() + ":" + port +
+                            (path.isPresent() ? "/" + path.get() : ""), null));
             } catch (URISyntaxException e) {
                 throw new IllegalArgumentException("Unable to build deviceID for device " + toString(), e);
             }
@@ -275,7 +317,11 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(ipAddress, port, name);
+        if (path.isPresent()) {
+            return Objects.hash(ipAddress, port, path.get(), name);
+        } else {
+            return Objects.hash(ipAddress, port, name);
+        }
     }
 
     @Override
@@ -285,10 +331,40 @@
             if (netconfDeviceInfo.name().equals(name)
                     && netconfDeviceInfo.ip().equals(ipAddress)
                     && netconfDeviceInfo.port() == port
+                    && netconfDeviceInfo.path().equals(path)
                     && netconfDeviceInfo.password().equals(password)) {
                 return true;
             }
         }
         return false;
     }
+
+    public static Triple<String, Integer, Optional<String>> extractIpPortPath(DeviceId deviceId) {
+        /*
+         * We can expect the following formats:
+         *
+         * netconf:ip:port/path
+         * netconf:ip:port
+         */
+        String string = deviceId.toString();
+
+        /*
+         * The first ':' is the separation between the scheme and the IP.
+         *
+         * The last ':' will represent the separator between the IP and the port.
+         */
+        int first = string.indexOf(':');
+        int last = string.lastIndexOf(':');
+        String ip = string.substring(first + 1, last);
+        String port = string.substring(last + 1);
+        String path = null;
+        int pathSep = port.indexOf('/');
+        if (pathSep != -1) {
+            path = port.substring(pathSep + 1);
+            port = port.substring(0, pathSep);
+        }
+
+        return Triple.of(ip, new Integer(port),
+                (path == null || path.isEmpty() ? Optional.empty() : Optional.of(path)));
+    }
 }
diff --git a/protocols/netconf/api/src/main/java/org/onosproject/netconf/config/NetconfDeviceConfig.java b/protocols/netconf/api/src/main/java/org/onosproject/netconf/config/NetconfDeviceConfig.java
index d9a3468..1135abd 100644
--- a/protocols/netconf/api/src/main/java/org/onosproject/netconf/config/NetconfDeviceConfig.java
+++ b/protocols/netconf/api/src/main/java/org/onosproject/netconf/config/NetconfDeviceConfig.java
@@ -17,22 +17,34 @@
 package org.onosproject.netconf.config;
 
 import com.google.common.annotations.Beta;
-import org.apache.commons.lang3.tuple.Pair;
 import org.onlab.packet.IpAddress;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.config.Config;
+import org.slf4j.Logger;
 
 import java.util.Optional;
 import java.util.OptionalInt;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.netconf.NetconfDeviceInfo.extractIpPortPath;
+import static org.slf4j.LoggerFactory.getLogger;
 
 /**
  * Configuration for Netconf provider.
+ *
+ * The URI for a netconf device is of the format
+ *
+ * {@code netconf:<ip>[:<port>][/<path>]}
+ *
+ * The {@code ip} and {@code port} are used to create a netconf connection
+ * to the device. The {@code path} is an optional component that is not used
+ * by the default netconf driver, but is leveragable by custom drivers.
  */
 @Beta
 public class NetconfDeviceConfig extends Config<DeviceId> {
 
+    private final Logger log = getLogger(getClass());
+
     /**
      * netcfg ConfigKey.
      */
@@ -40,6 +52,7 @@
 
     public static final String IP = "ip";
     public static final String PORT = "port";
+    public static final String PATH = "path";
     public static final String USERNAME = "username";
     public static final String PASSWORD = "password";
     public static final String SSHKEY = "sshkey";
@@ -50,7 +63,7 @@
 
     @Override
     public boolean isValid() {
-        return hasOnlyFields(IP, PORT, USERNAME, PASSWORD, SSHKEY, SSHCLIENT,
+        return hasOnlyFields(IP, PORT, PATH, USERNAME, PASSWORD, SSHKEY, SSHCLIENT,
                 CONNECT_TIMEOUT, REPLY_TIMEOUT, IDLE_TIMEOUT) && ip() != null;
     }
 
@@ -60,7 +73,7 @@
      * @return ip
      */
     public IpAddress ip() {
-        return IpAddress.valueOf(get(IP, checkNotNull(extractIpPort()).getKey()));
+        return IpAddress.valueOf(get(IP, checkNotNull(extractIpPortPath(subject)).getLeft()));
     }
 
     /**
@@ -69,7 +82,20 @@
      * @return port
      */
     public int port() {
-        return get(PORT, checkNotNull(extractIpPort()).getValue());
+        return get(PORT, checkNotNull(extractIpPortPath(subject)).getMiddle());
+    }
+
+    /**
+     * Gets the path of the NETCONF device.
+     *
+     * @return path
+     */
+    public Optional<String> path() {
+        String val = get(PATH, "");
+        if (val.isEmpty()) {
+            return extractIpPortPath(subject).getRight();
+        }
+        return Optional.ofNullable(val);
     }
 
     /**
@@ -161,6 +187,16 @@
     }
 
     /**
+     * Sets the path for the device.
+     *
+     * @param path the path
+     * @return instance for chaining
+     */
+    public NetconfDeviceConfig setPath(String path) {
+        return (NetconfDeviceConfig) setOrClear(PATH, path);
+    }
+
+    /**
      * Sets the username for the Device.
      *
      * @param username username
@@ -241,22 +277,4 @@
     public NetconfDeviceConfig setIdleTimeout(Integer idleTimeout) {
         return (NetconfDeviceConfig) setOrClear(IDLE_TIMEOUT, idleTimeout);
     }
-
-
-    private Pair<String, Integer> extractIpPort() {
-        // Assuming one of
-        //  - netconf:ip:port
-        //  - netconf:ip
-
-        // foo:schemespecifcpart
-        String info = subject.uri().getSchemeSpecificPart();
-        int portSeparator = info.lastIndexOf(':');
-        if (portSeparator == -1) {
-            // assume default port
-            return Pair.of(info, 830);
-        }
-        String ip = info.substring(0, portSeparator);
-        int port = Integer.parseInt(info.substring(portSeparator + 1));
-        return Pair.of(ip, port);
-    }
 }
diff --git a/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/impl/NetconfControllerImpl.java b/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/impl/NetconfControllerImpl.java
index 0395dee..10eabd9 100644
--- a/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/impl/NetconfControllerImpl.java
+++ b/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/impl/NetconfControllerImpl.java
@@ -16,6 +16,8 @@
 
 package org.onosproject.netconf.ctl.impl;
 
+import org.apache.commons.lang3.tuple.Triple;
+
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.onlab.packet.IpAddress;
 import org.onosproject.cfg.ComponentConfigService;
@@ -49,9 +51,9 @@
 import org.slf4j.LoggerFactory;
 
 import java.security.Security;
-import java.util.Arrays;
 import java.util.Dictionary;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArraySet;
@@ -62,6 +64,7 @@
 import static org.onlab.util.Tools.getIntegerProperty;
 import static org.onlab.util.Tools.groupedThreads;
 import static org.onosproject.netconf.ctl.impl.OsgiPropertyConstants.*;
+import static org.onosproject.netconf.NetconfDeviceInfo.extractIpPortPath;
 
 /**
  * The implementation of NetconfController.
@@ -204,13 +207,15 @@
     }
 
     @Override
+    public NetconfDevice getNetconfDevice(IpAddress ip, int port, String path) {
+        return getNetconfDevice(DeviceId.deviceId(
+                    String.format("netconf:%s:%d%s",
+                        ip.toString(), port, (path != null && !path.isEmpty() ? "/" + path : ""))));
+    }
+
+    @Override
     public NetconfDevice getNetconfDevice(IpAddress ip, int port) {
-        for (DeviceId info : netconfDeviceMap.keySet()) {
-            if (info.uri().getSchemeSpecificPart().equals(ip.toString() + ":" + port)) {
-                return netconfDeviceMap.get(info);
-            }
-        }
-        return null;
+        return getNetconfDevice(ip, port, null);
     }
 
     @Override
@@ -225,28 +230,19 @@
         } else if (netCfg != null) {
             log.debug("Device {} is present in NetworkConfig", deviceId);
             deviceInfo = new NetconfDeviceInfo(netCfg);
-
         } else {
             log.debug("Creating NETCONF device {}", deviceId);
             Device device = deviceService.getDevice(deviceId);
-            String ip;
+            String ip, path = null;
             int port;
             if (device != null) {
                 ip = device.annotations().value("ipaddress");
                 port = Integer.parseInt(device.annotations().value("port"));
             } else {
-                String[] info = deviceId.toString().split(":");
-                if (info.length == 3) {
-                    ip = info[1];
-                    port = Integer.parseInt(info[2]);
-                } else {
-                    ip = Arrays.asList(info).stream().filter(el -> !el.equals(info[0])
-                            && !el.equals(info[info.length - 1]))
-                            .reduce((t, u) -> t + ":" + u)
-                            .get();
-                    log.debug("ip v6 {}", ip);
-                    port = Integer.parseInt(info[info.length - 1]);
-                }
+                Triple<String, Integer, Optional<String>> info = extractIpPortPath(deviceId);
+                ip = info.getLeft();
+                port = info.getMiddle();
+                path = (info.getRight().isPresent() ? info.getRight().get() : null);
             }
             try {
                 DeviceKey deviceKey = deviceKeyService.getDeviceKey(
@@ -257,7 +253,8 @@
                     deviceInfo = new NetconfDeviceInfo(usernamepasswd.username(),
                                                        usernamepasswd.password(),
                                                        IpAddress.valueOf(ip),
-                                                       port);
+                                                       port,
+                                                       path);
 
                 } else if (deviceKey.type() == DeviceKey.Type.SSL_KEY) {
                     String username = deviceKey.annotations().value(AnnotationKeys.USERNAME);
@@ -268,6 +265,7 @@
                                                        password,
                                                        IpAddress.valueOf(ip),
                                                        port,
+                                                       path,
                                                        sshkey);
                 } else {
                     log.error("Unknown device key for device {}", deviceId);
diff --git a/protocols/netconf/ctl/src/test/java/org/onosproject/netconf/ctl/impl/NetconfControllerImplTest.java b/protocols/netconf/ctl/src/test/java/org/onosproject/netconf/ctl/impl/NetconfControllerImplTest.java
index 44011f1..e503584 100644
--- a/protocols/netconf/ctl/src/test/java/org/onosproject/netconf/ctl/impl/NetconfControllerImplTest.java
+++ b/protocols/netconf/ctl/src/test/java/org/onosproject/netconf/ctl/impl/NetconfControllerImplTest.java
@@ -322,7 +322,9 @@
         reflectedDeviceMap.clear();
         NetconfDevice device1 = ctrl.connectDevice(deviceInfo1.getDeviceId());
         NetconfDevice device2 = ctrl.connectDevice(deviceInfo2.getDeviceId());
-        assertTrue("Incorrect device connection", ctrl.getDevicesMap().containsKey(deviceId1));
+        assertTrue(String.format("Incorrect device connection from '%s' we get '%s' contains '%s'",
+                    deviceInfo1, ctrl.getDevicesMap(), deviceId1),
+                ctrl.getDevicesMap().containsKey(deviceId1));
         assertTrue("Incorrect device connection", ctrl.getDevicesMap().containsKey(deviceId2));
         assertEquals("Incorrect device connection", 2, ctrl.getDevicesMap().size());
     }
diff --git a/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceProvider.java b/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceProvider.java
index e60e285..fe5cda9 100644
--- a/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceProvider.java
+++ b/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceProvider.java
@@ -19,6 +19,7 @@
 import com.google.common.base.Objects;
 import com.google.common.base.Preconditions;
 import com.google.common.util.concurrent.Striped;
+import org.apache.commons.lang3.tuple.Triple;
 import org.onlab.packet.ChassisId;
 import org.onlab.util.Tools;
 import org.onosproject.cfg.ComponentConfigService;
@@ -75,7 +76,6 @@
 import java.net.Socket;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Dictionary;
 import java.util.Map;
@@ -95,6 +95,7 @@
 import static java.util.concurrent.Executors.newScheduledThreadPool;
 import static org.onlab.util.Tools.groupedThreads;
 import static org.onosproject.provider.netconf.device.impl.OsgiPropertyConstants.*;
+import static org.onosproject.netconf.NetconfDeviceInfo.extractIpPortPath;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -143,6 +144,7 @@
     private static final String IPADDRESS = "ipaddress";
     private static final String NETCONF = "netconf";
     private static final String PORT = "port";
+    private static final String PATH = "path";
     private static final int CORE_POOL_SIZE = 10;
 
     /** Configure poll frequency for port status and statistics; default is 30 sec. */
@@ -313,18 +315,9 @@
             ip = device.annotations().value(IPADDRESS);
             port = Integer.parseInt(device.annotations().value(PORT));
         } else {
-            String[] info = deviceId.toString().split(":");
-            if (info.length == 3) {
-                ip = info[1];
-                port = Integer.parseInt(info[2]);
-            } else {
-                ip = Arrays.asList(info).stream().filter(el -> !el.equals(info[0])
-                        && !el.equals(info[info.length - 1]))
-                        .reduce((t, u) -> t + ":" + u)
-                        .get();
-                log.debug("ip v6 {}", ip);
-                port = Integer.parseInt(info[info.length - 1]);
-            }
+            Triple<String, Integer, Optional<String>> info = extractIpPortPath(deviceId);
+            ip = info.getLeft();
+            port = info.getMiddle();
         }
         // FIXME just opening TCP session probably is not the appropriate
         // method to test reachability.
@@ -438,8 +431,10 @@
             return;
         }
         DeviceDescription deviceDescription = createDeviceRepresentation(deviceId, config);
-        log.debug("Connecting NETCONF device {}, on {}:{} with username {}",
-                  deviceId, config.ip(), config.port(), config.username());
+        log.debug("Connecting NETCONF device {}, on {}:{}{} with username {}",
+                  deviceId, config.ip(), config.port(),
+                 (config.path().isPresent() ? "/" + config.path().get() : ""),
+                 config.username());
         storeDeviceKey(config.sshKey(), config.username(), config.password(), deviceId);
         retriedPortDiscoveryMap.put(deviceId, new AtomicInteger(0));
         if (deviceService.getDevice(deviceId) == null) {
@@ -543,19 +538,22 @@
         //Netconf configuration object
         ChassisId cid = new ChassisId();
         String ipAddress = config.ip().toString();
-        SparseAnnotations annotations = DefaultAnnotations.builder()
+        DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
                 .set(IPADDRESS, ipAddress)
                 .set(PORT, String.valueOf(config.port()))
                 .set(AnnotationKeys.PROTOCOL, SCHEME_NAME.toUpperCase())
-                .set(AnnotationKeys.PROVIDER_MARK_ONLINE, String.valueOf(true))
-                .build();
+                .set(AnnotationKeys.PROVIDER_MARK_ONLINE, String.valueOf(true));
+        if (config.path().isPresent()) {
+            annotations.set(PATH, config.path().get());
+        }
+
         return new DefaultDeviceDescription(
                 deviceId.uri(),
                 Device.Type.SWITCH,
                 UNKNOWN, UNKNOWN,
                 UNKNOWN, UNKNOWN,
                 cid, false,
-                annotations);
+                annotations.build());
     }
 
     private void storeDeviceKey(String sshKey, String username, String password, DeviceId deviceId) {
@@ -616,14 +614,16 @@
      *
      * @param ip   IP address
      * @param port port number
+     * @param path path aspect
      * @return DeviceId
      */
-    public DeviceId getDeviceId(String ip, int port) {
+    public DeviceId getDeviceId(String ip, int port, Optional<String> path) {
         try {
-            return DeviceId.deviceId(new URI(NETCONF, ip + ":" + port, null));
+            return DeviceId.deviceId(new URI(NETCONF, ip + ":" + port +
+                        (path.isPresent() ? "/" + path : ""), null));
         } catch (URISyntaxException e) {
             throw new IllegalArgumentException("Unable to build deviceID for device "
-                                                       + ip + ":" + port, e);
+                    + ip + ":" + port + (path.isPresent() ? "/" + path : ""), e);
         }
     }
 
@@ -650,7 +650,6 @@
      */
     private class InternalNetworkConfigListener implements NetworkConfigListener {
 
-
         @Override
         public void event(NetworkConfigEvent event) {
             if (event.configClass().equals(NetconfDeviceConfig.class)) {
diff --git a/providers/netconf/device/src/test/java/org/onosproject/provider/netconf/device/impl/NetconfControllerAdapter.java b/providers/netconf/device/src/test/java/org/onosproject/provider/netconf/device/impl/NetconfControllerAdapter.java
index 8714dd8..51410f5 100644
--- a/providers/netconf/device/src/test/java/org/onosproject/provider/netconf/device/impl/NetconfControllerAdapter.java
+++ b/providers/netconf/device/src/test/java/org/onosproject/provider/netconf/device/impl/NetconfControllerAdapter.java
@@ -75,4 +75,9 @@
     public NetconfDevice getNetconfDevice(IpAddress ip, int port) {
         return null;
     }
+
+    @Override
+    public NetconfDevice getNetconfDevice(IpAddress ip, int port, String path) {
+        return null;
+    }
 }