[ONOS-6076] Moving NETCONF devices configuration under devices key

Change-Id: I5a0dc2c2d33c7cd79655497f66373c6f4f9af656
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 eb63974..be76056 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
@@ -55,7 +55,7 @@
      */
     public NetconfDeviceInfo(String name, String password, IpAddress ipAddress,
                              int port) {
-        Preconditions.checkArgument(!name.equals(""), "Empty device name");
+        Preconditions.checkArgument(!name.equals(""), "Empty device username");
         Preconditions.checkNotNull(port > 0, "Negative port");
         Preconditions.checkNotNull(ipAddress, "Null ip address");
         this.name = name;
diff --git a/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceConfig.java b/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceConfig.java
new file mode 100644
index 0000000..ea17015
--- /dev/null
+++ b/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceConfig.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2015-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.provider.netconf.device.impl;
+
+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 static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Configuration for Netconf provider.
+ */
+@Beta
+public class NetconfDeviceConfig extends Config<DeviceId> {
+
+    private static final String IP = "ip";
+    private static final String PORT = "port";
+    private static final String USERNAME = "username";
+    private static final String PASSWORD = "password";
+    private static final String SSHKEY = "sshkey";
+
+    @Override
+    public boolean isValid() {
+        return hasOnlyFields(IP, PORT, USERNAME, PASSWORD, SSHKEY) &&
+                ip() != null;
+    }
+
+    /**
+     * Gets the Ip of the NETCONF device.
+     *
+     * @return ip
+     */
+    public IpAddress ip() {
+        return IpAddress.valueOf(get(IP, checkNotNull(extractIpPort()).getKey()));
+    }
+
+    /**
+     * Gets the port of the NETCONF device.
+     *
+     * @return port
+     */
+    public int port() {
+        return get(PORT, checkNotNull(extractIpPort()).getValue());
+    }
+
+    /**
+     * Gets the username of the NETCONF device.
+     *
+     * @return username
+     */
+    public String username() {
+        return get(USERNAME, "");
+    }
+
+    /**
+     * Gets the password of the NETCONF device.
+     *
+     * @return password
+     */
+    public String password() {
+        return get(PASSWORD, "");
+    }
+
+    /**
+     * Gets the sshKey of the NETCONF device.
+     *
+     * @return sshkey
+     */
+    public String sshKey() {
+        return get(SSHKEY, "");
+    }
+
+    /**
+     * Sets the Ip for the Device.
+     *
+     * @param ip the ip
+     * @return instance for chaining
+     */
+    public NetconfDeviceConfig setIp(String ip) {
+        return (NetconfDeviceConfig) setOrClear(IP, ip);
+    }
+
+    /**
+     * Sets the Port for the Device.
+     *
+     * @param port the port
+     * @return instance for chaining
+     */
+    public NetconfDeviceConfig setPort(int port) {
+        return (NetconfDeviceConfig) setOrClear(PORT, port);
+    }
+
+    /**
+     * Sets the username for the Device.
+     *
+     * @param username username
+     * @return instance for chaining
+     */
+    public NetconfDeviceConfig setUsername(String username) {
+        return (NetconfDeviceConfig) setOrClear(USERNAME, username);
+    }
+
+    /**
+     * Sets the password for the Device.
+     *
+     * @param password password
+     * @return instance for chaining
+     */
+    public NetconfDeviceConfig setPassword(String password) {
+        return (NetconfDeviceConfig) setOrClear(PASSWORD, password);
+    }
+
+    /**
+     * Sets the SshKey for the Device.
+     *
+     * @param sshKey sshKey as string
+     * @return instance for chaining
+     */
+    public NetconfDeviceConfig setSshKey(String sshKey) {
+        return (NetconfDeviceConfig) setOrClear(SSHKEY, sshKey);
+    }
+
+    private Pair<String, Integer> extractIpPort() {
+        String info = subject.toString();
+        if (info.startsWith(NetconfDeviceProvider.SCHEME_NAME)) {
+            //+1 is due to length of colon separator
+            String ip = info.substring(info.indexOf(":") + 1, info.lastIndexOf(":"));
+            int port = Integer.parseInt(info.substring(info.lastIndexOf(":") + 1));
+            return Pair.of(ip, port);
+        }
+        return null;
+    }
+}
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 215f104..c66643f 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
@@ -16,8 +16,11 @@
 
 package org.onosproject.provider.netconf.device.impl;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.base.Objects;
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -44,6 +47,7 @@
 import org.onosproject.net.config.NetworkConfigEvent;
 import org.onosproject.net.config.NetworkConfigListener;
 import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.config.basics.SubjectFactories;
 import org.onosproject.net.device.DefaultDeviceDescription;
 import org.onosproject.net.device.DeviceDescription;
 import org.onosproject.net.device.DeviceDescriptionDiscovery;
@@ -73,6 +77,8 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Dictionary;
+import java.util.List;
+import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
@@ -119,7 +125,7 @@
 
 
     protected static final String APP_NAME = "org.onosproject.netconf";
-    private static final String SCHEME_NAME = "netconf";
+    protected static final String SCHEME_NAME = "netconf";
     private static final String DEVICE_PROVIDER_PACKAGE = "org.onosproject.netconf.provider.device";
     private static final String UNKNOWN = "unknown";
     protected static final String ISNULL = "NetconfDeviceInfo is null";
@@ -147,7 +153,15 @@
     private InternalDeviceListener deviceListener = new InternalDeviceListener();
     protected ScheduledFuture<?> scheduledTask;
 
-    private final ConfigFactory factory =
+    protected final List<ConfigFactory> factories = ImmutableList.of(
+            new ConfigFactory<DeviceId, NetconfDeviceConfig>(
+                    SubjectFactories.DEVICE_SUBJECT_FACTORY,
+                    NetconfDeviceConfig.class, SCHEME_NAME) {
+                @Override
+                public NetconfDeviceConfig createConfig() {
+                    return new NetconfDeviceConfig();
+                }
+            },
             new ConfigFactory<ApplicationId, NetconfProviderConfig>(APP_SUBJECT_FACTORY,
                                                                     NetconfProviderConfig.class,
                                                                     "devices",
@@ -156,7 +170,8 @@
                 public NetconfProviderConfig createConfig() {
                     return new NetconfProviderConfig();
                 }
-            };
+            });
+
     protected final NetworkConfigListener cfgListener = new InternalNetworkConfigListener();
     private ApplicationId appId;
     private boolean active;
