diff options
Diffstat (limited to 'framework/src/onos/protocols/netconf/ctl')
5 files changed, 716 insertions, 0 deletions
diff --git a/framework/src/onos/protocols/netconf/ctl/pom.xml b/framework/src/onos/protocols/netconf/ctl/pom.xml new file mode 100644 index 00000000..e022acba --- /dev/null +++ b/framework/src/onos/protocols/netconf/ctl/pom.xml @@ -0,0 +1,91 @@ +<?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</artifactId> + <version>1.4.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onos-netconf-ctl</artifactId> + <packaging>bundle</packaging> + + <dependencies> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.compendium</artifactId> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-netconf-api</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <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> diff --git a/framework/src/onos/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/NetconfControllerImpl.java b/framework/src/onos/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/NetconfControllerImpl.java new file mode 100644 index 00000000..a572a2bc --- /dev/null +++ b/framework/src/onos/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/NetconfControllerImpl.java @@ -0,0 +1,143 @@ +/* + * 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; + } + + +} diff --git a/framework/src/onos/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/NetconfDeviceImpl.java b/framework/src/onos/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/NetconfDeviceImpl.java new file mode 100644 index 00000000..3141aafc --- /dev/null +++ b/framework/src/onos/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/NetconfDeviceImpl.java @@ -0,0 +1,66 @@ +/* + * 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; + } +} diff --git a/framework/src/onos/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/NetconfSessionImpl.java b/framework/src/onos/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/NetconfSessionImpl.java new file mode 100644 index 00000000..8619abc0 --- /dev/null +++ b/framework/src/onos/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/NetconfSessionImpl.java @@ -0,0 +1,396 @@ +/* + * 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; + } + } + } + } + +} diff --git a/framework/src/onos/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/package-info.java b/framework/src/onos/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/package-info.java new file mode 100644 index 00000000..84992bf2 --- /dev/null +++ b/framework/src/onos/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/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. + */ + +/** + * Created by ray on 10/30/15. + */ +package org.onosproject.netconf.ctl; |