Enable REST to manage devices under a proxy

This commit integrates the REST proxy within the standard REST
protocol and provider, supporting multiple devices managed by
a single REST end point (e.g. a REST-based second level controller).

It introduces the concept of RestSBServer, that represents
the REST service exposed by a single or a set of devices.

Change-Id: I0b187151848c20bc1fd8c39a33d57a500f667533
diff --git a/core/api/src/main/java/org/onosproject/net/AnnotationKeys.java b/core/api/src/main/java/org/onosproject/net/AnnotationKeys.java
index 5345e34..9e68636 100644
--- a/core/api/src/main/java/org/onosproject/net/AnnotationKeys.java
+++ b/core/api/src/main/java/org/onosproject/net/AnnotationKeys.java
@@ -156,6 +156,11 @@
     public static final String PROTECTED = "protected";
 
     /**
+     * Annotation key for REST server identifier.
+     */
+    public static final String REST_SERVER = "restServer";
+
+    /**
      * Returns the value annotated object for the specified annotation key.
      * The annotated value is expected to be String that can be parsed as double.
      * If parsing fails, the returned value will be 1.0.
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/DevicesDiscovery.java b/core/api/src/main/java/org/onosproject/net/behaviour/DevicesDiscovery.java
new file mode 100644
index 0000000..6c553aa
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/DevicesDiscovery.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2017-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.net.behaviour;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceDescription;
+import org.onosproject.net.driver.HandlerBehaviour;
+
+import java.util.Set;
+
+/**
+ * Handler behaviour capable of managing a set of forwarding plane devices.
+ */
+public interface DevicesDiscovery extends HandlerBehaviour {
+
+    /**
+     * Returns a set of proxied device ids.
+     *
+     * @return a set of deviceId
+     */
+    Set<DeviceId> deviceIds();
+
+    /**
+     * Returns a device description appropriately annotated to support
+     * downstream model extension via projections of the resulting device,
+     * as in the following example.
+     * This process permits to discovery the description of a proxied device by
+     * leveraging on the device ids discovered
+     * @see org.onosproject.net.behaviour.DevicesDiscovery#deviceDetails(DeviceId)
+     *
+     * @param deviceId the device Id of the device of interest
+     * @return annotated device description
+     */
+    DeviceDescription deviceDetails(DeviceId deviceId);
+}
\ No newline at end of file
diff --git a/protocols/rest/api/src/main/java/org/onosproject/protocol/http/ctl/HttpSBControllerImpl.java b/protocols/rest/api/src/main/java/org/onosproject/protocol/http/ctl/HttpSBControllerImpl.java
index 2c2d1a2..b162b56 100644
--- a/protocols/rest/api/src/main/java/org/onosproject/protocol/http/ctl/HttpSBControllerImpl.java
+++ b/protocols/rest/api/src/main/java/org/onosproject/protocol/http/ctl/HttpSBControllerImpl.java
@@ -63,8 +63,8 @@
             LoggerFactory.getLogger(HttpSBControllerImpl.class);
     private static final String XML = "xml";
     private static final String JSON = "json";
-    private static final String DOUBLESLASH = "//";
-    private static final String COLON = ":";
+    protected static final String DOUBLESLASH = "//";
+    protected static final String COLON = ":";
     private static final int STATUS_OK = Response.Status.OK.getStatusCode();
     private static final int STATUS_CREATED = Response.Status.CREATED.getStatusCode();
     private static final int STATUS_ACCEPTED = Response.Status.ACCEPTED.getStatusCode();
@@ -279,7 +279,7 @@
                                       .build()).build();
     }
 