@@ -168,10 +183,11 @@
         componentConfigService.registerProperties(getClass());
         providerService = providerRegistry.register(this);
         appId = coreService.registerApplication(APP_NAME);
-        cfgService.registerConfigFactory(factory);
+        factories.forEach(cfgService::registerConfigFactory);
         cfgService.addListener(cfgListener);
         controller.addDeviceListener(innerNodeListener);
         deviceService.addListener(deviceListener);
+        translateConfig();
         executor.execute(NetconfDeviceProvider.this::connectDevices);
         modified(context);
         log.info("Started");
@@ -191,7 +207,7 @@
         deviceService.removeListener(deviceListener);
         providerRegistry.unregister(this);
         providerService = null;
-        cfgService.unregisterConfigFactory(factory);
+        factories.forEach(cfgService::unregisterConfigFactory);
         scheduledTask.cancel(true);
         executor.shutdown();
         log.info("Stopped");
@@ -343,49 +359,31 @@
     }
 
     private void connectDevices() {
-        NetconfProviderConfig cfg = cfgService.getConfig(appId, NetconfProviderConfig.class);
-        if (cfg != null) {
-            try {
-                cfg.getDevicesAddresses().forEach(addr -> {
-                    DeviceId deviceId = getDeviceId(addr.ip().toString(), addr.port());
-                    Preconditions.checkNotNull(deviceId, ISNULL);
-                    //Netconf configuration object
-                    ChassisId cid = new ChassisId();
-                    String ipAddress = addr.ip().toString();
-                    SparseAnnotations annotations = DefaultAnnotations.builder()
-                            .set(IPADDRESS, ipAddress)
-                            .set(PORT, String.valueOf(addr.port()))
-                            .set(AnnotationKeys.PROTOCOL, SCHEME_NAME.toUpperCase())
-                            .build();
-                    DeviceDescription deviceDescription = new DefaultDeviceDescription(
-                            deviceId.uri(),
-                            Device.Type.SWITCH,
-                            UNKNOWN, UNKNOWN,
-                            UNKNOWN, UNKNOWN,
-                            cid, false,
-                            annotations);
-                    storeDeviceKey(addr, deviceId);
-
-                    if (deviceService.getDevice(deviceId) == null) {
-                        providerService.deviceConnected(deviceId, deviceDescription);
-                    }
-                    try {
-                        checkAndUpdateDevice(deviceId, deviceDescription);
-                    } catch (Exception e) {
-                        log.error("Unhandled exception checking {}", deviceId, e);
-                    }
-                });
-            } catch (ConfigException e) {
-                log.error("Cannot read config error " + e);
+        Set<DeviceId> deviceSubjects =
+                cfgService.getSubjects(DeviceId.class, NetconfDeviceConfig.class);
+        deviceSubjects.forEach(deviceId -> {
+            NetconfDeviceConfig config =
+                    cfgService.getConfig(deviceId, NetconfDeviceConfig.class);
+            DeviceDescription deviceDescription = createDeviceRepresentation(deviceId, config);
+            log.debug("Connecting NETCONF device {}, on {}:{} with username {}",
+                      deviceId, config.ip(), config.port(), config.username());
+            storeDeviceKey(config.sshKey(), config.username(), config.password(), deviceId);
+            if (deviceService.getDevice(deviceId) == null) {
+                providerService.deviceConnected(deviceId, deviceDescription);
             }
-        }
+            try {
+                checkAndUpdateDevice(deviceId, deviceDescription);
+            } catch (Exception e) {
+                log.error("Unhandled exception checking {}", deviceId, e);
+            }
+        });
     }
 
     private void checkAndUpdateDevice(DeviceId deviceId, DeviceDescription deviceDescription) {
         Device device = deviceService.getDevice(deviceId);
         if (device == null) {
             log.warn("Device {} has not been added to store, " +
-                             "maybe due to a problem in connectivity", deviceId);
+                             "since it's not reachable", deviceId);
         } else {
             boolean isReachable = isReachable(deviceId);
             if (isReachable && !deviceService.isAvailable(deviceId)) {
@@ -393,16 +391,19 @@
                     if (mastershipService.isLocalMaster(deviceId)) {
                         DeviceDescriptionDiscovery deviceDescriptionDiscovery =
                                 device.as(DeviceDescriptionDiscovery.class);
-                        DeviceDescription updatedDeviceDescription = deviceDescriptionDiscovery.discoverDeviceDetails();
+                        DeviceDescription updatedDeviceDescription =
+                                deviceDescriptionDiscovery.discoverDeviceDetails();
                         if (updatedDeviceDescription != null &&
                                 !descriptionEquals(device, updatedDeviceDescription)) {
                             providerService.deviceConnected(
                                     deviceId, new DefaultDeviceDescription(
-                                            updatedDeviceDescription, true, updatedDeviceDescription.annotations()));
+                                            updatedDeviceDescription, true,
+                                            updatedDeviceDescription.annotations()));
                         } else if (updatedDeviceDescription == null) {
                             providerService.deviceConnected(
                                     deviceId, new DefaultDeviceDescription(
-                                            deviceDescription, true, deviceDescription.annotations()));
+                                            deviceDescription, true,
+                                            deviceDescription.annotations()));
                         }
                         //if ports are not discovered, retry the discovery
                         if (deviceService.getPorts(deviceId).isEmpty()) {
@@ -451,48 +452,48 @@
     }
 
     private void checkAndUpdateDevices() {
-        NetconfProviderConfig cfg = cfgService.getConfig(appId, NetconfProviderConfig.class);
-        if (cfg != null) {
-            log.info("Checking connection to devices in configuration");
-            try {
-                cfg.getDevicesAddresses().forEach(addr -> {
-                    DeviceId deviceId = getDeviceId(addr.ip().toString(), addr.port());
-                    Preconditions.checkNotNull(deviceId, ISNULL);
-                    //Netconf configuration object
-                    ChassisId cid = new ChassisId();
-                    String ipAddress = addr.ip().toString();
-                    SparseAnnotations annotations = DefaultAnnotations.builder()
-                            .set(IPADDRESS, ipAddress)
-                            .set(PORT, String.valueOf(addr.port()))
-                            .set(AnnotationKeys.PROTOCOL, SCHEME_NAME.toUpperCase())
-                            .build();
-                    DeviceDescription deviceDescription = new DefaultDeviceDescription(
-                            deviceId.uri(),
-                            Device.Type.SWITCH,
-                            UNKNOWN, UNKNOWN,
-                            UNKNOWN, UNKNOWN,
-                            cid, false,
-                            annotations);
-                    storeDeviceKey(addr, deviceId);
-                    checkAndUpdateDevice(deviceId, deviceDescription);
-                });
-            } catch (ConfigException e) {
-                log.error("Cannot read config error " + e);
-            }
-        }
+        Set<DeviceId> deviceSubjects =
+                cfgService.getSubjects(DeviceId.class, NetconfDeviceConfig.class);
+        deviceSubjects.forEach(deviceId -> {
+            NetconfDeviceConfig config =
+                    cfgService.getConfig(deviceId, NetconfDeviceConfig.class);
+            DeviceDescription deviceDescription = createDeviceRepresentation(deviceId, config);
+            storeDeviceKey(config.sshKey(), config.username(), config.password(), deviceId);
+            checkAndUpdateDevice(deviceId, deviceDescription);
+        });
     }
 
