blob: 478829e3b638b30df7dba69187865f3fc123f04a [file] [log] [blame]
/*
* Copyright 2016-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.rest.device.impl;
import com.google.common.base.Objects;
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;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onlab.packet.ChassisId;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.incubator.net.config.basics.ConfigException;
import org.onosproject.net.AnnotationKeys;
import org.onosproject.net.DefaultAnnotations;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
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;
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;
import org.onosproject.net.device.DeviceProvider;
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.DefaultRestSBDevice;
import org.onosproject.protocol.rest.RestSBController;
import org.onosproject.protocol.rest.RestSBDevice;
import org.slf4j.Logger;
import javax.ws.rs.ProcessingException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
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;
import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Provider for devices that use REST as means of configuration communication.
*/
@Component(immediate = true)
public class RestDeviceProvider extends AbstractProvider
implements DeviceProvider {
private static final String APP_NAME = "org.onosproject.restsb";
protected static final String REST = "rest";
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 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)
protected DeviceProviderRegistry providerRegistry;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected RestSBController controller;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigRegistry cfgService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceService deviceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DriverService driverService;
private DeviceProviderService providerService;
private ApplicationId appId;
private final ExecutorService executor =
Executors.newFixedThreadPool(5, groupedThreads("onos/restsbprovider", "device-installer-%d", log));
protected final List<ConfigFactory> factories = ImmutableList.of(
new ConfigFactory<ApplicationId, RestProviderConfig>(APP_SUBJECT_FACTORY,
RestProviderConfig.class,
"devices",
true) {
@Override
public RestProviderConfig createConfig() {
return new RestProviderConfig();
}
},
new ConfigFactory<DeviceId, RestDeviceConfig>(SubjectFactories.DEVICE_SUBJECT_FACTORY,
RestDeviceConfig.class,
REST) {
@Override
public RestDeviceConfig createConfig() {
return new RestDeviceConfig();
}
});
private final NetworkConfigListener cfgLister = new InternalNetworkConfigListener();
private Set<DeviceId> addedDevices = new HashSet<>();
@Activate
public void activate() {
appId = coreService.registerApplication(APP_NAME);
providerService = providerRegistry.register(this);
factories.forEach(cfgService::registerConfigFactory);
cfgService.addListener(cfgLister);
executor.execute(RestDeviceProvider.this::createAndConnectDevices);
executor.execute(RestDeviceProvider.this::createDevices);
log.info("Started");
}
@Deactivate
public void deactivate() {
cfgService.removeListener(cfgLister);
controller.getDevices().keySet().forEach(this::deviceRemoved);
providerRegistry.unregister(this);
providerService = null;
factories.forEach(cfgService::unregisterConfigFactory);
log.info("Stopped");
}
public RestDeviceProvider() {
super(new ProviderId(REST, PROVIDER));
}
@Override
public void triggerProbe(DeviceId deviceId) {
// TODO: This will be implemented later.
log.info("Triggering probe on device {}", deviceId);
}
@Override
public void roleChanged(DeviceId deviceId, MastershipRole newRole) {
// TODO: This will be implemented later.
}
@Override
public boolean isReachable(DeviceId deviceId) {
RestSBDevice restDevice = controller.getDevice(deviceId);
if (restDevice == null) {
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 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) {
if (deviceService.getDevice(deviceId) == null) {
log.warn("Device {} has not been added to store, " +
"maybe due to a problem in connectivity", deviceId);
} else {
boolean isReachable = isReachable(deviceId);
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()));
//if ports are not discovered, retry the discovery
if (deviceService.getPorts(deviceId).isEmpty()) {
discoverPorts(deviceId);
}
}
} else {
log.warn("No DeviceDescriptionDiscovery behaviour for device {}", deviceId);
}
} else if (!isReachable && deviceService.isAvailable(deviceId)) {
providerService.deviceDisconnected(deviceId);
}
}
}
private boolean descriptionEquals(Device device, DeviceDescription updatedDeviceDescription) {
return Objects.equal(device.id().uri(), updatedDeviceDescription.deviceUri())
&& Objects.equal(device.type(), updatedDeviceDescription.type())
&& Objects.equal(device.manufacturer(), updatedDeviceDescription.manufacturer())
&& Objects.equal(device.hwVersion(), updatedDeviceDescription.hwVersion())
&& Objects.equal(device.swVersion(), updatedDeviceDescription.swVersion())
&& Objects.equal(device.serialNumber(), updatedDeviceDescription.serialNumber())
&& Objects.equal(device.chassisId(), updatedDeviceDescription.chassisId())
&& Objects.equal(device.annotations(), updatedDeviceDescription.annotations());
}
private void deviceRemoved(DeviceId deviceId) {
checkNotNull(deviceId, ISNOTNULL);
providerService.deviceDisconnected(deviceId);
controller.getProxiedDevices(deviceId).stream().forEach(device -> {
controller.removeProxiedDevice(device);
providerService.deviceDisconnected(device);
});
controller.removeDevice(deviceId);
}
//Method to connect devices provided via net-cfg under devices/ tree
private void createAndConnectDevices() {
Set<DeviceId> deviceSubjects =
cfgService.getSubjects(DeviceId.class, RestDeviceConfig.class);
connectDevices(deviceSubjects.stream()
.filter(deviceId -> deviceService.getDevice(deviceId) == null)
.map(deviceId -> {
RestDeviceConfig config =
cfgService.getConfig(deviceId, RestDeviceConfig.class);
RestSBDevice device = new DefaultRestSBDevice(config.ip(),
config.port(),
config.username(),
config.password(),
config.protocol(),
config.url(),
false, config.testUrl(),
config.manufacturer(),
config.hwVersion(),
config.swVersion());
return device;
}).collect(Collectors.toSet()));
}
//Old method to register devices provided via net-cfg under apps/rest/ tree
private void createDevices() {
RestProviderConfig cfg = cfgService.getConfig(appId, RestProviderConfig.class);
try {
if (cfg != null && cfg.getDevicesAddresses() != null) {
connectDevices(cfg.getDevicesAddresses());
}
} catch (ConfigException e) {
log.error("Configuration error {}", e);
}
log.debug("REST Devices {}", controller.getDevices());
addedDevices.clear();
}
private void connectDevices(Set<RestSBDevice> devices) {
//Precomputing the devices to be removed
Set<RestSBDevice> toBeRemoved = new HashSet<>(controller.getDevices().values());
toBeRemoved.removeAll(devices);
//Adding new devices
devices.stream()
.filter(device -> {
device.setActive(false);
controller.addDevice(device);
return testDeviceConnection(device);
})
.forEach(device -> {
deviceAdded(device);
});
//Removing devices not wanted anymore
toBeRemoved.forEach(device -> deviceRemoved(device.deviceId()));
}
private void discoverPorts(DeviceId deviceId) {
Device device = deviceService.getDevice(deviceId);
//TODO remove when PortDiscovery is removed from master
if (device.is(PortDiscovery.class)) {
PortDiscovery portConfig = device.as(PortDiscovery.class);
providerService.updatePorts(deviceId, portConfig.getPorts());
} else {
DeviceDescriptionDiscovery deviceDescriptionDiscovery =
device.as(DeviceDescriptionDiscovery.class);
providerService.updatePorts(deviceId, deviceDescriptionDiscovery.discoverPortDetails());
}
}
private boolean testDeviceConnection(RestSBDevice dev) {
try {
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 {}", dev, e);
}
return false;
}
private class InternalNetworkConfigListener implements NetworkConfigListener {
@Override
public void event(NetworkConfigEvent event) {
if (event.configClass().equals(RestDeviceConfig.class)) {
executor.execute(RestDeviceProvider.this::createAndConnectDevices);
} else {
log.warn("Injecting device via this Json is deprecated, " +
"please put configuration under devices/");
executor.execute(RestDeviceProvider.this::createDevices);
}
}
@Override
public boolean isRelevant(NetworkConfigEvent event) {
return (event.configClass().equals(RestDeviceConfig.class) ||
event.configClass().equals(RestProviderConfig.class)) &&
(event.type() == CONFIG_ADDED ||
event.type() == CONFIG_UPDATED);
}
}
@Override
public void changePortState(DeviceId deviceId, PortNumber portNumber,
boolean enable) {
// TODO if required
}
}