-    private String getUrlString(DeviceId device, String request) {
+    protected String getUrlString(DeviceId device, String request) {
         if (deviceMap.get(device).url() != null) {
             return deviceMap.get(device).protocol() + COLON + DOUBLESLASH
                     + deviceMap.get(device).url() + request;
diff --git a/protocols/rest/api/src/main/java/org/onosproject/protocol/http/ctl/package-info.java b/protocols/rest/api/src/main/java/org/onosproject/protocol/http/ctl/package-info.java
index e6fe54d..6401451 100644
--- a/protocols/rest/api/src/main/java/org/onosproject/protocol/http/ctl/package-info.java
+++ b/protocols/rest/api/src/main/java/org/onosproject/protocol/http/ctl/package-info.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright 2016-present Open Networking Laboratory
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,8 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 /**
- * @author onos
- *
+ * REST southbound protocol controller.
  */
 package org.onosproject.protocol.http.ctl;
diff --git a/protocols/rest/api/src/main/java/org/onosproject/protocol/http/package-info.java b/protocols/rest/api/src/main/java/org/onosproject/protocol/http/package-info.java
index c54b035..e4e6cb7 100644
--- a/protocols/rest/api/src/main/java/org/onosproject/protocol/http/package-info.java
+++ b/protocols/rest/api/src/main/java/org/onosproject/protocol/http/package-info.java
@@ -1,20 +1,20 @@
-/**
+/*
  * Copyright 2016-present Open Networking Laboratory
- *
+ * <p>
  * 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
- *
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
  * 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.
  */
+
 /**
- * @author onos
- *
+ * REST southbound protocol controller.
  */
 package org.onosproject.protocol.http;
diff --git a/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/DefaultRestSBDevice.java b/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/DefaultRestSBDevice.java
index 40816a4..53ae0dc 100644
--- a/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/DefaultRestSBDevice.java
+++ b/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/DefaultRestSBDevice.java
@@ -16,16 +16,16 @@
 
 package org.onosproject.protocol.rest;
 
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Objects;
-
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
 import org.apache.commons.lang3.StringUtils;
 import org.onlab.packet.IpAddress;
 import org.onosproject.net.DeviceId;
 
-import com.google.common.base.MoreObjects;
-import com.google.common.base.Preconditions;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Objects;
+import java.util.Optional;
 
 /**
  * Default implementation for Rest devices.
@@ -40,9 +40,21 @@
     private boolean isActive;
     private String protocol;
     private String url;
+    private boolean isProxy;
+    private final Optional<String> testUrl;
+    private final Optional<String> manufacturer;
+    private final Optional<String> hwVersion;
+    private final Optional<String> swVersion;
 
     public DefaultRestSBDevice(IpAddress ip, int port, String name, String password,
                                String protocol, String url, boolean isActive) {
+        this(ip, port, name, password, protocol, url, isActive, "", "", "", "");
+    }
+
+    public DefaultRestSBDevice(IpAddress ip, int port, String name, String password,
+                               String protocol, String url, boolean isActive, String testUrl, String manufacturer,
+                               String hwVersion,
+                               String swVersion) {
         Preconditions.checkNotNull(ip, "IP address cannot be null");
         Preconditions.checkArgument(port > 0, "Port address cannot be negative");
         Preconditions.checkNotNull(protocol, "protocol address cannot be null");
@@ -53,6 +65,21 @@
         this.isActive = isActive;
         this.protocol = protocol;
         this.url = StringUtils.isEmpty(url) ? null : url;
+        this.manufacturer = StringUtils.isEmpty(manufacturer) ?
+                Optional.empty() : Optional.ofNullable(manufacturer);
+        this.hwVersion = StringUtils.isEmpty(hwVersion) ?
+                Optional.empty() : Optional.ofNullable(hwVersion);
+        this.swVersion = StringUtils.isEmpty(swVersion) ?
+                Optional.empty() : Optional.ofNullable(swVersion);
+        this.testUrl = StringUtils.isEmpty(testUrl) ?
+                Optional.empty() : Optional.ofNullable(testUrl);
+        if (this.manufacturer.isPresent()
+                && this.hwVersion.isPresent()
+                && this.swVersion.isPresent()) {
+            this.isProxy = true;
+        } else {
+            this.isProxy = false;
+        }
     }
 
     @Override
@@ -107,14 +134,45 @@
     }
 
     @Override
+    public boolean isProxy() {
+        return isProxy;
+    }
+
+    @Override
+    public Optional<String> testUrl() {
+        return testUrl;
+    }
+
+    @Override
+    public Optional<String> manufacturer() {
+        return manufacturer;
+    }
+
+    @Override
+    public Optional<String> hwVersion() {
+        return hwVersion;
+    }
+
+    @Override
+    public Optional<String> swVersion() {
+        return swVersion;
+    }
+
+    @Override
     public String toString() {
         return MoreObjects.toStringHelper(this)
+                .omitNullValues()
                 .add("url", url)
+                .add("testUrl", testUrl)
                 .add("protocol", protocol)
                 .add("username", username)
                 .add("port", port)
                 .add("ip", ip)
+                .add("manufacturer", manufacturer.orElse(null))
+                .add("hwVersion", hwVersion.orElse(null))
+                .add("swVersion", swVersion.orElse(null))
                 .toString();
+
     }
 
     @Override
diff --git a/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/RestSBController.java b/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/RestSBController.java
index 2fe3791..bca4a6c 100644
--- a/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/RestSBController.java
+++ b/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/RestSBController.java
@@ -16,11 +16,44 @@
 
 package org.onosproject.protocol.rest;
 
+import org.onosproject.net.DeviceId;
 import org.onosproject.protocol.http.HttpSBController;
 
+import java.util.Set;
+
 /**
  * Abstraction of an REST controller. Serves as a one stop shop for obtaining
- * Rest southbound devices and (un)register listeners.
+ * Rest southbound devices.
  */
 public interface RestSBController extends HttpSBController {
+
+    /**
+     * Add a new association between a proxied device exposed to ONOS and
+     * a REST proxy server.
+     * @param deviceId REST device identifier
+     * @param proxy REST proxy device
+     */
+    void addProxiedDevice(DeviceId deviceId, RestSBDevice proxy);
+
+    /**
+     * Remove the association between a proxied device exposed to ONOS
+     * and a REST proxy server.
+     * @param deviceId REST device identifier
+     */
+    void removeProxiedDevice(DeviceId deviceId);
+
+    /**
+     * Get all the proxied device exposed to ONOS ids under the same
+     * REST proxy server.
+     * @param proxyId REST proxy device identifier
+     * @return set of device ids under same proxy
+     */
+    Set<DeviceId> getProxiedDevices(DeviceId proxyId);
+
+    /**
+     * Get a REST proxied server given a device id.
+     * @param deviceId the id of proxied device exposed to ONOS
+     * @return the corresponding REST proxied device
+     */
+    RestSBDevice getProxySBDevice(DeviceId deviceId);
 }
diff --git a/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/RestSBDevice.java b/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/RestSBDevice.java
index b1f61ac..bacbbfe 100644
--- a/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/RestSBDevice.java
+++ b/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/RestSBDevice.java
@@ -19,6 +19,8 @@
 import org.onlab.packet.IpAddress;
 import org.onosproject.net.DeviceId;
 
+import java.util.Optional;
+
 /**
  * Represents an abstraction of a Rest Device in ONOS.
  */
@@ -85,4 +87,39 @@
      * @return url
      */
     String url();
+
+    /**
+     * Returns the proxy state of this device
+     * (if true, the device is proxying multiple ONOS devices).
+     * @return proxy state
+     */
+    boolean isProxy();
+
+    /**
+     * Returns the url for the REST TEST requests.
+     *
+     * @return testUrl
+     */
+    Optional<String> testUrl();
+
+    /**
+     * The manufacturer of the rest device.
+     *
+     * @return the name of the manufacturer
+     */
+    Optional<String> manufacturer();
+
+    /**
+     * The hardware version of the rest device.
+     *
+     * @return the hardware version
+     */
+    Optional<String> hwVersion();
+
+    /**
+     * The software version of rest device.
+     *
+     * @return the software version.
+     */
+    Optional<String> swVersion();
 }
diff --git a/protocols/rest/ctl/src/main/java/org/onosproject/protocol/rest/ctl/RestSBControllerImpl.java b/protocols/rest/ctl/src/main/java/org/onosproject/protocol/rest/ctl/RestSBControllerImpl.java
index 37dfc85..86f8020 100644
--- a/protocols/rest/ctl/src/main/java/org/onosproject/protocol/rest/ctl/RestSBControllerImpl.java
+++ b/protocols/rest/ctl/src/main/java/org/onosproject/protocol/rest/ctl/RestSBControllerImpl.java
@@ -16,15 +16,24 @@
 
 package org.onosproject.protocol.rest.ctl;
 
+import com.google.common.collect.ImmutableSet;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
 import org.apache.felix.scr.annotations.Service;
+import org.onosproject.net.DeviceId;
 import org.onosproject.protocol.http.ctl.HttpSBControllerImpl;
 import org.onosproject.protocol.rest.RestSBController;
+import org.onosproject.protocol.rest.RestSBDevice;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.ws.rs.client.WebTarget;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
 /**
  * The implementation of RestSBController.
  */
@@ -35,6 +44,8 @@
     private static final Logger log =
             LoggerFactory.getLogger(RestSBControllerImpl.class);
 
+    private final Map<DeviceId, RestSBDevice> proxiedDeviceMap = new ConcurrentHashMap<>();
+
     @Activate
     public void activate() {
         log.info("Started");
@@ -47,4 +58,64 @@
         log.info("Stopped");
     }
 
+
+    @Override
+    public void addProxiedDevice(DeviceId deviceId, RestSBDevice proxy) {
+        proxiedDeviceMap.put(deviceId, proxy);
+        log.debug("Added device: {} to proxy {}", deviceId, proxy.deviceId());
+    }
+
+    @Override
+    public void removeProxiedDevice(DeviceId deviceId) {
+        log.debug("Removed device: {} from proxy {}", deviceId, proxiedDeviceMap.get(deviceId).deviceId());
+        proxiedDeviceMap.remove(deviceId);
+    }
+
+
+    @Override
+    public Set<DeviceId> getProxiedDevices(DeviceId proxyId) {
+        return ImmutableSet.copyOf(
+                proxiedDeviceMap.keySet().stream().filter(
+                        v -> proxiedDeviceMap.get(v).deviceId().equals(proxyId)
+                ).collect(Collectors.toSet()));
+    }
+
+    @Override
+    public RestSBDevice getProxySBDevice(DeviceId deviceId) {
+        return proxiedDeviceMap.get(deviceId);
+    }
+
+    @Override
+    protected WebTarget getWebTarget(DeviceId device, String request) {
+        DeviceId deviceId = device;
+        if (proxiedDeviceMap.containsKey(device)) {
+            deviceId = proxiedDeviceMap.get(device).deviceId();
+        }
+        return super.getWebTarget(deviceId, request);
+    }
+
+    @Override
+    protected String getUrlString(DeviceId id, String request) {
+        DeviceId deviceId = id;
+        if (proxiedDeviceMap.containsKey(id)) {
+            deviceId = proxiedDeviceMap.get(id).deviceId();
+        }
+
+        RestSBDevice device = super.getDeviceMap().get(deviceId);
+        if (device != null) {
+            if (device.url() != null && !device.url().isEmpty()) {
+                return device.protocol() + super.COLON + super.DOUBLESLASH + device.ip().toString() +
+                        super.COLON + device.port()
+                        + device.url() + request;
+            } else {
+                return device.protocol() + super.COLON +
+                        super.DOUBLESLASH +
+                        device.ip().toString() +
+                        super.COLON + device.port() + request;
+            }
+        } else {
+            return super.getUrlString(deviceId, request);
+        }
+
+    }
 }
diff --git a/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProvider.java b/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProvider.java
index 48f6a68..93f9ca9 100644
--- a/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProvider.java
+++ b/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProvider.java
@@ -17,7 +17,6 @@
 package org.onosproject.provider.rest.device.impl;
 
 import com.google.common.base.Objects;
-import com.google.common.base.Preconditions;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -34,6 +33,7 @@
 import org.onosproject.net.MastershipRole;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.SparseAnnotations;
+import org.onosproject.net.behaviour.DevicesDiscovery;
 import org.onosproject.net.behaviour.PortDiscovery;
 import org.onosproject.net.config.ConfigFactory;
 import org.onosproject.net.config.NetworkConfigEvent;
@@ -46,6 +46,12 @@
 import org.onosproject.net.device.DeviceProviderRegistry;
 import org.onosproject.net.device.DeviceProviderService;
 import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.DefaultDriverData;
+import org.onosproject.net.driver.DefaultDriverHandler;
+import org.onosproject.net.driver.Driver;
+import org.onosproject.net.driver.DriverData;
+import org.onosproject.net.driver.DriverHandler;
+import org.onosproject.net.driver.DriverService;
 import org.onosproject.net.provider.AbstractProvider;
 import org.onosproject.net.provider.ProviderId;
 import org.onosproject.protocol.rest.RestSBController;
@@ -58,6 +64,7 @@
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
+import static com.google.common.base.Preconditions.checkNotNull;
 import static org.onlab.util.Tools.groupedThreads;
 import static org.onosproject.net.config.NetworkConfigEvent.Type.CONFIG_ADDED;
 import static org.onosproject.net.config.NetworkConfigEvent.Type.CONFIG_UPDATED;
@@ -75,11 +82,12 @@
     private static final String JSON = "json";
     private static final String PROVIDER = "org.onosproject.provider.rest.device";
     private static final String IPADDRESS = "ipaddress";
-    private static final int TEST_CONNECT_TIMEOUT = 1000;
     private static final String HTTPS = "https";
     private static final String AUTHORIZATION_PROPERTY = "authorization";
     private static final String BASIC_AUTH_PREFIX = "Basic ";
     private static final String URL_SEPARATOR = "://";
+    protected static final String ISNOTNULL = "Rest device is not null";
+    private static final String UNKNOWN = "unknown";
     private final Logger log = getLogger(getClass());
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
@@ -97,10 +105,12 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected DeviceService deviceService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DriverService driverService;
+
 
     private DeviceProviderService providerService;
-    protected static final String ISNOTNULL = "Rest device is not null";
-    private static final String UNKNOWN = "unknown";
+    private ApplicationId appId;
 
     private final ExecutorService executor =
             Executors.newFixedThreadPool(5, groupedThreads("onos/restsbprovider", "device-installer-%d", log));
@@ -116,7 +126,6 @@
                 }
             };
     private final NetworkConfigListener cfgLister = new InternalNetworkConfigListener();