-    private void storeDeviceKey(NetconfProviderConfig.NetconfDeviceAddress addr, DeviceId deviceId) {
-        if (addr.sshkey().equals("")) {
+    private DeviceDescription createDeviceRepresentation(DeviceId deviceId, NetconfDeviceConfig config) {
+        Preconditions.checkNotNull(deviceId, ISNULL);
+        //Netconf configuration object
+        ChassisId cid = new ChassisId();
+        String ipAddress = config.ip().toString();
+        SparseAnnotations annotations = DefaultAnnotations.builder()
+                .set(IPADDRESS, ipAddress)
+                .set(PORT, String.valueOf(config.port()))
+                .set(AnnotationKeys.PROTOCOL, SCHEME_NAME.toUpperCase())
+                .build();
+        return new DefaultDeviceDescription(
+                deviceId.uri(),
+                Device.Type.SWITCH,
+                UNKNOWN, UNKNOWN,
+                UNKNOWN, UNKNOWN,
+                cid, false,
+                annotations);
+    }
+
+    private void storeDeviceKey(String sshKey, String username, String password, DeviceId deviceId) {
+        if (sshKey.equals("")) {
             deviceKeyAdminService.addKey(
                     DeviceKey.createDeviceKeyUsingUsernamePassword(
                             DeviceKeyId.deviceKeyId(deviceId.toString()),
-                            null, addr.name(), addr.password()));
+                            null, username, password));
         } else {
             deviceKeyAdminService.addKey(
                     DeviceKey.createDeviceKeyUsingSshKey(
                             DeviceKeyId.deviceKeyId(deviceId.toString()),
-                            null, addr.name(), addr.password(), addr.sshkey()));
+                            null, username, password,
+                            sshKey));
         }
     }
 
