andrea
Committed by Gerrit Code Review

[ONOS-3253/3144] Insert support for Netconf device configuration, set and get controllers commands

Change-Id: I99188aa18207b9d0b0d935b9f9e61e547f4ddab1
Showing 33 changed files with 1812 additions and 1161 deletions
......@@ -59,8 +59,11 @@ public class DeviceSetControllersCommand extends AbstractShellCommand {
ControllerConfig config = h.behaviour(ControllerConfig.class);
print("before:");
config.getControllers().forEach(c -> print(c.target()));
try {
config.setControllers(newControllers);
} catch (NullPointerException e) {
print("No Device with requested parameters {} ", uri);
}
print("after:");
config.getControllers().forEach(c -> print(c.target()));
print("size %d", config.getControllers().size());
......
......@@ -24,5 +24,7 @@
<bundle>mvn:${project.groupId}/onos-ovsdb-api/${project.version}</bundle>
<bundle>mvn:${project.groupId}/onos-ovsdb-rfc/${project.version}</bundle>
<bundle>mvn:${project.groupId}/onos-netconf-api/${project.version}</bundle>
</feature>
</features>
......
......@@ -67,6 +67,11 @@
<artifactId>easymock</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-netconf-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
......
/*
* 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.driver.netconf;
import com.google.common.base.Preconditions;
import org.onosproject.net.DeviceId;
import org.onosproject.net.behaviour.ControllerConfig;
import org.onosproject.net.behaviour.ControllerInfo;
import org.onosproject.net.driver.AbstractHandlerBehaviour;
import org.onosproject.net.driver.DriverHandler;
import org.onosproject.netconf.NetconfController;
import org.onosproject.netconf.NetconfDevice;
import org.slf4j.Logger;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Implementation of controller config which allows to get and set controllers
* through the Netconf protocol.
*/
public class NetconfControllerConfig extends AbstractHandlerBehaviour
implements ControllerConfig {
private final Logger log = getLogger(NetconfControllerConfig.class);
@Override
public List<ControllerInfo> getControllers() {
DriverHandler handler = handler();
NetconfController controller = handler.get(NetconfController.class);
DeviceId ofDeviceId = handler.data().deviceId();
Preconditions.checkNotNull(controller, "Netconf controller is null");
List<ControllerInfo> controllers = new ArrayList<>();
controllers.addAll(XmlConfigParser.parseStreamControllers(XmlConfigParser.
loadXml(new ByteArrayInputStream(controller.
getDevicesMap().get(ofDeviceId).getSession().
getConfig("running").getBytes(StandardCharsets.UTF_8)))));
return controllers;
}
@Override
public void setControllers(List<ControllerInfo> controllers) {
DriverHandler handler = handler();
NetconfController controller = handler.get(NetconfController.class);
DeviceId deviceId = handler.data().deviceId();
Preconditions.checkNotNull(controller, "Netconf controller is null");
try {
NetconfDevice device = controller.getNetconfDevice(deviceId);
log.warn("provider map {}", controller.getDevicesMap());
String config = XmlConfigParser.createControllersConfig(
XmlConfigParser.loadXml(getClass().getResourceAsStream("controllers.xml")),
XmlConfigParser.loadXml(
new ByteArrayInputStream(device.getSession()
.getConfig("running")
.getBytes(
StandardCharsets.UTF_8))),
"running", "merge", "create", controllers
);
device.getSession().editConfig(config.substring(config.indexOf("-->") + 3));
} catch (NullPointerException e) {
log.warn("No NETCONF device with requested parameters " + e);
throw new NullPointerException("No NETCONF device with requested parameters " + e);
}
}
//TODO maybe put method getNetconfClientService like in ovsdb if we need it
}
/*
* 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.driver.netconf;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.HierarchicalConfiguration;
import org.apache.commons.configuration.XMLConfiguration;
import org.apache.commons.configuration.tree.ConfigurationNode;
import org.onlab.packet.IpAddress;
import org.onosproject.net.behaviour.ControllerInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
/**
* Parser for Netconf XML configurations and replys.
*/
final class XmlConfigParser {
public static final Logger log = LoggerFactory
.getLogger(XmlConfigParser.class);
private XmlConfigParser() {
//not called, preventing any allocation
}
protected static HierarchicalConfiguration loadXml(InputStream xmlStream) {
XMLConfiguration cfg = new XMLConfiguration();
try {
cfg.load(xmlStream);
return cfg;
} catch (ConfigurationException e) {
throw new IllegalArgumentException("Cannot load xml from Stream", e);
}
}
protected static List<ControllerInfo> parseStreamControllers(HierarchicalConfiguration cfg) {
List<ControllerInfo> controllers = new ArrayList<>();
List<HierarchicalConfiguration> fields =
cfg.configurationsAt("data.capable-switch." +
"logical-switches." +
"switch.controllers.controller");
for (HierarchicalConfiguration sub : fields) {
controllers.add(new ControllerInfo(
IpAddress.valueOf(sub.getString("ip-address")),
Integer.parseInt(sub.getString("port")),
sub.getString("protocol")));
}
return controllers;
}
protected static String parseSwitchId(HierarchicalConfiguration cfg) {
HierarchicalConfiguration field =
cfg.configurationAt("data.capable-switch." +
"logical-switches." +
"switch");
return field.getProperty("id").toString();
}
protected static String parseCapableSwitchId(HierarchicalConfiguration cfg) {
HierarchicalConfiguration field =
cfg.configurationAt("data.capable-switch");
return field.getProperty("id").toString();
}
protected static String createControllersConfig(HierarchicalConfiguration cfg,
HierarchicalConfiguration actualCfg,
String target, String netconfOperation,
String controllerOperation,
List<ControllerInfo> controllers) {
//cfg.getKeys().forEachRemaining(key -> System.out.println(key));
cfg.setProperty("edit-config.target", target);
cfg.setProperty("edit-config.default-operation", netconfOperation);
cfg.setProperty("edit-config.config.capable-switch.id",
parseCapableSwitchId(actualCfg));
cfg.setProperty("edit-config.config.capable-switch." +
"logical-switches.switch.id", parseSwitchId(actualCfg));
List<ConfigurationNode> newControllers = new ArrayList<>();
for (ControllerInfo ci : controllers) {
XMLConfiguration controller = new XMLConfiguration();
controller.setRoot(new HierarchicalConfiguration.Node("controller"));
String id = ci.type() + ":" + ci.ip() + ":" + ci.port();
controller.setProperty("id", id);
controller.setProperty("ip-address", ci.ip());
controller.setProperty("port", ci.port());
controller.setProperty("protocol", ci.type());
newControllers.add(controller.getRootNode());
}
cfg.addNodes("edit-config.config.capable-switch.logical-switches." +
"switch.controllers", newControllers);
XMLConfiguration editcfg = (XMLConfiguration) cfg;
StringWriter stringWriter = new StringWriter();
try {
editcfg.save(stringWriter);
} catch (ConfigurationException e) {
e.printStackTrace();
}
String s = stringWriter.toString()
.replaceAll("<controller>",
"<controller nc:operation=\"" + controllerOperation + "\">");
s = s.replace("<target>" + target + "</target>",
"<target><" + target + "/></target>");
return s;
}
//TODO implement mor methods for parsing configuration when you need them
}
/*
* 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.
*/
/**
* Implementations of the Netconf driver behaviours.
*/
package org.onosproject.driver.netconf;
\ No newline at end of file
......@@ -37,6 +37,13 @@
<behaviour api="org.onosproject.net.behaviour.ExtensionResolver"
impl="org.onosproject.driver.extensions.NiciraExtensionInterpreter" />
</driver>
<driver name="ovs-netconf" extends="default"
manufacturer="Nicira, Inc\." hwVersion="Open vSwitch" swVersion="2\..*">
<behaviour api="org.onosproject.openflow.controller.driver.OpenFlowSwitchDriver"
impl="org.onosproject.driver.handshaker.NiciraSwitchHandshaker"/>
<behaviour api="org.onosproject.net.behaviour.ControllerConfig"
impl="org.onosproject.driver.netconf.NetconfControllerConfig"/>
</driver>
<driver name="ovs-corsa" extends="ovs"
manufacturer="Corsa" hwVersion="emulation" swVersion="0.0.0">
<behaviour api="org.onosproject.net.behaviour.Pipeliner"
......
<!--
~ 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.
-->
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<edit-config>
<target>
</target>
<default-operation>
</default-operation>
<config>
<capable-switch xmlns="urn:onf:config:yang">
<id></id>
<logical-switches>
<switch>
<id></id>
<controllers>
</controllers>
</switch>
</logical-switches>
</capable-switch>
</config>
</edit-config>
</rpc>
\ No newline at end of file
/*
* 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.driver.netconf;
import org.junit.Test;
import org.onlab.packet.IpAddress;
import org.onosproject.net.behaviour.ControllerInfo;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertTrue;
import static org.onosproject.driver.netconf.XmlConfigParser.*;
//import static org.junit.Assert.*;
/**
* Test the XML document Parsing for netconf configuration.
*/
public class XmlConfigParserTest {
@Test
public void basics() throws IOException {
InputStream stream = getClass().getResourceAsStream("testConfig.xml");
List<ControllerInfo> controllers = parseStreamControllers(loadXml(stream));
assertTrue(controllers.get(0).equals(new ControllerInfo(
IpAddress.valueOf("10.128.12.1"), 6653, "tcp")));
assertTrue(controllers.get(1).equals(new ControllerInfo(
IpAddress.valueOf("10.128.12.2"), 6654, "tcp")));
}
@Test
public void switchId() {
InputStream stream = getClass().getResourceAsStream("testConfig.xml");
String switchId = parseSwitchId(loadXml(stream));
assertTrue(switchId.equals("ofc-bridge"));
}
@Test
public void capableSwitchId() {
InputStream stream = getClass().getResourceAsStream("testConfig.xml");
String capableSwitchId = parseCapableSwitchId(loadXml(stream));
assertTrue(capableSwitchId.equals("openvswitch"));
}
@Test
public void controllersConfig() {
InputStream streamOrig = getClass().getResourceAsStream("testConfig.xml");
InputStream streamCFG = XmlConfigParser.class
.getResourceAsStream("controllers.xml");
String config = createControllersConfig(loadXml(streamCFG),
loadXml(streamOrig), "running", "merge",
"create", new ArrayList<>(Arrays.asList(
new ControllerInfo(IpAddress.valueOf("192.168.1.1"),
5000, "tcp"))));
assertTrue(config.contains("192.168.1.1"));
assertTrue(config.contains("tcp"));
assertTrue(config.contains("5000"));
}
}
\ No newline at end of file
<?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.
-->
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="7">
<data>
<capable-switch xmlns="urn:onf:config:yang">
<id>openvswitch</id>
<resources>
<port>
<name>ofc-bridge</name>
<requested-number>666</requested-number>
<configuration>
<admin-state>down</admin-state>
<no-receive>false</no-receive>
<no-forward>false</no-forward>
<no-packet-in>false</no-packet-in>
</configuration>
</port>
</resources>
<logical-switches>
<switch>
<id>ofc-bridge</id>
<datapath-id>00:01:02:03:04:05:06:07</datapath-id>
<lost-connection-behavior>failSecureMode</lost-connection-behavior>
<controllers>
<controller>
<id>(null)</id>
<ip-address>10.128.12.1</ip-address>
<port>6653</port>
<protocol>tcp</protocol>
</controller>
<controller>
<id>(null)</id>
<ip-address>10.128.12.2</ip-address>
<port>6654</port>
<protocol>tcp</protocol>
</controller>
</controllers>
<resources>
<port>ofc-bridge</port>
</resources>
</switch>
</logical-switches>
</capable-switch>
</data>
</rpc-reply>
\ No newline at end of file
......@@ -40,11 +40,6 @@
<artifactId>netty-transport-native-epoll</artifactId>
<version>${netty4.version}</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-netconf-rfc</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
......
package org.onosproject.netconf;
/**
* Created by tom on 10/19/15.
*/
public class Foo {
}
/*
* 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.netconf;
import org.onlab.packet.IpAddress;
import org.onosproject.net.DeviceId;
import java.util.Map;
/**
* Abstraction of an NETCONF controller. Serves as a one stop shop for obtaining
* NetconfDevice and (un)register listeners on NETCONF device events.
*/
public interface NetconfController {
/**
* Adds Device Event Listener.
*
* @param listener node listener
*/
void addDeviceListener(NetconfDeviceListener listener);
/**
* Removes Device Listener.
*
* @param listener node listener
*/
void removeDeviceListener(NetconfDeviceListener listener);
/**
* Tries to connect to a specific NETCONF device, if the connection is succesful
* it creates and adds the device to the ONOS core as a NetconfDevice.
*
* @param deviceInfo info about the device to add
* @return NetconfDevice Netconf device
*/
NetconfDevice connectDevice(NetconfDeviceInfo deviceInfo);
/**
* Removes a Netconf device.
*
* @param deviceInfo info about the device to remove
*/
void removeDevice(NetconfDeviceInfo deviceInfo);
/**
* Gets all the nodes information.
*
* @return map of devices
*/
Map<DeviceId, NetconfDevice> getDevicesMap();
/**
* Gets a Netconf Device by node identifier.
*
* @param deviceInfo node identifier
* @return NetconfDevice Netconf device
*/
NetconfDevice getNetconfDevice(DeviceId deviceInfo);
/**
* Gets a Netconf Device by node identifier.
*
* @param ip device ip
* @param port device port
* @return NetconfDevice Netconf device
*/
NetconfDevice getNetconfDevice(IpAddress ip, int port);
}
/*
* 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.netconf;
/**
* Interface representing a NETCONF device.
*/
public interface NetconfDevice {
/**
* Returns whether a device is a NETCONF device with a capabilities list
* and is accessible.
*
* @return true if device is accessible, false otherwise
*/
boolean isActive();
/**
* Returns a NETCONF session context for this device.
*
* @return netconf session
*/
NetconfSession getSession();
/**
* Ensures that all sessions are closed.
* A device cannot be used after disconnect is called.
*/
void disconnect();
/**
* return all the info associated with this device.
* @return NetconfDeviceInfo
*/
NetconfDeviceInfo getDeviceInfo();
}
\ No newline at end of file
/*
* 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.netconf;
import com.google.common.base.Preconditions;
import org.onlab.packet.IpAddress;
import org.onosproject.net.DeviceId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Objects;
/**
* Represents a Netconf device information.
*/
public class NetconfDeviceInfo {
public static final Logger log = LoggerFactory
.getLogger(NetconfDeviceInfo.class);
private String name;
private String password;
private IpAddress ipAddress;
private int port;
private File keyFile;
/**
* Information for contacting the controller.
*
* @param name the connection type
* @param password the password for the device
* @param ipAddress the ip address
* @param port the tcp port
*/
public NetconfDeviceInfo(String name, String password, IpAddress ipAddress,
int port) {
Preconditions.checkArgument(!name.equals(""), "Empty device name");
Preconditions.checkNotNull(port > 0, "Negative port");
Preconditions.checkNotNull(ipAddress, "Null ip address");
this.name = name;
this.password = password;
this.ipAddress = ipAddress;
this.port = port;
}
/**
* Information for contacting the controller.
*
* @param name the connection type
* @param password the password for the device
* @param ipAddress the ip address
* @param port the tcp port
* @param keyString the string cointaing the key.
*/
public NetconfDeviceInfo(String name, String password, IpAddress ipAddress,
int port, String keyString) {
Preconditions.checkArgument(!name.equals(""), "Empty device name");
Preconditions.checkNotNull(port > 0, "Negative port");
Preconditions.checkNotNull(ipAddress, "Null ip address");
this.name = name;
this.password = password;
this.ipAddress = ipAddress;
this.port = port;
this.keyFile = new File(keyString);
}
/**
* Exposes the name of the controller.
*
* @return String name
*/
public String name() {
return name;
}
/**
* Exposes the password of the controller.
*
* @return String password
*/
public String password() {
return password;
}
/**
* Exposes the ip address of the controller.
*
* @return IpAddress ip address
*/
public IpAddress ip() {
return ipAddress;
}
/**
* Exposes the port of the controller.
*
* @return int port address
*/
public int port() {
return port;
}
/**
* Exposes the keyFile of the controller.
*
* @return int port address
*/
public File getKeyFile() {
return keyFile;
}
/**
* Return the info about the device in a string.
* String format: "netconf:name@ip:port"
*
* @return String device info
*/
public String toString() {
return "netconf:" + name + "@" + ipAddress + ":" + port;
}
/**
* Return the DeviceId about the device containing the URI.
*
* @return DeviceId
*/
public DeviceId getDeviceId() {
try {
return DeviceId.deviceId(new URI(this.toString()));
} catch (URISyntaxException e) {
log.debug("Unable to build deviceID for device {} ", this, e);
}
return null;
}
@Override
public int hashCode() {
return Objects.hash(ipAddress, port, name);
}
@Override
public boolean equals(Object toBeCompared) {
if (toBeCompared instanceof NetconfDeviceInfo) {
NetconfDeviceInfo netconfDeviceInfo = (NetconfDeviceInfo) toBeCompared;
if (netconfDeviceInfo.name().equals(name)
&& netconfDeviceInfo.ip().equals(ipAddress)
&& netconfDeviceInfo.port() == port
&& netconfDeviceInfo.password().equals(password)) {
return true;
}
}
return false;
}
}
/*
* 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.netconf;
/**
* Allows for providers interested in node events to be notified.
*/
public interface NetconfDeviceListener {
/**
* Notifies that the node was added.
*
* @param nodeId the node where the event occurred
*/
void deviceAdded(NetconfDeviceInfo nodeId);
/**
* Notifies that the node was removed.
*
* @param nodeId the node where the event occurred
*/
void deviceRemoved(NetconfDeviceInfo nodeId);
}
/*
* 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.netconf;
import java.util.List;
/**
* NETCONF session object that allows NETCONF operations on top with the physical
* device on top of an SSH connection.
*/
// TODO change return type of methdos to <Capability, XMLdoc, string or yang obj>
public interface NetconfSession {
/**
* Retrives the requested configuration, different from get-config.
* @param request the XML containing the request to the server.
* @return device running configuration
*/
String get(String request);
/**
* Executes an RPC to the server.
* @param request the XML containing the RPC for the server.
* @return Server response or ERROR
*/
String doRPC(String request);
/**
* Retrives the specified configuration.
*
* @param targetConfiguration the type of configuration to retrieve.
* @return specified configuration.
*/
String getConfig(String targetConfiguration);
/**
* Retrives part of the specivied configuration based on the filterSchema.
*
* @param targetConfiguration the type of configuration to retrieve.
* @param configurationFilterSchema XML schema to filter the configuration
* elements we are interested in
* @return device running configuration.
*/
String getConfig(String targetConfiguration, String configurationFilterSchema);
/**
* Retrives part of the specified configuration based on the filterSchema.
*
* @param newConfiguration configuration to set
* @return true if the configuration was edited correctly
*/
boolean editConfig(String newConfiguration);
/**
* Copies the new configuration, an Url or a complete configuration xml tree
* to the target configuration.
* The target configuration can't be the running one
*
* @param targetConfiguration the type of configuration to retrieve.
* @param newConfiguration configuration to set
* @return true if the configuration was copied correctly
*/
boolean copyConfig(String targetConfiguration, String newConfiguration);
/**
* Deletes part of the specified configuration based on the filterSchema.
*
* @param targetConfiguration the name of the configuration to delete
* @return true if the configuration was copied correctly
*/
boolean deleteConfig(String targetConfiguration);
/**
* Locks the candidate configuration.
*
* @return true if successful.
*/
boolean lock();
/**
* Unlocks the candidate configuration.
*
* @return true if successful.
*/
boolean unlock();
/**
* Closes the Netconf session with the device.
* the first time it tries gracefully, then kills it forcefully
* @return true if closed
*/
boolean close();
/**
* Gets the session ID of the Netconf session.
*
* @return Session ID as a string.
*/
String getSessionId();
/**
* Gets the capabilities of the Netconf server associated to this session.
*
* @return Network capabilities as a string.
*/
String getServerCapabilities();
/**
* Sets the device capabilities.
* @param capabilities list of capabilities the device has.
*/
void setDeviceCapabilities(List<String> capabilities);
}
......@@ -39,9 +39,53 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-netconf-rfc</artifactId>
<version>${project.version}</version>
<groupId>ch.ethz.ganymed</groupId>
<artifactId>ganymed-ssh2</artifactId>
<version>262</version>
</dependency>
</dependencies>
<build>
<plugins>
<!--plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<configuration>
<filters>
<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-bundle-plugin</artifactId>
<configuration>
<instructions>
<Private-Package>ch.ethz.ssh2.*</Private-Package>
<Embed-Dependecy>ganymed-ssh2</Embed-Dependecy>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
......
package org.onosproject.netconf.ctl;
/**
* Created by tom on 10/19/15.
*/
public class Foo {
}
/*
* 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.netconf.ctl;
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.onlab.packet.IpAddress;
import org.onosproject.net.DeviceId;
import org.onosproject.netconf.NetconfController;
import org.onosproject.netconf.NetconfDevice;
import org.onosproject.netconf.NetconfDeviceInfo;
import org.onosproject.netconf.NetconfDeviceListener;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* The implementation of NetconfController.
*/
@Component(immediate = true)
@Service
public class NetconfControllerImpl implements NetconfController {
public static final Logger log = LoggerFactory
.getLogger(NetconfControllerImpl.class);
public Map<DeviceId, NetconfDevice> netconfDeviceMap = new ConcurrentHashMap<>();
protected Set<NetconfDeviceListener> netconfDeviceListeners = new CopyOnWriteArraySet<>();
@Activate
public void activate(ComponentContext context) {
log.info("Started");
}
@Deactivate
public void deactivate() {
netconfDeviceMap.clear();
log.info("Stopped");
}
@Override
public void addDeviceListener(NetconfDeviceListener listener) {
if (!netconfDeviceListeners.contains(listener)) {
netconfDeviceListeners.add(listener);
}
}
@Override
public void removeDeviceListener(NetconfDeviceListener listener) {
netconfDeviceListeners.remove(listener);
}
@Override
public NetconfDevice getNetconfDevice(DeviceId deviceInfo) {
return netconfDeviceMap.get(deviceInfo);
}
@Override
public NetconfDevice getNetconfDevice(IpAddress ip, int port) {
NetconfDevice device = null;
for (DeviceId info : netconfDeviceMap.keySet()) {
if (IpAddress.valueOf(info.uri().getHost()).equals(ip) &&
info.uri().getPort() == port) {
return netconfDeviceMap.get(info);
}
}
return device;
}
@Override
public NetconfDevice connectDevice(NetconfDeviceInfo deviceInfo) {
if (netconfDeviceMap.containsKey(deviceInfo.getDeviceId())) {
log.warn("Device {} is already present");
return netconfDeviceMap.get(deviceInfo.getDeviceId());
} else {
log.info("Creating NETCONF device {}", deviceInfo);
return createDevice(deviceInfo);
}
}
@Override
public void removeDevice(NetconfDeviceInfo deviceInfo) {
if (netconfDeviceMap.containsKey(deviceInfo.getDeviceId())) {
log.warn("Device {} is not present");
} else {
stopDevice(deviceInfo);
}
}
private NetconfDevice createDevice(NetconfDeviceInfo deviceInfo) {
NetconfDevice netconfDevice = null;
try {
netconfDevice = new NetconfDeviceImpl(deviceInfo);
for (NetconfDeviceListener l : netconfDeviceListeners) {
l.deviceAdded(deviceInfo);
}
netconfDeviceMap.put(deviceInfo.getDeviceId(), netconfDevice);
} catch (IOException e) {
throw new IllegalStateException("Cannot create NETCONF device " +
"with device Info: " +
deviceInfo + " \n" + e);
}
return netconfDevice;
}
private void stopDevice(NetconfDeviceInfo deviceInfo) {
netconfDeviceMap.get(deviceInfo.getDeviceId()).disconnect();
netconfDeviceMap.remove(deviceInfo.getDeviceId());
for (NetconfDeviceListener l : netconfDeviceListeners) {
l.deviceRemoved(deviceInfo);
}
}
@Override
public Map<DeviceId, NetconfDevice> getDevicesMap() {
return netconfDeviceMap;
}
}
/*
* 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.netconf.ctl;
import org.onosproject.netconf.NetconfDevice;
import org.onosproject.netconf.NetconfDeviceInfo;
import org.onosproject.netconf.NetconfSession;
import java.io.IOException;
/**
* Implementation of a NETCONF device.
*/
public class NetconfDeviceImpl implements NetconfDevice {
private NetconfDeviceInfo netconfDeviceInfo;
private boolean deviceState = false;
private NetconfSession netconfSession;
//private String config;
public NetconfDeviceImpl(NetconfDeviceInfo deviceInfo) throws IOException {
netconfDeviceInfo = deviceInfo;
try {
netconfSession = new NetconfSessionImpl(netconfDeviceInfo);
} catch (IOException e) {
throw new IOException("Cannot create connection and session", e);
}
deviceState = true;
//config = netconfSession.getConfig("running");
}
@Override
public boolean isActive() {
return deviceState;
}
@Override
public NetconfSession getSession() {
return netconfSession;
}
@Override
public void disconnect() {
deviceState = false;
netconfSession.close();
}
@Override
public NetconfDeviceInfo getDeviceInfo() {
return netconfDeviceInfo;
}
}
/*
* 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.netconf.ctl;
import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.Session;
import com.google.common.base.Preconditions;
import org.onosproject.netconf.NetconfDeviceInfo;
import org.onosproject.netconf.NetconfSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Implementation of a NETCONF session to talk to a device.
*/
public class NetconfSessionImpl implements NetconfSession {
public static final Logger log = LoggerFactory
.getLogger(NetconfSessionImpl.class);
private static final int CONNECTION_TIMEOUT = 0;
private Connection netconfConnection;
private NetconfDeviceInfo deviceInfo;
private Session sshSession;
private boolean connectionActive;
private BufferedReader bufferReader = null;
private PrintWriter out = null;
private int messageID = 0;
private List<String> deviceCapabilities =
new ArrayList<>(
Arrays.asList("urn:ietf:params:netconf:base:1.0"));
private String serverCapabilities;
private String endpattern = "]]>]]>";
public NetconfSessionImpl(NetconfDeviceInfo deviceInfo) throws IOException {
this.deviceInfo = deviceInfo;
connectionActive = false;
startConnection();
}
private void startConnection() throws IOException {
if (!connectionActive) {
netconfConnection = new Connection(deviceInfo.ip().toString(), deviceInfo.port());
netconfConnection.connect(null, CONNECTION_TIMEOUT, 0);
boolean isAuthenticated;
try {
if (deviceInfo.getKeyFile() != null) {
isAuthenticated = netconfConnection.authenticateWithPublicKey(
deviceInfo.name(), deviceInfo.getKeyFile(),
deviceInfo.password());
} else {
log.info("authenticate with username {} and password {}",
deviceInfo.name(), deviceInfo.password());
isAuthenticated = netconfConnection.authenticateWithPassword(
deviceInfo.name(), deviceInfo.password());
}
} catch (IOException e) {
throw new IOException("Authentication connection failed:" +
e.getMessage());
}
connectionActive = true;
Preconditions.checkArgument(isAuthenticated,
"Authentication password and username failed");
startSshSession();
}
}
private void startSshSession() throws IOException {
try {
sshSession = netconfConnection.openSession();
sshSession.startSubSystem("netconf");
bufferReader = new BufferedReader(new InputStreamReader(
sshSession.getStdout()));
out = new PrintWriter(sshSession.getStdin());
sendHello();
} catch (IOException e) {
throw new IOException("Failed to create ch.ethz.ssh2.Session session:" +
e.getMessage());
}
}
private void sendHello() throws IOException {
serverCapabilities = doRequest(createHelloString());
}
private String createHelloString() {
StringBuilder hellobuffer = new StringBuilder();
hellobuffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
hellobuffer.append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
hellobuffer.append(" <capabilities>\n");
deviceCapabilities.forEach(
cap -> hellobuffer.append(" <capability>" + cap + "</capability>\n"));
hellobuffer.append(" </capabilities>\n");
hellobuffer.append("</hello>\n");
hellobuffer.append(endpattern);
return hellobuffer.toString();
}
@Override
public String doRPC(String request) {
String reply = "ERROR";
try {
reply = doRequest(request);
if (checkReply(reply)) {
return reply;
} else {
return "ERROR " + reply;
}
} catch (IOException e) {
log.error("Problem in the reading from the SSH connection " + e);
}
return reply;
}
private String doRequest(String request) throws IOException {
log.info("sshState " + sshSession.getState() + "request" + request);
if (sshSession.getState() != 2) {
try {
startSshSession();
} catch (IOException e) {
log.info("the connection had to be reopened");
startConnection();
}
sendHello();
}
log.info("sshState after" + sshSession.getState());
out.print(request);
out.flush();
messageID++;
return readOne();
}
@Override
public String get(String request) {
return doRPC(request);
}
@Override
public String getConfig(String targetConfiguration) {
return getConfig(targetConfiguration, null);
}
@Override
public String getConfig(String targetConfiguration, String configurationSchema) {
StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
rpc.append("<rpc message-id=\"" + messageID + "\" "
+ "xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
rpc.append("<get-config>\n");
rpc.append("<source>\n");
rpc.append("<" + targetConfiguration + "/>");
rpc.append("</source>");
if (configurationSchema != null) {
rpc.append("<filter type=\"subtree\">\n");
rpc.append(configurationSchema + "\n");
rpc.append("</filter>\n");
}
rpc.append("</get-config>\n");
rpc.append("</rpc>\n");
rpc.append(endpattern);
String reply = null;
try {
reply = doRequest(rpc.toString());
} catch (IOException e) {
e.printStackTrace();
}
return checkReply(reply) ? reply : null;
}
@Override
public boolean editConfig(String newConfiguration) {
newConfiguration = newConfiguration + endpattern;
String reply = null;
try {
reply = doRequest(newConfiguration);
} catch (IOException e) {
e.printStackTrace();
}
return checkReply(reply);
}
@Override
public boolean copyConfig(String targetConfiguration, String newConfiguration) {
newConfiguration = newConfiguration.trim();
if (!newConfiguration.startsWith("<configuration>")) {
newConfiguration = "<configuration>" + newConfiguration
+ "</configuration>";
}
StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" " +
"encoding=\"UTF-8\"?>");
rpc.append("<rpc>");
rpc.append("<copy-config>");
rpc.append("<target>");
rpc.append("<" + targetConfiguration + "/>");
rpc.append("</target>");
rpc.append("<source>");
rpc.append("<" + newConfiguration + "/>");
rpc.append("</source>");
rpc.append("</copy-config>");
rpc.append("</rpc>");
rpc.append(endpattern);
String reply = null;
try {
reply = doRequest(rpc.toString());
} catch (IOException e) {
e.printStackTrace();
}
return checkReply(reply);
}
@Override
public boolean deleteConfig(String targetConfiguration) {
if (targetConfiguration.equals("running")) {
log.warn("Target configuration for delete operation can't be \"running\"",
targetConfiguration);
return false;
}
StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" " +
"encoding=\"UTF-8\"?>");
rpc.append("<rpc>");
rpc.append("<delete-config>");
rpc.append("<target>");
rpc.append("<" + targetConfiguration + "/>");
rpc.append("</target>");
rpc.append("</delete-config>");
rpc.append("</rpc>");
rpc.append(endpattern);
String reply = null;
try {
reply = doRequest(rpc.toString());
} catch (IOException e) {
e.printStackTrace();
}
return checkReply(reply);
}
@Override
public boolean lock() {
StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" " +
"encoding=\"UTF-8\"?>");
rpc.append("<rpc>");
rpc.append("<lock>");
rpc.append("<target>");
rpc.append("<candidate/>");
rpc.append("</target>");
rpc.append("</lock>");
rpc.append("</rpc>");
rpc.append(endpattern);
String reply = null;
try {
reply = doRequest(rpc.toString());
} catch (IOException e) {
e.printStackTrace();
}
return checkReply(reply);
}
@Override
public boolean unlock() {
StringBuilder rpc = new StringBuilder("<?xml version=\"1.0\" " +
"encoding=\"UTF-8\"?>");
rpc.append("<rpc>");
rpc.append("<unlock>");
rpc.append("<target>");
rpc.append("<candidate/>");
rpc.append("</target>");
rpc.append("</unlock>");
rpc.append("</rpc>");
rpc.append(endpattern);
String reply = null;
try {
reply = doRequest(rpc.toString());
} catch (IOException e) {
e.printStackTrace();
}
return checkReply(reply);
}
@Override
public boolean close() {
return close(false);
}
private boolean close(boolean force) {
StringBuilder rpc = new StringBuilder();
rpc.append("<rpc>");
if (force) {
rpc.append("<kill-configuration/>");
} else {
rpc.append("<close-configuration/>");
}
rpc.append("<close-configuration/>");
rpc.append("</rpc>");
rpc.append(endpattern);
return checkReply(rpc.toString()) ? true : close(true);
}
@Override
public String getSessionId() {
if (serverCapabilities.contains("<session-id>")) {
String[] outer = serverCapabilities.split("<session-id>");
Preconditions.checkArgument(outer.length != 1,
"Error in retrieving the session id");
String[] value = outer[1].split("</session-id>");
Preconditions.checkArgument(value.length != 1,
"Error in retrieving the session id");
return value[0];
} else {
return String.valueOf(-1);
}
}
@Override
public String getServerCapabilities() {
return serverCapabilities;
}
@Override
public void setDeviceCapabilities(List<String> capabilities) {
deviceCapabilities = capabilities;
}
private boolean checkReply(String reply) {
if (reply != null) {
if (!reply.contains("<rpc-error>")) {
return true;
} else if (reply.contains("<ok/>")
|| (reply.contains("<rpc-error>")
&& reply.contains("warning"))) {
return true;
}
}
return false;
}
private String readOne() throws IOException {
//TODO try a simple string
final StringWriter reply = new StringWriter();
while (true) {
int charRead = bufferReader.read();
if (charRead == -1) {
throw new IOException("Session closed");
}
for (int i = 0; i < endpattern.length(); i++) {
if (charRead == endpattern.charAt(i)) {
if (i < endpattern.length() - 1) {
charRead = bufferReader.read();
} else {
return reply.getBuffer().toString();
}
} else {
String s = endpattern.substring(0, i);
for (int j = 0; i < s.length(); j++) {
reply.write(s.charAt(j));
}
reply.write(charRead);
break;
}
}
}
}
}
......@@ -27,6 +27,12 @@
<artifactId>onos-netconf</artifactId>
<packaging>pom</packaging>
<modules>
<module>api</module>
<module>rfc</module>
<module>ctl</module>
</modules>
<description>ONOS NETCONF southbound libraries</description>
<dependencies>
<dependency>
......@@ -54,6 +60,11 @@
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.scr.annotations</artifactId>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-core-net</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
......@@ -68,10 +79,4 @@
</plugin>
</plugins>
</build>
<modules>
<module>api</module>
<module>rfc</module>
<module>ctl</module>
</modules>
</project>
......
......@@ -27,5 +27,4 @@
<artifactId>onos-netconf-rfc</artifactId>
<packaging>bundle</packaging>
</project>
......
......@@ -19,6 +19,13 @@
features="${project.artifactId}">
<description>${project.description}</description>
<artifact>mvn:${project.groupId}/onos-netconf-rfc/${project.version}</artifact>
<artifact>mvn:${project.groupId}/onos-netconf-api/${project.version}</artifact>
<artifact>mvn:${project.groupId}/onos-netconf-ctl/${project.version}</artifact>
<artifact>mvn:${project.groupId}/onos-drivers/${project.version}</artifact>
<artifact>mvn:${project.groupId}/onos-netconf-provider-device/${project.version}</artifact>
<!--<artifact>mvn:${project.groupId}/onos-netconf-provider-device/${project.version}</artifact>-->
<!-- Question: should there be the jnc stuff here? Or is it just for testing -->
</app>
......
......@@ -20,6 +20,9 @@
description="${project.description}">
<feature>onos-api</feature>
<bundle>mvn:io.netty/netty/3.9.2.Final</bundle>
<bundle>mvn:${project.groupId}/onos-netconf-api/${project.version}</bundle>
<bundle>mvn:${project.groupId}/onos-netconf-ctl/${project.version}</bundle>
<bundle>mvn:${project.groupId}/onos-netconf-provider-device/${project.version}</bundle>
<!-- Question: should there be the jnc stuff here? Or is it just for testing -->
</feature>
......
......@@ -33,129 +33,29 @@
<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>
<artifactId>onlab-junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onlab-junit</artifactId>
<scope>test</scope>
<artifactId>onos-netconf-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<scope>test</scope>
<groupId>org.onosproject</groupId>
<artifactId>onos-netconf-ctl</artifactId>
<version>${project.version}</version>
</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>
......
/*
* 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;
}
}
......@@ -13,39 +13,28 @@
* 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;
package org.onosproject.provider.netconf.device.impl;
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;
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.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.incubator.net.config.basics.ConfigException;
import org.onosproject.net.DefaultAnnotations;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.MastershipRole;
import org.onosproject.net.SparseAnnotations;
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.device.DefaultDeviceDescription;
import org.onosproject.net.device.DeviceDescription;
import org.onosproject.net.device.DeviceProvider;
......@@ -54,305 +43,180 @@ 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.onosproject.netconf.NetconfController;
import org.onosproject.netconf.NetconfDevice;
import org.onosproject.netconf.NetconfDeviceInfo;
import org.onosproject.netconf.NetconfDeviceListener;
import org.slf4j.Logger;
import java.util.Map;
import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Provider which will try to fetch the details of NETCONF devices from the core
* and run a capability discovery on each of the device.
* Provider which uses an NETCONF controller to detect 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;
private final Logger log = getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceProviderRegistry providerRegistry;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceService deviceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ClusterService clusterService;
protected NetconfController controller; //where is initiated ?
@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";
protected NetworkConfigRegistry cfgService;
@Property(name = "devConfigs", value = "", label = "Instance-specific configurations")
private String devConfigs = null;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@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"));
private DeviceProviderService providerService;
private NetconfDeviceListener innerNodeListener = new InnerNetconfDeviceListener();
protected static final String ISNOTNULL = "NetconfDeviceInfo is not null";
private static final String UNKNOWN = "unknown";
private final ConfigFactory factory =
new ConfigFactory<ApplicationId, NetconfProviderConfig>(APP_SUBJECT_FACTORY,
NetconfProviderConfig.class,
"devices",
true) {
@Override
public NetconfProviderConfig createConfig() {
return new NetconfProviderConfig();
}
};
private final NetworkConfigListener cfgLister = new InternalNetworkConfigListener();
private ApplicationId appId;
@Activate
public void activate(ComponentContext context) {
cfgService.registerProperties(getClass());
public void activate() {
providerService = providerRegistry.register(this);
modified(context);
cfgService.registerConfigFactory(factory);
cfgService.addListener(cfgLister);
controller.addDeviceListener(innerNodeListener);
connectExistingDevices();
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();
public void deactivate() {
providerRegistry.unregister(this);
providerService = null;
cfgService.unregisterConfigFactory(factory);
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;
public NetconfDeviceProvider() {
super(new ProviderId("netconf", "org.onosproject.netconf.provider.device"));
}
@Override
public void triggerProbe(DeviceId deviceId) {
// TODO Auto-generated method stub
// 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) {
NetconfDevice netconfDevice = netconfDeviceMap.get(deviceId);
Map<DeviceId, NetconfDevice> devices = controller.getDevicesMap();
NetconfDevice netconfDevice = null;
for (DeviceId key : devices.keySet()) {
if (key.equals(deviceId)) {
netconfDevice = controller.getDevicesMap().get(key);
}
}
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();
return netconfDevice.isActive();
}
/**
* 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 class InnerNetconfDeviceListener implements NetconfDeviceListener {
private NetconfDevice device;
private boolean createFlag;
@Override
public void deviceAdded(NetconfDeviceInfo nodeId) {
Preconditions.checkNotNull(nodeId, ISNOTNULL);
DeviceId deviceId = nodeId.getDeviceId();
//TODO filter for not netconf devices
//Netconf configuration object
ChassisId cid = new ChassisId();
String ipAddress = nodeId.ip().toString();
SparseAnnotations annotations = DefaultAnnotations.builder()
.set("ipaddress", ipAddress).build();
DeviceDescription deviceDescription = new DefaultDeviceDescription(
deviceId.uri(),
Device.Type.SWITCH,
UNKNOWN, UNKNOWN,
UNKNOWN, UNKNOWN,
cid,
annotations);
providerService.deviceConnected(deviceId, deviceDescription);
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();
public void deviceRemoved(NetconfDeviceInfo nodeId) {
Preconditions.checkNotNull(nodeId, ISNOTNULL);
DeviceId deviceId = nodeId.getDeviceId();
providerService.deviceDisconnected(deviceId);
}
}
/**
* 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;
private void connectExistingDevices() {
//TODO consolidate
appId = coreService.registerApplication("org.onosproject.netconf");
connectDevices();
}
private void connectDevices() {
NetconfProviderConfig cfg = cfgService.getConfig(appId, NetconfProviderConfig.class);
if (cfg != null) {
log.info("cfg {}", cfg);
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;
cfg.getDevicesAddresses().stream().forEach(addr -> controller
.connectDevice(new NetconfDeviceInfo(addr.name(),
addr.password(),
addr.ip(),
addr.port())));
} catch (ConfigException e) {
log.error("Cannot read config error " + e);
}
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);
}
private class InternalNetworkConfigListener implements NetworkConfigListener {
@Override
public void event(NetworkConfigEvent event) {
connectDevices();
}
/**
* 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;
@Override
public boolean isRelevant(NetworkConfigEvent event) {
//TODO refactor
return event.configClass().equals(NetconfProviderConfig.class) &&
(event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED);
}
}
}
......
/*
* 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 com.fasterxml.jackson.databind.JsonNode;
import com.google.common.annotations.Beta;
import com.google.common.collect.Sets;
import org.onlab.packet.IpAddress;
import org.onosproject.core.ApplicationId;
import org.onosproject.incubator.net.config.basics.ConfigException;
import org.onosproject.net.config.Config;
import java.util.Set;
/**
* Configuration for Netconf provider.
*/
@Beta
public class NetconfProviderConfig extends Config<ApplicationId> {
public static final String CONFIG_VALUE_ERROR = "Error parsing config value";
private static final String IP = "ip";
private static final int DEFAULT_TCP_PORT = 830;
private static final String PORT = "port";
private static final String NAME = "name";
private static final String PASSWORD = "password";
public Set<NetconfDeviceAddress> getDevicesAddresses() throws ConfigException {
Set<NetconfDeviceAddress> devicesAddresses = Sets.newHashSet();
try {
for (JsonNode node : array) {
String ip = node.path(IP).asText();
IpAddress ipAddr = ip.isEmpty() ? null : IpAddress.valueOf(ip);
int port = node.path(PORT).asInt(DEFAULT_TCP_PORT);
String name = node.path(NAME).asText();
String password = node.path(PASSWORD).asText();
devicesAddresses.add(new NetconfDeviceAddress(ipAddr, port, name, password));
}
} catch (IllegalArgumentException e) {
throw new ConfigException(CONFIG_VALUE_ERROR, e);
}
return devicesAddresses;
}
public class NetconfDeviceAddress {
private final IpAddress ip;
private final int port;
private final String name;
private final String password;
public NetconfDeviceAddress(IpAddress ip, int port, String name, String password) {
this.ip = ip;
this.port = port;
this.name = name;
this.password = password;
}
public IpAddress ip() {
return ip;
}
public int port() {
return port;
}
public String name() {
return name;
}
public String password() {
return password;
}
}
}
/*
* 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) {
}
}
}
}
/*
* 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";
}
{
"devices":{
"netconf:mininet@10.1.9.24:1830":{
"basic":{
"driver":"ovs-netconf"
}
}
},
"apps":{
"org.onosproject.netconf":{
"devices":[{
"name":"mininet",
"password":"mininet",
"ip":"10.1.9.24",
"port":1830
}]
}
}
}
\ No newline at end of file