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/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);