@@ -508,7 +509,7 @@
             }
             deviceKeyAdminService.removeKey(DeviceKeyId.deviceKeyId(deviceId.toString()));
             throw new RuntimeException(new NetconfException(
-                    "Can't connect to NETCONF " + "device on " + deviceId + ":" + deviceId, e));
+                    "Can't connect to NETCONF device " + deviceId, e));
 
         }
     }
@@ -549,6 +550,38 @@
         }
     }
 
+
+    protected void translateConfig() {
+        NetconfProviderConfig cfg = cfgService.getConfig(appId, NetconfProviderConfig.class);
+        if (cfg != null) {
+            try {
+                cfg.getDevicesAddresses().forEach(addr -> {
+                    DeviceId deviceId = getDeviceId(addr.ip().toString(), addr.port());
+                    log.info("Translating config for device {}", deviceId);
+                    if (cfgService.getConfig(deviceId, NetconfDeviceConfig.class) == null) {
+                        ObjectMapper mapper = new ObjectMapper();
+                        ObjectNode device = mapper.createObjectNode();
+                        device.put("ip", addr.ip().toString());
+                        device.put("port", addr.port());
+                        device.put("username", addr.name());
+                        device.put("password", addr.password());
+                        device.put("sshkey", addr.sshkey());
+                        cfgService.applyConfig(deviceId, NetconfDeviceConfig.class, device);
+                    } else {
+                        // This is a corner case where new updated config is
+                        // pushed with old /app tree after an initial with the
+                        // new device/ tree. Since old method will be deprecated
+                        // it's ok to ignore
+                        log.warn("Config for device {} already exists, ignoring", deviceId);
+                    }
+
+                });
+            } catch (ConfigException e) {
+                log.error("Cannot read config error " + e);
+            }
+        }
+    }
+
     /**
      * Listener for configuration events.
      */
@@ -557,12 +590,20 @@
 
         @Override
         public void event(NetworkConfigEvent event) {
-            executor.execute(NetconfDeviceProvider.this::connectDevices);
+            if (event.configClass().equals(NetconfDeviceConfig.class)) {
+                executor.execute(NetconfDeviceProvider.this::connectDevices);
+            } else {
+                log.warn("Injecting device via this Json is deprecated, " +
+                                 "please put configuration under devices/ as shown in the wiki");
+                translateConfig();
+            }
+
         }
 
         @Override
         public boolean isRelevant(NetworkConfigEvent event) {
-            return event.configClass().equals(NetconfProviderConfig.class) &&
+            return (event.configClass().equals(NetconfDeviceConfig.class) ||
+                    event.configClass().equals(NetconfProviderConfig.class)) &&
                     (event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
                             event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED);
         }
diff --git a/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/NetconfProviderConfig.java b/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/NetconfProviderConfig.java
index 2cdeb53..59cffb6 100644
--- a/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/NetconfProviderConfig.java
+++ b/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/NetconfProviderConfig.java
@@ -28,8 +28,10 @@
 
 /**
  * Configuration for Netconf provider.
+ * @deprecated 1.10.0 Kingfisher
  */
 @Beta
