diff options
Diffstat (limited to 'framework/src/onos/providers/netconf/device')
6 files changed, 1314 insertions, 0 deletions
diff --git a/framework/src/onos/providers/netconf/device/pom.xml b/framework/src/onos/providers/netconf/device/pom.xml new file mode 100644 index 00000000..be05e3cd --- /dev/null +++ b/framework/src/onos/providers/netconf/device/pom.xml @@ -0,0 +1,165 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-netconf-providers</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onos-netconf-provider-device</artifactId> + <packaging>bundle</packaging> + + <description>ONOS Netconf protocol device provider</description> + + <dependencies> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.compendium</artifactId> + </dependency> + <dependency> + <groupId>ch.ethz.ganymed</groupId> + <artifactId>ganymed-ssh2</artifactId> + <version>262</version> + </dependency> + <dependency> + <!-- TODO: change this appropriately when the official TailF JNC is available --> + <groupId>org.onosproject</groupId> + <artifactId>jnc</artifactId> + <version>1.0</version> + </dependency> + <dependency> + <groupId>org.jdom</groupId> + <artifactId>jdom2</artifactId> + <version>2.0.5</version> + </dependency> + <dependency> + <groupId>jaxen</groupId> + <artifactId>jaxen</artifactId> + <version>1.1.4</version> + <optional>true</optional> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.core</artifactId> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onlab-junit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymock</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>2.3</version> + <configuration> + <filters> + <filter> + <artifact>com.tailf:JNC</artifact> + <includes> + <include>com/tailf/jnc/**</include> + </includes> + </filter> + <filter> + <artifact>ch.ethz.ganymed:ganymed-ssh2</artifact> + <includes> + <include>ch/ethz/ssh2/**</include> + </includes> + </filter> + <filter> + <artifact>org.jdom:jdom2</artifact> + <includes> + <include>org/jdom2/**</include> + </includes> + </filter> + </filters> + </configuration> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-scr-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <configuration> + <instructions> + <Export-Package> + com.tailf.jnc, + ch.ethz.ssh2, + ch.ethz.ssh2.auth, + ch.ethz.ssh2.channel, + ch.ethz.ssh2.crypto, + ch.ethz.ssh2.crypto.cipher, + ch.ethz.ssh2.crypto.dh, + ch.ethz.ssh2.crypto.digest, + ch.ethz.ssh2.log, + ch.ethz.ssh2.packets, + ch.ethz.ssh2.server, + ch.ethz.ssh2.sftp, + ch.ethz.ssh2.signature, + ch.ethz.ssh2.transport, + ch.ethz.ssh2.util, + org.jdom2, + org.jdom2.input, + org.jdom2.output, + org.jdom2.adapters, + org.jdom2.filter, + org.jdom2.internal, + org.jdom2.located, + org.jdom2.transform, + org.jdom2.util, + org.jdom2.xpath, + org.jdom2.input.sax, + org.jdom2.input.stax, + org.jdom2.output.support, + org.jdom2.xpath.jaxen, + org.jdom2.xpath.util + </Export-Package> + </instructions> + </configuration> + </plugin> + <plugin> + <groupId>org.onosproject</groupId> + <artifactId>onos-maven-plugin</artifactId> + </plugin> + </plugins> + </build> + +</project> diff --git a/framework/src/onos/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/NetconfDevice.java b/framework/src/onos/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/NetconfDevice.java new file mode 100644 index 00000000..b3d26b06 --- /dev/null +++ b/framework/src/onos/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/NetconfDevice.java @@ -0,0 +1,304 @@ +/* + * 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.Preconditions.checkNotNull; +import static org.onlab.util.Tools.delay; +import static org.slf4j.LoggerFactory.getLogger; + +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.List; + +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.input.SAXBuilder; +import org.jdom2.output.Format; +import org.jdom2.output.XMLOutputter; +import org.slf4j.Logger; + +import com.tailf.jnc.Capabilities; +import com.tailf.jnc.JNCException; +import com.tailf.jnc.SSHConnection; +import com.tailf.jnc.SSHSession; + +/** + * This is a logical representation of actual NETCONF device, carrying all the + * necessary information to connect and execute NETCONF operations. + */ +public class NetconfDevice { + private final Logger log = getLogger(NetconfDevice.class); + + /** + * The Device State is used to determine whether the device is active or + * inactive. This state infomation will help Device Creator to add or delete + * the device from the core. + */ + public static enum DeviceState { + /* Used to specify Active state of the device */ + ACTIVE, + /* Used to specify inactive state of the device */ + INACTIVE, + /* Used to specify invalid state of the device */ + INVALID + } + + private static final int DEFAULT_SSH_PORT = 22; + private static final int DEFAULT_CON_TIMEOUT = 0; + private static final String XML_CAPABILITY_KEY = "capability"; + private static final int EVENTINTERVAL = 2000; + private static final int CONNECTION_CHECK_INTERVAL = 3; + private static final String INPUT_HELLO_XML_MSG = new StringBuilder( + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>") + .append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">") + .append("<capabilities><capability>urn:ietf:params:netconf:base:1.0</capability>") + .append("</capabilities></hello>").toString(); + + private String sshHost; + private int sshPort = DEFAULT_SSH_PORT; + private int connectTimeout = DEFAULT_CON_TIMEOUT; + private String username; + private String password; + private boolean reachable = false; + + private List<String> capabilities = new ArrayList<String>(); + private SSHConnection sshConnection = null; + + private DeviceState deviceState = DeviceState.INVALID; + + protected NetconfDevice(String sshHost, int sshPort, String username, + String password) { + this.username = checkNotNull(username, + "Netconf Username Cannot be null"); + this.sshHost = checkNotNull(sshHost, "Netconf Device IP cannot be null"); + this.sshPort = checkNotNull(sshPort, + "Netconf Device SSH port cannot be null"); + this.password = password; + } + + /** + * This will try to connect to NETCONF device and find all the capabilities. + * + * @throws Exception if unable to connect to the device + */ + // FIXME: this should not be a generic Exception; perhaps wrap in some RuntimeException + public void init() throws Exception { + try { + if (sshConnection == null) { + sshConnection = new SSHConnection(sshHost, sshPort, connectTimeout); + sshConnection.authenticateWithPassword(username, password); + } + // Send hello message to retrieve capabilities. + } catch (IOException e) { + log.error("Fatal Error while creating connection to the device: " + + deviceInfo(), e); + throw e; + } catch (JNCException e) { + log.error("Failed to connect to the device: " + deviceInfo(), e); + throw e; + } + + hello(); + } + + private void hello() { + SSHSession ssh = null; + try { + ssh = new SSHSession(sshConnection); + String helloRequestXML = INPUT_HELLO_XML_MSG.trim(); + + log.debug("++++++++++++++++++++++++++++++++++Sending Hello: " + + sshConnection.getGanymedConnection().getHostname() + + "++++++++++++++++++++++++++++++++++"); + printPrettyXML(helloRequestXML); + ssh.print(helloRequestXML); + // ssh.print(endCharSeq); + ssh.flush(); + String xmlResponse = null; + int i = CONNECTION_CHECK_INTERVAL; + while (!ssh.ready() && i > 0) { + delay(EVENTINTERVAL); + i--; + } + + if (ssh.ready()) { + StringBuffer readOne = ssh.readOne(); + if (readOne == null) { + log.error("The Hello Contains No Capabilites"); + throw new JNCException( + JNCException.SESSION_ERROR, + "server does not support NETCONF base capability: " + + Capabilities.NETCONF_BASE_CAPABILITY); + } else { + xmlResponse = readOne.toString().trim(); + + log.debug("++++++++++++++++++++++++++++++++++Reading Capabilities: " + + sshConnection.getGanymedConnection() + .getHostname() + + "++++++++++++++++++++++++++++++++++"); + + printPrettyXML(xmlResponse); + processCapabilities(xmlResponse); + } + } + reachable = true; + } catch (IOException e) { + log.error("Fatal Error while sending Hello Message to the device: " + + deviceInfo(), e); + } catch (JNCException e) { + log.error("Fatal Error while sending Hello Message to the device: " + + deviceInfo(), e); + } finally { + log.debug("Closing the session after successful execution"); + if (ssh != null) { + ssh.close(); + } + } + } + + private void processCapabilities(String xmlResponse) throws JNCException { + if (xmlResponse.isEmpty()) { + log.error("The capability response cannot be empty"); + throw new JNCException( + JNCException.SESSION_ERROR, + "server does not support NETCONF base capability: " + + Capabilities.NETCONF_BASE_CAPABILITY); + } + try { + Document doc = new SAXBuilder() + .build(new StringReader(xmlResponse)); + Element rootElement = doc.getRootElement(); + processCapabilities(rootElement); + } catch (Exception e) { + log.error("ERROR while parsing the XML " + xmlResponse); + } + } + + private void processCapabilities(Element rootElement) { + List<Element> children = rootElement.getChildren(); + if (children.isEmpty()) { + return; + } + for (Element child : children) { + + if (child.getName().equals(XML_CAPABILITY_KEY)) { + capabilities.add(child.getValue()); + } + if (!child.getChildren().isEmpty()) { + processCapabilities(child); + } + } + } + + private void printPrettyXML(String xmlstring) { + try { + Document doc = new SAXBuilder().build(new StringReader(xmlstring)); + XMLOutputter xmOut = new XMLOutputter(Format.getPrettyFormat()); + String outputString = xmOut.outputString(doc); + log.debug(outputString); + } catch (Exception e) { + log.error("ERROR while parsing the XML " + xmlstring, e); + + } + } + + /** + * This would return host IP and host Port, used by this particular Netconf + * Device. + * @return Device Information. + */ + public String deviceInfo() { + return new StringBuilder("host: ").append(sshHost).append(". port: ") + .append(sshPort).toString(); + } + + /** + * This will terminate the device connection. + */ + public void disconnect() { + sshConnection.close(); + reachable = false; + } + + /** + * This will list down all the capabilities supported on the device. + * @return Capability list. + */ + public List<String> getCapabilities() { + return capabilities; + } + + /** + * This api is intended to know whether the device is connected or not. + * @return true if connected + */ + public boolean isReachable() { + return reachable; + } + + /** + * This will return the IP used connect ssh on the device. + * @return Netconf Device IP + */ + public String getSshHost() { + return sshHost; + } + + /** + * This will return the SSH Port used connect the device. + * @return SSH Port number + */ + public int getSshPort() { + return sshPort; + } + + /** + * The usename used to connect Netconf Device. + * @return Device Username + */ + public String getUsername() { + return username; + } + + /** + * Retrieve current state of the device. + * @return Current Device State + */ + public DeviceState getDeviceState() { + return deviceState; + } + + /** + * This is set the state information for the device. + * @param deviceState Next Device State + */ + public void setDeviceState(DeviceState deviceState) { + this.deviceState = deviceState; + } + + /** + * Check whether the device is in Active state. + * @return true if the device is Active + */ + public boolean isActive() { + return deviceState == DeviceState.ACTIVE ? true : false; + } + + public void setConnectTimeout(int connectTimeout) { + this.connectTimeout = connectTimeout; + } +} diff --git a/framework/src/onos/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceProvider.java b/framework/src/onos/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceProvider.java new file mode 100644 index 00000000..f9194a7e --- /dev/null +++ b/framework/src/onos/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceProvider.java @@ -0,0 +1,358 @@ +/* + * 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.io.IOException; +import java.net.SocketTimeoutException; +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.cfg.ComponentConfigService; +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 final Logger log = getLogger(NetconfDeviceProvider.class); + + protected 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; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ComponentConfigService cfgService; + + 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 = "Instance-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) { + cfgService.registerProperties(getClass()); + providerService = providerRegistry.register(this); + modified(context); + log.info("Started"); + } + + @Deactivate + public void deactivate(ComponentContext context) { + cfgService.unregisterProperties(getClass(), false); + try { + for (Entry<DeviceId, NetconfDevice> deviceEntry : netconfDeviceMap + .entrySet()) { + deviceBuilder.submit(new DeviceCreator(deviceEntry.getValue(), + false)); + } + deviceBuilder.awaitTermination(1000, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + log.error("Device builder did not terminate"); + } + deviceBuilder.shutdownNow(); + netconfDeviceMap.clear(); + providerRegistry.unregister(this); + providerService = null; + log.info("Stopped"); + } + + @Modified + public void modified(ComponentContext context) { + if (context == null) { + log.info("No configuration file"); + return; + } + Dictionary<?, ?> properties = context.getProperties(); + String deviceCfgValue = get(properties, "devConfigs"); + log.info("Settings: devConfigs={}", deviceCfgValue); + if (!isNullOrEmpty(deviceCfgValue)) { + addOrRemoveDevicesConfig(deviceCfgValue); + } + } + + private void addOrRemoveDevicesConfig(String deviceConfig) { + for (String deviceEntry : deviceConfig.split(",")) { + NetconfDevice device = processDeviceEntry(deviceEntry); + if (device != null) { + log.info("Device Detail: username: {}, host={}, port={}, state={}", + device.getUsername(), device.getSshHost(), + device.getSshPort(), device.getDeviceState().name()); + if (device.isActive()) { + deviceBuilder.submit(new DeviceCreator(device, true)); + } else { + deviceBuilder.submit(new DeviceCreator(device, false)); + } + } + } + } + + private NetconfDevice processDeviceEntry(String deviceEntry) { + if (deviceEntry == null) { + log.info("No content for Device Entry, so cannot proceed further."); + return null; + } + 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) { + 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) { + 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.INACTIVE); + } else { + log.warn("Device State Information can not be empty, so marking the state as INVALID"); + device.setDeviceState(DeviceState.INVALID); + } + } else { + log.warn("The device entry do not specify state information, so marking the state as INVALID"); + device.setDeviceState(DeviceState.INVALID); + } + } + } catch (ArrayIndexOutOfBoundsException aie) { + 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) { + 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) { + 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) { + log.info("Trying to create Device Info on ONOS core"); + advertiseDevices(); + } else { + 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 == null) { + log.warn("The Request Netconf Device is null, cannot proceed further"); + return; + } + try { + DeviceId did = getDeviceId(); + if (!netconfDeviceMap.containsKey(did)) { + log.error("BAD Request: 'Currently device is not discovered, " + + "so cannot remove/disconnect the device: " + + device.deviceInfo() + "'"); + return; + } + providerService.deviceDisconnected(did); + device.disconnect(); + netconfDeviceMap.remove(did); + delay(EVENTINTERVAL); + } catch (URISyntaxException uriSyntaxExcpetion) { + 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) { + 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); + log.info("Persisting Device" + did.uri().toString()); + + netconfDeviceMap.put(did, device); + providerService.deviceConnected(did, desc); + log.info("Done with Device Info Creation on ONOS core. Device Info: " + + device.deviceInfo() + " " + did.uri().toString()); + delay(EVENTINTERVAL); + } catch (URISyntaxException e) { + log.error("Syntax Error while creating URI for the device: " + + device.deviceInfo() + + " couldn't persist the device onto the store", e); + } catch (SocketTimeoutException e) { + log.error("Error while setting connection for the device: " + + device.deviceInfo(), e); + } catch (IOException e) { + log.error("Error while setting connection for the device: " + + device.deviceInfo(), e); + } catch (Exception e) { + log.error("Error while initializing session for the device: " + + (device != null ? device.deviceInfo() : null), 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; + } + } +} diff --git a/framework/src/onos/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/package-info.java b/framework/src/onos/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/package-info.java new file mode 100644 index 00000000..583aeaa2 --- /dev/null +++ b/framework/src/onos/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Provider that uses Netconf capability request as a means of infrastructure device discovery. + */ +package org.onosproject.provider.netconf.device.impl;
\ No newline at end of file diff --git a/framework/src/onos/providers/netconf/device/src/test/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceProviderTest.java b/framework/src/onos/providers/netconf/device/src/test/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceProviderTest.java new file mode 100644 index 00000000..e56c5959 --- /dev/null +++ b/framework/src/onos/providers/netconf/device/src/test/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceProviderTest.java @@ -0,0 +1,421 @@ +/* + * 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 org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.junit.Assert.assertFalse; +import static org.onlab.util.Tools.delay; +import static org.onosproject.provider.netconf.device.impl.NetconfDeviceProviderTestConstant.*; +import static org.slf4j.LoggerFactory.getLogger; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Collection; +import java.util.Dictionary; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.easymock.EasyMock; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.onlab.packet.ChassisId; +import org.onosproject.cfg.ComponentConfigService; +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.PortDescription; +import org.onosproject.net.device.PortStatistics; +import org.onosproject.net.provider.ProviderId; +import org.osgi.service.component.ComponentContext; +import org.slf4j.Logger; + +import com.tailf.jnc.JNCException; + +/** + * Test Case to Validate Netconf Device Provider. + */ +public class NetconfDeviceProviderTest { + TestDeviceCreator create; + + private final Logger log = getLogger(NetconfDeviceProviderTest.class); + + private Map<DeviceId, NetconfDevice> netconfDeviceMap = new ConcurrentHashMap<DeviceId, NetconfDevice>(); + + private DeviceProviderService providerService; + + private static final DeviceId DID1 = DeviceId.deviceId(DEVICE_ID); + + private final NetconfDeviceProvider provider = new NetconfDeviceProvider(); + private final TestDeviceRegistry registry = new TestDeviceRegistry(); + + private ComponentConfigService mockCfgService; + + @Before + public void setUp() { + mockCfgService = EasyMock.createMock(ComponentConfigService.class); + provider.cfgService = mockCfgService; + provider.providerRegistry = registry; + } + + @SuppressWarnings("unchecked") + private Dictionary<String, String> getDictionaryMockWithoutValues(ComponentContext componentContext) { + Dictionary<String, String> dictionary = EasyMock + .createMock(Dictionary.class); + expect(dictionary.get(DEV_CONFIG)).andReturn(NULL); + replay(dictionary); + expect(componentContext.getProperties()).andReturn(dictionary); + return dictionary; + } + + @SuppressWarnings("unchecked") + private Dictionary<String, String> getDictionaryMockWithDeviceEntryNull(ComponentContext componentContext) { + Dictionary<String, String> dictionary = EasyMock + .createMock(Dictionary.class); + expect(dictionary.get(DEV_CONFIG)).andReturn(NULL_NULL); + replay(dictionary); + expect(componentContext.getProperties()).andReturn(dictionary); + return dictionary; + } + + @SuppressWarnings("unchecked") + private Dictionary<String, String> getDictionaryMockDeviceEntryNumberFomatEx(ComponentContext componentContext) { + Dictionary<String, String> dictionary = EasyMock + .createMock(Dictionary.class); + expect(dictionary.get(DEV_CONFIG)) + .andReturn(CONFIG_WITH_INVALID_ENTRY_NUMBER) + .andThrow(new NumberFormatException()); + replay(dictionary); + expect(componentContext.getProperties()).andReturn(dictionary); + return dictionary; + } + + @SuppressWarnings("unchecked") + private Dictionary<String, String> getDictionaryMockWithoutUsernameAndPassword(ComponentContext componentContext) { + Dictionary<String, String> dictionary = EasyMock + .createMock(Dictionary.class); + expect(dictionary.get(DEV_CONFIG)).andReturn(CONFIG_WITH_NULL_ENTRY); + replay(dictionary); + expect(componentContext.getProperties()).andReturn(dictionary); + return dictionary; + } + + @SuppressWarnings("unchecked") + private Dictionary<String, String> getDictionaryMockWithDifferentDeviceState(ComponentContext componentContext) { + Dictionary<String, String> dictionary = EasyMock + .createMock(Dictionary.class); + expect(dictionary.get(DEV_CONFIG)) + .andReturn(CONFIG_WITH_DIFFERENT_DEVICE_STATE); + replay(dictionary); + expect(componentContext.getProperties()).andReturn(dictionary); + return dictionary; + } + + @SuppressWarnings("unchecked") + private Dictionary<String, String> getDictionaryMockDeviceWithArrayOutOFBoundEx(ComponentContext componentContext) { + Dictionary<String, String> dictionary = EasyMock + .createMock(Dictionary.class); + expect(dictionary.get(DEV_CONFIG)) + .andReturn(CONFIG_WITH_ARRAY_OUT_OF_BOUNDEX) + .andThrow(new ArrayIndexOutOfBoundsException()); + replay(dictionary); + expect(componentContext.getProperties()).andReturn(dictionary); + return dictionary; + } + + @SuppressWarnings("unchecked") + private Dictionary<String, String> getDictionaryMockDeviceEntryForDeactivate(ComponentContext componentContext) { + Dictionary<String, String> dictionary = EasyMock + .createMock(Dictionary.class); + expect(dictionary.get(DEV_CONFIG)) + .andReturn(CONFIG_ENTRY_FOR_DEACTIVATE) + .andThrow(new ArrayIndexOutOfBoundsException()); + replay(dictionary); + expect(componentContext.getProperties()).andReturn(dictionary); + return dictionary; + } + + @Ignore + @Test(expected = IOException.class) + public void testSSHAuthentication() throws IOException, JNCException { + TestDeviceCreator objForTestDev = new TestDeviceCreator( + new NetconfDevice( + DEVICE_IP, + DEVICE_PORT, + DEVICE_USERNAME, + DEVICE_PASSWORD), + true); + objForTestDev.run(); + } + + @After + public void tearDown() { + provider.providerRegistry = null; + provider.cfgService = null; + } + + // To check if deviceCfgValue is empty or null + @Test + public void testActiveWithcomponentContextIsNull() { + + ComponentContext componentContext = EasyMock + .createMock(ComponentContext.class); + getDictionaryMockWithoutValues(componentContext); + replay(componentContext); + provider.activate(componentContext); + } + + // To check deviceEntry and device is null + @Test + public void testActiveWithDeviceEntryIsNull() { + + ComponentContext componentContext = EasyMock + .createMock(ComponentContext.class); + getDictionaryMockWithDeviceEntryNull(componentContext); + replay(componentContext); + provider.activate(componentContext); + } + + @Test + public void testActiveWithDeviceEntryWithoutUsernameAndPassword() { + + ComponentContext componentContext = EasyMock + .createMock(ComponentContext.class); + getDictionaryMockWithoutUsernameAndPassword(componentContext); + replay(componentContext); + provider.activate(componentContext); + } + + @Test + public void testActiveWithDeviceEntryWithNumberFomatEx() { + + ComponentContext componentContext = EasyMock + .createMock(ComponentContext.class); + getDictionaryMockDeviceEntryNumberFomatEx(componentContext); + replay(componentContext); + provider.activate(componentContext); + } + + @Test + public void testActiveWithDeviceEntryWithDifferentDeviceState() { + + ComponentContext componentContext = EasyMock + .createMock(ComponentContext.class); + getDictionaryMockWithDifferentDeviceState(componentContext); + replay(componentContext); + provider.activate(componentContext); + } + + @Test + public void testActiveWithDeviceEntryWithArrayOutOFBoundEx() { + + ComponentContext componentContext = EasyMock + .createMock(ComponentContext.class); + getDictionaryMockDeviceWithArrayOutOFBoundEx(componentContext); + replay(componentContext); + provider.activate(componentContext); + } + + @Test + public void isReachableWithInvalidDeviceId() { + assertFalse("Initially the Device ID Should not be reachable", + provider.isReachable(DID1)); + NetconfDevice device = new NetconfDevice(NULL, ZERO, NULL, NULL); + provider.netconfDeviceMap.put(DID1, device); + assertFalse("Particular Device ID cannot be Reachable", + provider.isReachable(DID1)); + } + + @Test + public void testDeactivate() { + + ComponentContext componentContext = EasyMock + .createMock(ComponentContext.class); + getDictionaryMockDeviceEntryForDeactivate(componentContext); + replay(componentContext); + testActiveWithDeviceEntryWithDifferentDeviceState(); + provider.deactivate(componentContext); + } + + private class TestDeviceCreator { + + private NetconfDevice device; + private boolean createFlag; + + public TestDeviceCreator(NetconfDevice device, boolean createFlag) { + this.device = device; + this.createFlag = createFlag; + } + + public void run() throws JNCException, IOException { + if (createFlag) { + log.info("Trying to create Device Info on ONOS core"); + advertiseDevices(); + } else { + 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 == null) { + log.warn("The Request Netconf Device is null, cannot proceed further"); + return; + } + try { + DeviceId did = getDeviceId(); + if (!netconfDeviceMap.containsKey(did)) { + log.error("BAD Request: 'Currently device is not discovered, " + + "so cannot remove/disconnect the device: " + + device.deviceInfo() + "'"); + return; + } + providerService.deviceDisconnected(did); + device.disconnect(); + netconfDeviceMap.remove(did); + delay(EVENTINTERVAL); + } catch (URISyntaxException uriSyntaxExcpetion) { + 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() throws JNCException, IOException { + try { + if (device == null) { + 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, + NULL, + NULL, + NULL, + NULL, cid); + log.info("Persisting Device" + did.uri().toString()); + + netconfDeviceMap.put(did, device); + providerService.deviceConnected(did, desc); + log.info("Done with Device Info Creation on ONOS core. Device Info: " + + device.deviceInfo() + " " + did.uri().toString()); + delay(EVENTINTERVAL); + } catch (URISyntaxException e) { + log.error("Syntax Error while creating URI for the device: " + + device.deviceInfo() + + " couldn't persist the device onto the store", e); + } catch (JNCException e) { + throw e; + } catch (IOException e) { + throw e; + } catch (Exception e) { + log.error("Error while initializing session for the device: " + + device.deviceInfo(), e); + } + } + + private DeviceId getDeviceId() throws URISyntaxException { + String additionalSSP = new StringBuilder(device.getUsername()) + .append(AT_THE_RATE).append(device.getSshHost()) + .append(COLON).append(device.getSshPort()).toString(); + DeviceId did = DeviceId.deviceId(new URI(SCHEME_NETCONF, + additionalSSP, null)); + return did; + } + } + + private class TestDeviceRegistry implements DeviceProviderRegistry { + + @Override + public DeviceProviderService register(DeviceProvider provider) { + return new TestProviderService(); + } + + @Override + public void unregister(DeviceProvider provider) { + } + + @Override + public Set<ProviderId> getProviders() { + return null; + } + + private class TestProviderService implements DeviceProviderService { + + @Override + public DeviceProvider provider() { + return null; + } + + @Override + public void deviceConnected(DeviceId deviceId, + DeviceDescription deviceDescription) { + } + + @Override + public void deviceDisconnected(DeviceId deviceId) { + + } + + @Override + public void updatePorts(DeviceId deviceId, + List<PortDescription> portDescriptions) { + + } + + @Override + public void portStatusChanged(DeviceId deviceId, + PortDescription portDescription) { + + } + + @Override + public void receivedRoleReply(DeviceId deviceId, + MastershipRole requested, + MastershipRole response) { + + } + + @Override + public void updatePortStatistics(DeviceId deviceId, + Collection<PortStatistics> portStatistics) { + + } + } + } +} diff --git a/framework/src/onos/providers/netconf/device/src/test/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceProviderTestConstant.java b/framework/src/onos/providers/netconf/device/src/test/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceProviderTestConstant.java new file mode 100644 index 00000000..1d848e26 --- /dev/null +++ b/framework/src/onos/providers/netconf/device/src/test/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceProviderTestConstant.java @@ -0,0 +1,46 @@ +/* + * 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; + +public final class NetconfDeviceProviderTestConstant { + + private NetconfDeviceProviderTestConstant() { + } + + public static final int ZERO = 0; + public static final int EVENTINTERVAL = 5; + public static final String DEV_CONFIG = "devConfigs"; + public static final String CONFIG_WITH_INVALID_ENTRY_NUMBER = "cisco:cisco" + + "@10.18.11.14:cisco:active"; + public static final String CONFIG_WITH_NULL_ENTRY = "null:null@null:0:active"; + public static final String CONFIG_WITH_DIFFERENT_DEVICE_STATE = "cisco:cisco@10.18.11.14:22:active," + + "cisco:cisco@10.18.11.18:22:inactive,cisco:cisco@10.18.11.14:22:invalid," + + "cisco:cisco@10.18.11.14:22:null"; + public static final String CONFIG_WITH_ARRAY_OUT_OF_BOUNDEX = "@10.18.11.14:22:active"; + public static final String CONFIG_ENTRY_FOR_DEACTIVATE = "netconf:cisco" + + "@10.18.11.14:22:active"; + public static final String DEVICE_IP = "10.18.14.19"; + public static final int DEVICE_PORT = 22; + public static final String DEVICE_USERNAME = "cisco"; + public static final String DEVICE_PASSWORD = "cisco"; + public static final String AT_THE_RATE = "@"; + public static final String COLON = ":"; + public static final String NULL = ""; + public static final String NULL_NULL = "null,null"; + public static final String SCHEME_NETCONF = "netconf"; + public static final String DEVICE_ID = "of:0000000000000001"; + +} |