| /* |
| * Copyright 2015 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 static com.google.common.base.Strings.isNullOrEmpty; |
| import static org.onlab.util.Tools.delay; |
| import static org.onlab.util.Tools.get; |
| import static org.onlab.util.Tools.groupedThreads; |
| import static org.slf4j.LoggerFactory.getLogger; |
| |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.util.Dictionary; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.TimeUnit; |
| |
| 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.Modified; |
| import org.apache.felix.scr.annotations.Property; |
| import org.apache.felix.scr.annotations.Reference; |
| import org.apache.felix.scr.annotations.ReferenceCardinality; |
| import org.onlab.packet.ChassisId; |
| import org.onosproject.cluster.ClusterService; |
| import org.onosproject.net.Device; |
| import org.onosproject.net.DeviceId; |
| import org.onosproject.net.MastershipRole; |
| import org.onosproject.net.device.DefaultDeviceDescription; |
| import org.onosproject.net.device.DeviceDescription; |
| 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.provider.AbstractProvider; |
| import org.onosproject.net.provider.ProviderId; |
| import org.onosproject.provider.netconf.device.impl.NetconfDevice.DeviceState; |
| import org.osgi.service.component.ComponentContext; |
| import org.slf4j.Logger; |
| |
| /** |
| * Provider which will try to fetch the details of NETCONF devices from the core |
| * and run a capability discovery on each of the device. |
| */ |
| @Component(immediate = true) |
| public class NetconfDeviceProvider extends AbstractProvider |
| implements DeviceProvider { |
| |
| private static final Logger log = getLogger(NetconfDeviceProvider.class); |
| |
| private Map<DeviceId, NetconfDevice> netconfDeviceMap = new ConcurrentHashMap<DeviceId, NetconfDevice>(); |
| |
| private DeviceProviderService providerService; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected DeviceProviderRegistry providerRegistry; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected DeviceService deviceService; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected ClusterService clusterService; |
| |
| private ExecutorService deviceBuilder = Executors |
| .newFixedThreadPool(1, |
| groupedThreads("onos/netconf", "device-creator")); |
| |
| // Delay between events in ms. |
| private static final int EVENTINTERVAL = 5; |
| |
| private static final String SCHEME = "netconf"; |
| |
| @Property(name = "devConfigs", value = "", label = "Instance-specific configurations") |
| private String devConfigs = null; |
| |
| @Property(name = "devPasswords", value = "", label = "Instace-specific password") |
| private String devPasswords = null; |
| |
| /** |
| * Creates a provider with the supplier identifier. |
| */ |
| public NetconfDeviceProvider() { |
| super(new ProviderId("netconf", "org.onosproject.provider.netconf")); |
| } |
| |
| @Activate |
| public void activate(ComponentContext context) { |
| NetconfDeviceProvider.log.info("Netconf Device Provider Started"); |
| providerService = providerRegistry.register(this); |
| modified(context); |
| } |
| |
| @Deactivate |
| public void deactivate(ComponentContext context) { |
| try { |
| for (Entry<DeviceId, NetconfDevice> deviceEntry : netconfDeviceMap |
| .entrySet()) { |
| deviceBuilder.submit(new DeviceCreator(deviceEntry.getValue(), |
| false)); |
| } |
| deviceBuilder.awaitTermination(1000, TimeUnit.MILLISECONDS); |
| } catch (InterruptedException e) { |
| NetconfDeviceProvider.log.error("Device builder did not terminate"); |
| } |
| deviceBuilder.shutdownNow(); |
| netconfDeviceMap.clear(); |
| providerRegistry.unregister(this); |
| providerService = null; |
| NetconfDeviceProvider.log.info("Stopped"); |
| } |
| |
| @Modified |
| public void modified(ComponentContext context) { |
| if (context == null) { |
| NetconfDeviceProvider.log.info("No configuration file"); |
| return; |
| } |
| Dictionary<?, ?> properties = context.getProperties(); |
| String deviceCfgValue = get(properties, "devConfigs"); |
| NetconfDeviceProvider.log |
| .info("Getting Device configuration from cfg file: " |
| + deviceCfgValue); |
| if (!isNullOrEmpty(deviceCfgValue)) { |
| addOrRemoveDevicesConfig(deviceCfgValue); |
| } else { |
| NetconfDeviceProvider.log |
| .info("Device Configuration value receiviced from the property 'devConfigs': " |
| + deviceCfgValue + ", is not valid"); |
| } |
| } |
| |
| private void addOrRemoveDevicesConfig(String deviceConfig) { |
| for (String deviceEntry : deviceConfig.split(",")) { |
| NetconfDevice device = processDeviceEntry(deviceEntry); |
| if (device != null) { |
| NetconfDeviceProvider.log.info("Device Detail: " + "username: " |
| + device.getUsername() + ", host: " |
| + device.getSshHost() + ", port: " |
| + device.getSshPort()); |
| if (device.isActive()) { |
| deviceBuilder.submit(new DeviceCreator(device, true)); |
| } else { |
| deviceBuilder.submit(new DeviceCreator(device, false)); |
| } |
| } |
| } |
| } |
| |
| private NetconfDevice processDeviceEntry(String deviceEntry) { |
| if (deviceEntry == null) { |
| NetconfDeviceProvider.log |
| .info("No content for Device Entry, so cannot proceed further."); |
| return null; |
| } |
| NetconfDeviceProvider.log |
| .info("Trying to convert Device Entry String: " + deviceEntry |
| + " to a Netconf Device Object"); |
| NetconfDevice device = null; |
| try { |
| String userInfo = deviceEntry.substring(0, deviceEntry |
| .lastIndexOf('@')); |
| String hostInfo = deviceEntry.substring(deviceEntry |
| .lastIndexOf('@') + 1); |
| String[] infoSplit = userInfo.split(":"); |
| String username = infoSplit[0]; |
| String password = infoSplit[1]; |
| infoSplit = hostInfo.split(":"); |
| String hostIp = infoSplit[0]; |
| Integer hostPort; |
| try { |
| hostPort = Integer.parseInt(infoSplit[1]); |
| } catch (NumberFormatException nfe) { |
| NetconfDeviceProvider.log |
| .error("Bad Configuration Data: Failed to parse host port number string: " |
| + infoSplit[1]); |
| throw nfe; |
| } |
| String deviceState = infoSplit[2]; |
| if (isNullOrEmpty(username) || isNullOrEmpty(password) |
| || isNullOrEmpty(hostIp) || hostPort == 0) { |
| NetconfDeviceProvider.log |
| .warn("Bad Configuration Data: both user and device information parts of Configuration " |
| + deviceEntry + " should be non-nullable"); |
| } else { |
| device = new NetconfDevice(hostIp, hostPort, username, password); |
| if (!isNullOrEmpty(deviceState)) { |
| if (deviceState.toUpperCase().equals(DeviceState.ACTIVE |
| .name())) { |
| device.setDeviceState(DeviceState.ACTIVE); |
| } else if (deviceState.toUpperCase() |
| .equals(DeviceState.INACTIVE.name())) { |
| device.setDeviceState(DeviceState.ACTIVE); |
| } else { |
| NetconfDeviceProvider.log |
| .warn("Device State Information can not be empty, so marking the state as INVALID"); |
| device.setDeviceState(DeviceState.INVALID); |
| } |
| } else { |
| NetconfDeviceProvider.log |
| .warn("The device entry do not specify state information, so marking the state as INVALID"); |
| device.setDeviceState(DeviceState.INVALID); |
| } |
| } |
| } catch (ArrayIndexOutOfBoundsException aie) { |
| NetconfDeviceProvider.log |
| .error("Error while reading config infromation from the config file: " |
| + "The user, host and device state infomation should be " |
| + "in the order 'userInfo@hostInfo:deviceState'" |
| + deviceEntry, aie); |
| } catch (Exception e) { |
| NetconfDeviceProvider.log |
| .error("Error while parsing config information for the device entry: " |
| + deviceEntry, e); |
| } |
| return device; |
| } |
| |
| @Override |
| public void triggerProbe(DeviceId deviceId) { |
| // TODO Auto-generated method stub |
| } |
| |
| @Override |
| public void roleChanged(DeviceId deviceId, MastershipRole newRole) { |
| |
| } |
| |
| @Override |
| public boolean isReachable(DeviceId deviceId) { |
| NetconfDevice netconfDevice = netconfDeviceMap.get(deviceId); |
| if (netconfDevice == null) { |
| NetconfDeviceProvider.log |
| .warn("BAD REQUEST: the requested device id: " |
| + deviceId.toString() |
| + " is not associated to any NETCONF Device"); |
| return false; |
| } |
| return netconfDevice.isReachable(); |
| } |
| |
| /** |
| * This class is intended to add or remove Configured Netconf Devices. |
| * Functionality relies on 'createFlag' and 'NetconfDevice' content. The |
| * functionality runs as a thread and dependening on the 'createFlag' value |
| * it will create or remove Device entry from the core. |
| */ |
| private class DeviceCreator implements Runnable { |
| |
| private NetconfDevice device; |
| private boolean createFlag; |
| |
| public DeviceCreator(NetconfDevice device, boolean createFlag) { |
| this.device = device; |
| this.createFlag = createFlag; |
| } |
| |
| @Override |
| public void run() { |
| if (createFlag && (device.getDeviceState() == DeviceState.ACTIVE)) { |
| NetconfDeviceProvider.log |
| .info("Trying to create Device Info on ONOS core"); |
| advertiseDevices(); |
| } else { |
| NetconfDeviceProvider.log |
| .info("Trying to remove Device Info on ONOS core"); |
| removeDevices(); |
| } |
| } |
| |
| /** |
| * For each Netconf Device, remove the entry from the device store. |
| */ |
| private void removeDevices() { |
| if (!device.isReachable()) { |
| log.error("BAD Request: 'Currently device is not discovered, so cannot remove/disconnect the device: " |
| + device.deviceInfo() + "'"); |
| return; |
| } |
| try { |
| DeviceId did = getDeviceId(); |
| providerService.deviceDisconnected(did); |
| device.disconnect(); |
| delay(EVENTINTERVAL); |
| } catch (URISyntaxException uriSyntaxExcpetion) { |
| NetconfDeviceProvider.log |
| .error("Syntax Error while creating URI for the device: " |
| + device.deviceInfo() |
| + " couldn't remove the device from the store", |
| uriSyntaxExcpetion); |
| } |
| } |
| |
| /** |
| * Initialize Netconf Device object, and notify core saying device |
| * connected. |
| */ |
| private void advertiseDevices() { |
| try { |
| if (device == null) { |
| NetconfDeviceProvider.log |
| .warn("The Request Netconf Device is null, cannot proceed further"); |
| return; |
| } |
| device.init(); |
| DeviceId did = getDeviceId(); |
| ChassisId cid = new ChassisId(); |
| DeviceDescription desc = new DefaultDeviceDescription( |
| did.uri(), |
| Device.Type.OTHER, |
| "", "", |
| "", "", |
| cid); |
| if (NetconfDeviceProvider.log.isDebugEnabled()) { |
| NetconfDeviceProvider.log.debug("Persisting Device" |
| + did.uri().toString()); |
| } |
| |
| netconfDeviceMap.put(did, device); |
| providerService.deviceConnected(did, desc); |
| if (NetconfDeviceProvider.log.isDebugEnabled()) { |
| NetconfDeviceProvider.log |
| .debug("Done with Device Info Creation on ONOS core. Device Info: " |
| + device.deviceInfo() |
| + " " |
| + did.uri().toString()); |
| } |
| delay(EVENTINTERVAL); |
| } catch (URISyntaxException e) { |
| NetconfDeviceProvider.log |
| .error("Syntax Error while creating URI for the device: " |
| + device.deviceInfo() |
| + " couldn't persist the device onto the store", |
| e); |
| } catch (Exception e) { |
| NetconfDeviceProvider.log |
| .error("Error while initializing session for the device: " |
| + device.deviceInfo(), e); |
| } |
| } |
| |
| /** |
| * This will build a device id for the device. |
| */ |
| private DeviceId getDeviceId() throws URISyntaxException { |
| String additionalSSP = new StringBuilder(device.getUsername()) |
| .append("@").append(device.getSshHost()).append(":") |
| .append(device.getSshPort()).toString(); |
| DeviceId did = DeviceId.deviceId(new URI(SCHEME, additionalSSP, |
| null)); |
| return did; |
| } |
| } |
| } |