+@Deprecated
 public class NetconfProviderConfig extends Config<ApplicationId> {
 
     public static final String CONFIG_VALUE_ERROR = "Error parsing config value";
@@ -42,7 +44,6 @@
 
     public Set<NetconfDeviceAddress> getDevicesAddresses() throws ConfigException {
         Set<NetconfDeviceAddress> devicesAddresses = Sets.newHashSet();
-
         try {
             for (JsonNode node : array) {
                 String ip = node.path(IP).asText();
@@ -61,7 +62,8 @@
         return devicesAddresses;
     }
 
-    public class NetconfDeviceAddress {
+    public class
+    NetconfDeviceAddress {
         private final IpAddress ip;
         private final int port;
         private final String name;
@@ -105,4 +107,4 @@
         }
     }
 
-}
+}
\ No newline at end of file
diff --git a/providers/netconf/device/src/test/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceProviderTest.java b/providers/netconf/device/src/test/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceProviderTest.java
index a0b84ca..6ebaef9 100644
--- a/providers/netconf/device/src/test/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceProviderTest.java
+++ b/providers/netconf/device/src/test/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceProviderTest.java
@@ -16,9 +16,11 @@
 
 package org.onosproject.provider.netconf.device.impl;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.collect.ImmutableSet;
+import org.apache.commons.lang.StringUtils;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.onlab.packet.ChassisId;
 import org.onlab.packet.IpAddress;
@@ -35,6 +37,7 @@
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.config.Config;
+import org.onosproject.net.config.ConfigApplyDelegate;
 import org.onosproject.net.config.ConfigFactory;
 import org.onosproject.net.config.NetworkConfigEvent;
 import org.onosproject.net.config.NetworkConfigListener;
@@ -67,6 +70,8 @@
 import org.onosproject.netconf.NetconfController;
 import org.onosproject.netconf.NetconfDeviceListener;
 
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -75,6 +80,7 @@
 import java.util.concurrent.CopyOnWriteArraySet;
 
 import static org.easymock.EasyMock.*;
+import static org.hamcrest.Matchers.is;
 import static org.junit.Assert.*;
 import static org.onlab.junit.TestTools.assertAfter;
 import static org.onosproject.provider.netconf.device.impl.NetconfDeviceProvider.APP_NAME;
@@ -94,17 +100,45 @@
     private final MastershipService mastershipService = new MockMastershipService();
     private final Driver driver = new MockDriver();
     private final NetworkConfigRegistry cfgService = new MockNetworkConfigRegistry();
+    private final Set<ConfigFactory> cfgFactories = new HashSet<>();
     private final DeviceKeyAdminService deviceKeyAdminService = new DeviceKeyAdminServiceAdapter();
     private final DeviceStore deviceStore = new MockDeviceStore();
 
     //Class for testing
     private final NetworkConfigEvent deviceAddedEvent =
             new NetworkConfigEvent(NetworkConfigEvent.Type.CONFIG_ADDED,
+                                   DeviceId.deviceId(NETCONF_DEVICE_ID_STRING), NetconfDeviceConfig.class);
+    private final NetconfDeviceConfig netconfDeviceConfig = new NetconfDeviceConfig();
+    private final NetconfDeviceConfig netconfDeviceConfigSshKey = new NetconfDeviceConfig();
+    private final NetconfDeviceConfig netconfDeviceConfigEmptyIpv4 = new NetconfDeviceConfig();
+    private final NetconfDeviceConfig netconfDeviceConfigEmptyIpv6 = new NetconfDeviceConfig();
+    private final NetworkConfigEvent deviceAddedEventOld =
+            new NetworkConfigEvent(NetworkConfigEvent.Type.CONFIG_ADDED,
                                    null, NetconfProviderConfig.class);
+    private final NetworkConfigEvent deviceAddedEventTranslated =
+            new NetworkConfigEvent(NetworkConfigEvent.Type.CONFIG_ADDED,
+                                   DeviceId.deviceId(NETCONF_DEVICE_ID_STRING_OLD),
+                                   NetconfDeviceConfig.class);
     private final NetconfProviderConfig netconfProviderConfig = new MockNetconfProviderConfig();
-    private static final String IP = "1.1.1.1";
+    private static final String NETCONF_DEVICE_ID_STRING = "netconf:1.1.1.1:830";
+    private static final String NETCONF_DEVICE_ID_STRING_OLD = "netconf:1.1.1.2:1";
+    private static final String NETCONF_DEVICE_ID_STRING_IPv6 = "netconf:2001:0db8:0000:0000:0000:ff00:0042:8329:830";
+    private static final String IP_STRING = "1.1.1.1";
+    private static final String IP_STRING_OLD = "1.1.1.2";
+    private static final String IP_STRING_IPv6 = "2001:0db8:0000:0000:0000:ff00:0042:8329";
+    private static final IpAddress IP = IpAddress.valueOf(IP_STRING);
+    private static final IpAddress IP_OLD = IpAddress.valueOf(IP_STRING_OLD);
+    private static final IpAddress IP_v6 = IpAddress.valueOf(IP_STRING_IPv6);
+    private static final int PORT = 830;
     private static final String TEST = "test";
     private static final int DELAY_DISCOVERY = 500;
+    private static final int DELAY_DURATION_DISCOVERY = 1500;
+
+    //Testing Files
+    InputStream jsonStream = NetconfDeviceProviderTest.class
+            .getResourceAsStream("/device.json");
+    InputStream jsonStreamSshKey = NetconfDeviceProviderTest.class
+            .getResourceAsStream("/deviceSshKey.json");
 
     //Provider related classes
     private CoreService coreService;
@@ -112,15 +146,16 @@
             new DefaultApplicationId(100, APP_NAME);
     private DeviceDescriptionDiscovery descriptionDiscovery = new TestDescription();
     private Set<DeviceListener> deviceListeners = new HashSet<>();
-    private ConfigFactory cfgFactory;
     private Set<NetworkConfigListener> netCfgListeners = new HashSet<>();
     private HashMap<DeviceId, Device> devices = new HashMap<>();
 
     //Controller related classes
     private Set<NetconfDeviceListener> netconfDeviceListeners = new CopyOnWriteArraySet<>();
+    private boolean available = false;
+    private boolean firstRequest = true;
 
     @Before
-    public void setUp() {
+    public void setUp() throws IOException {
         coreService = createMock(CoreService.class);
         expect(coreService.registerApplication(APP_NAME))
                 .andReturn(appId).anyTimes();
@@ -135,6 +170,21 @@
         provider.componentConfigService = new ComponentConfigAdapter();
         AbstractProjectableModel.setDriverService(null, new DriverServiceAdapter());
         provider.activate(null);
+        devices.clear();
+        available = false;
+        firstRequest = true;
+        DeviceId subject = DeviceId.deviceId(NETCONF_DEVICE_ID_STRING);
+        DeviceId subjectIpv6 = DeviceId.deviceId(NETCONF_DEVICE_ID_STRING_IPv6);
+        String key = "netconf";
+        ObjectMapper mapper = new ObjectMapper();
+        JsonNode jsonNode = mapper.readTree(jsonStream);
+        ConfigApplyDelegate delegate = new MockDelegate();
+        netconfDeviceConfig.init(subject, key, jsonNode, mapper, delegate);
+        JsonNode jsonNodesshKey = mapper.readTree(jsonStreamSshKey);
+        netconfDeviceConfigSshKey.init(subject, key, jsonNodesshKey, mapper, delegate);
+        JsonNode jsonNodeEmpty = mapper.createObjectNode();
+        netconfDeviceConfigEmptyIpv4.init(subject, key, jsonNodeEmpty, mapper, delegate);
+        netconfDeviceConfigEmptyIpv6.init(subjectIpv6, key, jsonNodeEmpty, mapper, delegate);
     }
 
     @Test
@@ -142,6 +192,7 @@
         assertTrue("Provider should be registered", deviceRegistry.getProviders().contains(provider.id()));
         assertEquals("Incorrect device service", deviceService, provider.deviceService);
         assertEquals("Incorrect provider service", providerService, provider.providerService);
+        assertTrue("Incorrect config factories", cfgFactories.containsAll(provider.factories));
         assertEquals("Device listener should be added", 1, deviceListeners.size());
         assertFalse("Thread to connect device should be running",
                     provider.executor.isShutdown() || provider.executor.isTerminated());
@@ -156,18 +207,69 @@
         assertTrue("Thread to connect device should be shutdown", provider.executor.isShutdown());
         assertTrue("Scheduled task to update device should be shutdown", provider.scheduledTask.isCancelled());
         assertNull("Provider service should be null", provider.providerService);
+        assertTrue("Network config factories not removed", cfgFactories.isEmpty());
         assertEquals("Controller listener should be removed", 0, netconfDeviceListeners.size());
     }
 
     @Test
-    @Ignore("Test is brittle")
-    public void addDevice() {
+    public void configuration() {
+        assertTrue("Configuration should be valid", netconfDeviceConfig.isValid());
+        assertThat(netconfDeviceConfig.ip(), is(IP));
+        assertThat(netconfDeviceConfig.port(), is(PORT));
+        assertThat(netconfDeviceConfig.username(), is(TEST));
+        assertThat(netconfDeviceConfig.password(), is(TEST));
+        assertThat(netconfDeviceConfigSshKey.sshKey(), is(TEST));
+    }
+
+    @Test
+    public void configurationDeviceIdIpv4() {
+        assertTrue("Configuration should be valid", netconfDeviceConfigEmptyIpv4.isValid());
+        assertThat(netconfDeviceConfigEmptyIpv4.ip(), is(IP));
+        assertThat(netconfDeviceConfigEmptyIpv4.port(), is(PORT));
+        assertThat(netconfDeviceConfigEmptyIpv4.username(), is(StringUtils.EMPTY));
+        assertThat(netconfDeviceConfigEmptyIpv4.password(), is(StringUtils.EMPTY));
+        assertThat(netconfDeviceConfigEmptyIpv4.sshKey(), is(StringUtils.EMPTY));
+    }
+
+    @Test
+    public void configurationDeviceIdIpv6() {
+        assertTrue("Configuration should be valid", netconfDeviceConfigEmptyIpv6.isValid());
+        assertThat(netconfDeviceConfigEmptyIpv6.ip(), is(IP_v6));
+        assertThat(netconfDeviceConfigEmptyIpv6.port(), is(PORT));
+        assertThat(netconfDeviceConfigEmptyIpv6.username(), is(StringUtils.EMPTY));
+        assertThat(netconfDeviceConfigEmptyIpv6.password(), is(StringUtils.EMPTY));
+        assertThat(netconfDeviceConfigEmptyIpv6.sshKey(), is(StringUtils.EMPTY));
+    }
+
+    @Test
+    public void addDeviceOld() {
         assertNotNull(providerService);
         assertTrue("Event should be relevant", provider.cfgListener.isRelevant(deviceAddedEvent));
+        assertTrue("Event should be relevant", provider.cfgListener.isRelevant(deviceAddedEventOld));
+        available = true;
+        provider.cfgListener.event(deviceAddedEventOld);
+
+        assertAfter(DELAY_DISCOVERY, DELAY_DURATION_DISCOVERY, () -> {
+            assertEquals("Device should be added", 1, deviceStore.getDeviceCount());
+            assertTrue("Device incorrectly added" + NETCONF_DEVICE_ID_STRING_OLD,
+                       devices.containsKey(DeviceId.deviceId(NETCONF_DEVICE_ID_STRING_OLD)));
+        });
+        devices.clear();
+    }
+
+    @Test
+    public void addDeviceNew() {
+        assertNotNull(providerService);
+        assertTrue("Event should be relevant", provider.cfgListener.isRelevant(deviceAddedEvent));
+        assertTrue("Event should be relevant", provider.cfgListener.isRelevant(deviceAddedEventOld));
+        available = true;
         provider.cfgListener.event(deviceAddedEvent);
 
-        assertAfter(DELAY_DISCOVERY, () ->
-                assertEquals("Device should be added", 1, deviceStore.getDeviceCount()));
+        assertAfter(DELAY_DISCOVERY, DELAY_DURATION_DISCOVERY, () -> {
+            assertEquals("Device should be added", 1, deviceStore.getDeviceCount());
+            assertTrue("Device incorrectly added" + NETCONF_DEVICE_ID_STRING,
+                       devices.containsKey(DeviceId.deviceId(NETCONF_DEVICE_ID_STRING)));
+        });
         devices.clear();
     }
 
@@ -244,7 +346,6 @@
                                                     desc.manufacturer(), desc.hwVersion(),
                                                     desc.swVersion(), desc.serialNumber(),
                                                     desc.chassisId(), desc.annotations()));