-    private ApplicationId appId;
 
     private Set<DeviceId> addedDevices = new HashSet<>();
 
@@ -161,34 +170,98 @@
     public boolean isReachable(DeviceId deviceId) {
         RestSBDevice restDevice = controller.getDevice(deviceId);
         if (restDevice == null) {
-            log.debug("the requested device id: " +
-                              deviceId.toString() +
-                              "  is not associated to any REST Device");
-            return false;
+            restDevice = controller.getProxySBDevice(deviceId);
+            if (restDevice == null) {
+                log.debug("the requested device id: " +
+                                  deviceId.toString() +
+                                  "  is not associated to any REST or REST " +
+                                  "proxy Device");
+                return false;
+            }
         }
         return restDevice.isActive();
     }
 
-    private void deviceAdded(RestSBDevice nodeId) {
-        Preconditions.checkNotNull(nodeId, ISNOTNULL);
-        DeviceId deviceId = nodeId.deviceId();
-        ChassisId cid = new ChassisId();
-        String ipAddress = nodeId.ip().toString();
-        SparseAnnotations annotations = DefaultAnnotations.builder()
-                .set(IPADDRESS, ipAddress)
-                .set(AnnotationKeys.PROTOCOL, REST.toUpperCase())
-                .build();
-        DeviceDescription deviceDescription = new DefaultDeviceDescription(
-                deviceId.uri(),
-                Device.Type.SWITCH,
-                UNKNOWN, UNKNOWN,
-                UNKNOWN, UNKNOWN,
-                cid,
-                annotations);
-        nodeId.setActive(true);
-        providerService.deviceConnected(deviceId, deviceDescription);
-        checkAndUpdateDevice(deviceId);
-        addedDevices.add(deviceId);
+    private void deviceAdded(RestSBDevice restSBDev) {
+        checkNotNull(restSBDev, ISNOTNULL);
+
+        //check if the server is controlling a single or multiple devices
+        if (restSBDev.isProxy()) {
+
+            Driver driver = driverService.getDriver(restSBDev.manufacturer().get(),
+                                                    restSBDev.hwVersion().get(),
+                                                    restSBDev.swVersion().get());
+
+            if (driver != null && driver.hasBehaviour(DevicesDiscovery.class)) {
+
+                //Creates the driver to communicate with the server
+                DevicesDiscovery devicesDiscovery =
+                        devicesDiscovery(restSBDev, driver);
+                Set<DeviceId> deviceIds = devicesDiscovery.deviceIds();
+                restSBDev.setActive(true);
+                deviceIds.stream().forEach(deviceId -> {
+                    controller.addProxiedDevice(deviceId, restSBDev);
+                    DeviceDescription devDesc =
+                            devicesDiscovery.deviceDetails(deviceId);
+                    checkNotNull(devDesc,
+                                 "deviceDescription cannot be null");
+                    providerService.deviceConnected(
+                            deviceId, mergeAnn(restSBDev.deviceId(), devDesc));
+
+                    if (driver.hasBehaviour(DeviceDescriptionDiscovery.class)) {
+                        DriverHandler h = driverService.createHandler(deviceId);
+                        DeviceDescriptionDiscovery devDisc =
+                                h.behaviour(DeviceDescriptionDiscovery.class);
+                        providerService.updatePorts(deviceId,
+                                                    devDisc.discoverPortDetails());
+                    }
+
+                    checkAndUpdateDevice(deviceId);
+                    addedDevices.add(deviceId);
+                });
+            } else {
+                log.warn("Driver not found for {}", restSBDev);
+            }
+        } else {
+            DeviceId deviceId = restSBDev.deviceId();
+            ChassisId cid = new ChassisId();
+            String ipAddress = restSBDev.ip().toString();
+            SparseAnnotations annotations = DefaultAnnotations.builder()
+                    .set(IPADDRESS, ipAddress)
+                    .set(AnnotationKeys.PROTOCOL, REST.toUpperCase())
+                    .build();
+            DeviceDescription deviceDescription = new DefaultDeviceDescription(
+                    deviceId.uri(),
+                    Device.Type.SWITCH,
+                    UNKNOWN, UNKNOWN,
+                    UNKNOWN, UNKNOWN,
+                    cid,
+                    annotations);
+            restSBDev.setActive(true);
+            providerService.deviceConnected(deviceId, deviceDescription);
+            checkAndUpdateDevice(deviceId);
+            addedDevices.add(deviceId);
+        }
+    }
+
+    private DefaultDeviceDescription mergeAnn(DeviceId devId, DeviceDescription desc) {
+        return new DefaultDeviceDescription(
+                desc,
+                DefaultAnnotations.merge(
+                        DefaultAnnotations.builder()
+                                .set(AnnotationKeys.PROTOCOL, REST.toUpperCase())
+                                // The rest server added as annotation to the device
+                                .set(AnnotationKeys.REST_SERVER, devId.toString())
+                                .build(),
+                        desc.annotations()));
+    }
+
+    private DevicesDiscovery devicesDiscovery(RestSBDevice restSBDevice, Driver driver) {
+        DriverData driverData = new DefaultDriverData(driver, restSBDevice.deviceId());
+        DevicesDiscovery devicesDiscovery = driver.createBehaviour(driverData,
+                                                                   DevicesDiscovery.class);
+        devicesDiscovery.setHandler(new DefaultDriverHandler(driverData));
+        return devicesDiscovery;
     }
 
     private void checkAndUpdateDevice(DeviceId deviceId) {
@@ -200,17 +273,17 @@
             if (isReachable && deviceService.isAvailable(deviceId)) {
                 Device device = deviceService.getDevice(deviceId);
                 if (device.is(DeviceDescriptionDiscovery.class)) {
-                        DeviceDescriptionDiscovery deviceDescriptionDiscovery =
-                                device.as(DeviceDescriptionDiscovery.class);
-                        DeviceDescription updatedDeviceDescription =
-                                deviceDescriptionDiscovery.discoverDeviceDetails();
-                        if (updatedDeviceDescription != null &&
-                                !descriptionEquals(device, updatedDeviceDescription)) {
-                            providerService.deviceConnected(
-                                    deviceId,
-                                    new DefaultDeviceDescription(
-                                            updatedDeviceDescription, true,
-                                            updatedDeviceDescription.annotations()));
+                    DeviceDescriptionDiscovery deviceDescriptionDiscovery =
+                            device.as(DeviceDescriptionDiscovery.class);
+                    DeviceDescription updatedDeviceDescription =
+                            deviceDescriptionDiscovery.discoverDeviceDetails();
+                    if (updatedDeviceDescription != null &&
+                            !descriptionEquals(device, updatedDeviceDescription)) {
+                        providerService.deviceConnected(
+                                deviceId,
+                                new DefaultDeviceDescription(
+                                        updatedDeviceDescription, true,
+                                        updatedDeviceDescription.annotations()));
                         //if ports are not discovered, retry the discovery
                         if (deviceService.getPorts(deviceId).isEmpty()) {
                             discoverPorts(deviceId);
@@ -237,8 +310,12 @@
     }
 
     private void deviceRemoved(DeviceId deviceId) {
-        Preconditions.checkNotNull(deviceId, ISNOTNULL);
+        checkNotNull(deviceId, ISNOTNULL);
         providerService.deviceDisconnected(deviceId);
+        controller.getProxiedDevices(deviceId).stream().forEach(device -> {
+            controller.removeProxiedDevice(device);
+            providerService.deviceDisconnected(device);
+        });
         controller.removeDevice(deviceId);
     }
 
@@ -282,11 +359,16 @@
         }
     }
 
-    private boolean testDeviceConnection(RestSBDevice device) {
+    private boolean testDeviceConnection(RestSBDevice dev) {
         try {
-            return controller.get(device.deviceId(), "", JSON) != null;
+            if (dev.testUrl().isPresent()) {
+                return controller
+                        .get(dev.deviceId(), dev.testUrl().get(), JSON) != null;
+            }
+            return controller.get(dev.deviceId(), "", JSON) != null;
+
         } catch (ProcessingException e) {
-            log.warn("Cannot connect to device {}", device, e);
+            log.warn("Cannot connect to device {}", dev, e);
         }
         return false;
     }
diff --git a/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProviderUtilities.java b/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProviderUtilities.java
index 1f97bee..e92d941 100644
--- a/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProviderUtilities.java
+++ b/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProviderUtilities.java
@@ -43,7 +43,7 @@
      * Needs addressing for secutirty purposes.
      *
      * @throws NoSuchAlgorithmException if algorithm specified is not available
-     * @throws KeyManagementException if unable to use the key
+     * @throws KeyManagementException   if unable to use the key
      */
     //FIXME redo for security purposes.
     protected static void enableSslCert() throws NoSuchAlgorithmException, KeyManagementException {
diff --git a/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestProviderConfig.java b/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestProviderConfig.java
index c31f604..5e6fc96 100644
--- a/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestProviderConfig.java
+++ b/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestProviderConfig.java
@@ -26,7 +26,6 @@
 import org.onosproject.protocol.rest.DefaultRestSBDevice;
 import org.onosproject.protocol.rest.RestSBDevice;
 
-
 import java.util.Set;
 
 /**
@@ -43,6 +42,10 @@
     private static final String PASSWORD = "password";
     private static final String PROTOCOL = "protocol";
     private static final String URL = "url";
+    private static final String TESTURL = "testUrl";
+    private static final String MANUFACTURER = "manufacturer";
+    private static final String HWVERSION = "hwVersion";
+    private static final String SWVERSION = "swVersion";
 
     public Set<RestSBDevice> getDevicesAddresses() throws ConfigException {
         Set<RestSBDevice> devicesAddresses = Sets.newHashSet();
@@ -56,10 +59,15 @@
                 String password = node.path(PASSWORD).asText();
                 String protocol = node.path(PROTOCOL).asText();
                 String url = node.path(URL).asText();
+                String testUrl = node.path(TESTURL).asText();
+                String manufacturer = node.path(MANUFACTURER).asText();
+                String hwVersion = node.path(HWVERSION).asText();
+                String swVersion = node.path(SWVERSION).asText();
+
                 devicesAddresses.add(new DefaultRestSBDevice(ipAddr, port, username,
                                                              password, protocol,
-                                                             url, false));
-
+                                                             url, false, testUrl, manufacturer,
+                                                             hwVersion, swVersion));
             }
         } catch (IllegalArgumentException e) {
             throw new ConfigException(CONFIG_VALUE_ERROR, e);