-
             return null;
         }
 
@@ -269,15 +370,16 @@
     }
 
     private class MockNetworkConfigRegistry extends NetworkConfigRegistryAdapter {
+        NetconfDeviceConfig cfg = null;
 
         @Override
         public void registerConfigFactory(ConfigFactory configFactory) {
-            cfgFactory = configFactory;
+            cfgFactories.add(configFactory);
         }
 
         @Override
         public void unregisterConfigFactory(ConfigFactory configFactory) {
-            cfgFactory = null;
+            cfgFactories.remove(configFactory);
         }
 
         @Override
@@ -293,17 +395,61 @@
 
         @Override
         public <S, C extends Config<S>> C getConfig(S subject, Class<C> configClass) {
-            if (configClass.equals(NetconfProviderConfig.class)) {
-                return (C) netconfProviderConfig;
-            } else {
-                return (C) new BasicDeviceConfig();
+            if (available) {
+                if (configClass.equals(NetconfProviderConfig.class)) {
+                    return (C) netconfProviderConfig;
+                }
+                DeviceId did = (DeviceId) subject;
+                if (configClass.equals(NetconfDeviceConfig.class)
+                        && did.equals(DeviceId.deviceId(NETCONF_DEVICE_ID_STRING))) {
+                    return (C) netconfDeviceConfig;
+                } else if (configClass.equals(NetconfDeviceConfig.class)
+                        && did.equals(DeviceId.deviceId(NETCONF_DEVICE_ID_STRING_OLD))) {
+                    if (firstRequest) {
+                        firstRequest = false;
+                        return null;
+                    }
+                    return (C) cfg;
+                } else {
+                    return (C) new BasicDeviceConfig();
+                }
             }
+            return null;
         }
+
+        @Override
+        public <S, C extends Config<S>> C applyConfig(S subject, Class<C> configClass,
+                                                      JsonNode json) {
+            cfg = new NetconfDeviceConfig();
+            ObjectMapper mapper = new ObjectMapper();
+            cfg.init((DeviceId) subject, "netconf", mapper.createObjectNode(), mapper, null);
+            cfg.setIp(json.get("ip").asText())
+                    .setPort(json.get("port").asInt())
+                    .setUsername(json.get("username").asText())
+                    .setPassword(json.get("password").asText())
+                    .setSshKey(json.get("sshkey").asText());
+            provider.cfgListener.event(deviceAddedEventTranslated);
+            return (C) cfg;
+        }
+
+        @Override
+        public <S, C extends Config<S>> Set<S> getSubjects(Class<S> subjectClass, Class<C> configClass) {
+            Set<S> subjects = new HashSet<S>();
+            if (available) {
+                if (cfg != null) {
+                    subjects.add((S) DeviceId.deviceId(NETCONF_DEVICE_ID_STRING_OLD));
+                } else {
+                    subjects.add((S) DeviceId.deviceId(NETCONF_DEVICE_ID_STRING));
+                }
+            }
+            return subjects;
+        }
+
     }
 
     private class MockNetconfProviderConfig extends NetconfProviderConfig {
         protected NetconfDeviceAddress deviceInfo =
-                new NetconfDeviceAddress(IpAddress.valueOf(IP), 1, TEST, TEST);
+                new NetconfDeviceAddress(IP_OLD, 1, TEST, TEST);
 
         @Override
         public Set<NetconfProviderConfig.NetconfDeviceAddress> getDevicesAddresses() throws ConfigException {
@@ -362,4 +508,10 @@
             portDescriptions.add(portDescription);
         }
     }
+
+    private class MockDelegate implements ConfigApplyDelegate {
+        @Override
+        public void onApply(Config configFile) {
+        }
+    }
 }
diff --git a/providers/netconf/device/src/test/resources/device.json b/providers/netconf/device/src/test/resources/device.json
new file mode 100644
index 0000000..f11a264
--- /dev/null
+++ b/providers/netconf/device/src/test/resources/device.json
@@ -0,0 +1,6 @@
+{
+  "ip":"1.1.1.1",
+  "port":830,
+  "username":"test",
+  "password":"test"
+}
diff --git a/providers/netconf/device/src/test/resources/deviceSshKey.json b/providers/netconf/device/src/test/resources/deviceSshKey.json
new file mode 100644
index 0000000..c799935
--- /dev/null
+++ b/providers/netconf/device/src/test/resources/deviceSshKey.json
@@ -0,0 +1,7 @@
+{
+  "ip":"1.1.1.1",
+  "port":830,
+  "username":"test",
+  "password":"test",
+  "sshkey":"test"
+}
diff --git a/tools/test/configs/netconf-cfg-old.json b/tools/test/configs/netconf-cfg-old.json
new file mode 100644
index 0000000..e9910b4
--- /dev/null
+++ b/tools/test/configs/netconf-cfg-old.json
@@ -0,0 +1,19 @@
+  {
+  "devices":{
+    "netconf:192.168.56.104:830": {
+      "basic":{
+        "driver":"ovs-netconf"
+      }
+    }
+  },
+  "apps":{
+    "org.onosproject.netconf":{
+      "devices":[{
+        "username":"mininet",
+        "password":"mininet",
+        "ip":"192.168.56.104",
+        "port":830
+      }]
+    }
+  }
+}
\ No newline at end of file
diff --git a/tools/test/configs/netconf-cfg.json b/tools/test/configs/netconf-cfg.json
index aff0604..fad22cb 100644
--- a/tools/test/configs/netconf-cfg.json
+++ b/tools/test/configs/netconf-cfg.json
@@ -1,19 +1,15 @@
 {
-  "devices":{
-    "netconf:10.1.9.24:830":{
-      "basic":{
-        "driver":"ovs-netconf"
+  "devices": {
+    "netconf:192.168.56.104:830": {
+      "netconf": {
+        "ip": "192.168.56.104",
+        "port": 830,
+        "username": "mininet",
+        "password": "mininet"
+      },
+      "basic": {
+        "driver": "ovs-netconf"
       }
     }
-  },
-  "apps":{
-    "org.onosproject.netconf":{
-      "devices":[{
-        "username":"mininet",
-        "password":"mininet",
-        "ip":"10.1.9.24",
-        "port":830
-      }]
-    }
   }
 }