Committed by
Gerrit Code Review
CORD-524 Added utils to execute IP commands to a remote host
Change-Id: Ie437f760a1d426cc748a7eae4ca1f5a2a1f104f5
Showing
5 changed files
with
409 additions
and
5 deletions
apps/cordvtn/app.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | +<!-- | ||
| 3 | + ~ Copyright 2016 Open Networking Laboratory | ||
| 4 | + ~ | ||
| 5 | + ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 6 | + ~ you may not use this file except in compliance with the License. | ||
| 7 | + ~ You may obtain a copy of the License at | ||
| 8 | + ~ | ||
| 9 | + ~ http://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | + ~ | ||
| 11 | + ~ Unless required by applicable law or agreed to in writing, software | ||
| 12 | + ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | + ~ See the License for the specific language governing permissions and | ||
| 15 | + ~ limitations under the License. | ||
| 16 | + --> | ||
| 17 | +<app name="org.onosproject.cordvtn" origin="ON.Lab" version="${project.version}" | ||
| 18 | + category="default" url="http://onosproject.org" | ||
| 19 | + featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features" | ||
| 20 | + features="${project.artifactId}" | ||
| 21 | + apps="org.onosproject.ovsdb-base,org.onosproject.openstackswitching,org.onosproject.dhcp"> | ||
| 22 | + <description>${project.description}</description> | ||
| 23 | + <artifact>mvn:${project.groupId}/onos-app-cordvtn/${project.version}</artifact> | ||
| 24 | +</app> |
apps/cordvtn/features.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> | ||
| 2 | +<!-- | ||
| 3 | + ~ Copyright 2016 Open Networking Laboratory | ||
| 4 | + ~ | ||
| 5 | + ~ Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 6 | + ~ you may not use this file except in compliance with the License. | ||
| 7 | + ~ You may obtain a copy of the License at | ||
| 8 | + ~ | ||
| 9 | + ~ http://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | + ~ | ||
| 11 | + ~ Unless required by applicable law or agreed to in writing, software | ||
| 12 | + ~ distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | + ~ See the License for the specific language governing permissions and | ||
| 15 | + ~ limitations under the License. | ||
| 16 | + --> | ||
| 17 | +<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}"> | ||
| 18 | + <feature name="${project.artifactId}" version="${project.version}" description="${project.description}"> | ||
| 19 | + <feature>onos-api</feature> | ||
| 20 | + <bundle>mvn:${project.groupId}/onos-app-cordvtn/${project.version}</bundle> | ||
| 21 | + <bundle>wrap:mvn:com.jcraft/jsch/0.1.53$Bundle-SymbolicName=jsch&Bundle-Version=0.1.53</bundle> | ||
| 22 | + </feature> | ||
| 23 | +</features> |
| ... | @@ -43,11 +43,6 @@ | ... | @@ -43,11 +43,6 @@ |
| 43 | APIs for interacting with the CORD VTN application. | 43 | APIs for interacting with the CORD VTN application. |
| 44 | </api.description> | 44 | </api.description> |
| 45 | <api.package>org.onosproject.cordvtn.rest</api.package> | 45 | <api.package>org.onosproject.cordvtn.rest</api.package> |
| 46 | - <onos.app.requires> | ||
| 47 | - org.onosproject.ovsdb-base, | ||
| 48 | - org.onosproject.openstackswitching, | ||
| 49 | - org.onosproject.dhcp | ||
| 50 | - </onos.app.requires> | ||
| 51 | </properties> | 46 | </properties> |
| 52 | 47 | ||
| 53 | <dependencies> | 48 | <dependencies> |
| ... | @@ -116,6 +111,11 @@ | ... | @@ -116,6 +111,11 @@ |
| 116 | <artifactId>onos-app-dhcp-api</artifactId> | 111 | <artifactId>onos-app-dhcp-api</artifactId> |
| 117 | <version>${project.version}</version> | 112 | <version>${project.version}</version> |
| 118 | </dependency> | 113 | </dependency> |
| 114 | + <dependency> | ||
| 115 | + <groupId>com.jcraft</groupId> | ||
| 116 | + <artifactId>jsch</artifactId> | ||
| 117 | + <version>0.1.53</version> | ||
| 118 | + </dependency> | ||
| 119 | </dependencies> | 119 | </dependencies> |
| 120 | 120 | ||
| 121 | <build> | 121 | <build> |
| ... | @@ -145,6 +145,7 @@ | ... | @@ -145,6 +145,7 @@ |
| 145 | com.fasterxml.jackson.databind, | 145 | com.fasterxml.jackson.databind, |
| 146 | com.fasterxml.jackson.databind.node, | 146 | com.fasterxml.jackson.databind.node, |
| 147 | com.fasterxml.jackson.core, | 147 | com.fasterxml.jackson.core, |
| 148 | + com.jcraft.jsch, | ||
| 148 | org.apache.karaf.shell.commands, | 149 | org.apache.karaf.shell.commands, |
| 149 | org.apache.karaf.shell.console, | 150 | org.apache.karaf.shell.console, |
| 150 | com.google.common.*, | 151 | com.google.common.*, | ... | ... |
| 1 | +/* | ||
| 2 | + * Copyright 2016 Open Networking Laboratory | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | +package org.onosproject.cordvtn; | ||
| 17 | + | ||
| 18 | +import com.google.common.collect.Sets; | ||
| 19 | +import com.google.common.io.CharStreams; | ||
| 20 | +import com.jcraft.jsch.Channel; | ||
| 21 | +import com.jcraft.jsch.ChannelExec; | ||
| 22 | +import com.jcraft.jsch.JSch; | ||
| 23 | +import com.jcraft.jsch.JSchException; | ||
| 24 | +import com.jcraft.jsch.Session; | ||
| 25 | +import org.onlab.packet.IpPrefix; | ||
| 26 | +import org.slf4j.Logger; | ||
| 27 | + | ||
| 28 | +import java.io.IOException; | ||
| 29 | +import java.io.InputStream; | ||
| 30 | +import java.io.InputStreamReader; | ||
| 31 | +import java.util.Set; | ||
| 32 | +import java.util.regex.Pattern; | ||
| 33 | +import java.util.stream.Collectors; | ||
| 34 | + | ||
| 35 | +import static org.slf4j.LoggerFactory.getLogger; | ||
| 36 | + | ||
| 37 | +/** | ||
| 38 | + * {@code RemoteIpCommandUtil} provides methods to help execute Linux IP commands to a remote server. | ||
| 39 | + * It opens individual exec channels for each command. User can create a session with {@code connect} | ||
| 40 | + * method and then execute a series commands. After done with all commands, the session must be closed | ||
| 41 | + * explicitly by calling {@code disconnect}. | ||
| 42 | + */ | ||
| 43 | +public final class RemoteIpCommandUtil { | ||
| 44 | + | ||
| 45 | + protected static final Logger log = getLogger(RemoteIpCommandUtil.class); | ||
| 46 | + | ||
| 47 | + private static final String STRICT_HOST_CHECKING = "StrictHostKeyChecking"; | ||
| 48 | + private static final String DEFAULT_STRICT_HOST_CHECKING = "no"; | ||
| 49 | + private static final int DEFAULT_SESSION_TIMEOUT = 60000; // milliseconds | ||
| 50 | + | ||
| 51 | + private static final String IP_PATTERN = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.)" + | ||
| 52 | + "{3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/([0-9]|[1-2][0-9]|3[0-2]))$"; | ||
| 53 | + | ||
| 54 | + private static final String IP_ADDR_SHOW = "sudo ip addr show %s"; | ||
| 55 | + private static final String IP_ADDR_FLUSH = "sudo ip addr flush %s"; | ||
| 56 | + private static final String IP_ADDR_ADD = "sudo ip addr add %s dev %s"; | ||
| 57 | + private static final String IP_ADDR_DELETE = "sudo ip addr delete %s dev %s"; | ||
| 58 | + private static final String IP_LINK_SHOW = "sudo ip link show %s"; | ||
| 59 | + private static final String IP_LINK_UP = "sudo ip link set %s up"; | ||
| 60 | + | ||
| 61 | + /** | ||
| 62 | + * Default constructor. | ||
| 63 | + */ | ||
| 64 | + private RemoteIpCommandUtil() { | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + /** | ||
| 68 | + * Adds a given IP address to a given device. | ||
| 69 | + * | ||
| 70 | + * @param session ssh connection | ||
| 71 | + * @param ip ip address | ||
| 72 | + * @param device device name to assign the ip address | ||
| 73 | + * @return true if the command succeeds, or false | ||
| 74 | + */ | ||
| 75 | + public static boolean addIp(Session session, IpPrefix ip, String device) { | ||
| 76 | + if (session == null || !session.isConnected()) { | ||
| 77 | + return false; | ||
| 78 | + } | ||
| 79 | + | ||
| 80 | + executeCommand(session, String.format(IP_ADDR_ADD, ip, device)); | ||
| 81 | + Set<IpPrefix> result = getCurrentIps(session, device); | ||
| 82 | + return result.contains(ip); | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + /** | ||
| 86 | + * Removes the IP address from a given device. | ||
| 87 | + * | ||
| 88 | + * @param session ssh connection | ||
| 89 | + * @param ip ip address | ||
| 90 | + * @param device device name | ||
| 91 | + * @return true if the command succeeds, or false | ||
| 92 | + */ | ||
| 93 | + public static boolean deleteIp(Session session, IpPrefix ip, String device) { | ||
| 94 | + if (session == null || !session.isConnected()) { | ||
| 95 | + return false; | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + executeCommand(session, String.format(IP_ADDR_DELETE, ip, device)); | ||
| 99 | + Set<IpPrefix> result = getCurrentIps(session, device); | ||
| 100 | + return !result.contains(ip); | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | + /** | ||
| 104 | + * Removes all IP address on a given device. | ||
| 105 | + * | ||
| 106 | + * @param session ssh connection | ||
| 107 | + * @param device device name | ||
| 108 | + * @return true if the command succeeds, or false | ||
| 109 | + */ | ||
| 110 | + public static boolean flushIp(Session session, String device) { | ||
| 111 | + if (session == null || !session.isConnected()) { | ||
| 112 | + return false; | ||
| 113 | + } | ||
| 114 | + | ||
| 115 | + executeCommand(session, String.format(IP_ADDR_FLUSH, device)); | ||
| 116 | + return getCurrentIps(session, device).isEmpty(); | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + /** | ||
| 120 | + * Returns a set of IP address that a given device has. | ||
| 121 | + * | ||
| 122 | + * @param session ssh connection | ||
| 123 | + * @param device device name | ||
| 124 | + * @return set of IP prefix or empty set | ||
| 125 | + */ | ||
| 126 | + public static Set<IpPrefix> getCurrentIps(Session session, String device) { | ||
| 127 | + if (session == null || !session.isConnected()) { | ||
| 128 | + return Sets.newHashSet(); | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + String output = executeCommand(session, String.format(IP_ADDR_SHOW, device)); | ||
| 132 | + Set<IpPrefix> result = Pattern.compile(" ") | ||
| 133 | + .splitAsStream(output) | ||
| 134 | + .filter(s -> s.matches(IP_PATTERN)) | ||
| 135 | + .map(IpPrefix::valueOf) | ||
| 136 | + .collect(Collectors.toSet()); | ||
| 137 | + | ||
| 138 | + return result; | ||
| 139 | + } | ||
| 140 | + | ||
| 141 | + /** | ||
| 142 | + * Sets link state up for a given device. | ||
| 143 | + * | ||
| 144 | + * @param session ssh connection | ||
| 145 | + * @param device device name | ||
| 146 | + * @return true if the command succeeds, or false | ||
| 147 | + */ | ||
| 148 | + public static boolean setInterfaceUp(Session session, String device) { | ||
| 149 | + if (session == null || !session.isConnected()) { | ||
| 150 | + return false; | ||
| 151 | + } | ||
| 152 | + | ||
| 153 | + executeCommand(session, String.format(IP_LINK_UP, device)); | ||
| 154 | + return isInterfaceUp(session, device); | ||
| 155 | + } | ||
| 156 | + | ||
| 157 | + /** | ||
| 158 | + * Checks if a given interface is up or not. | ||
| 159 | + * | ||
| 160 | + * @param session ssh connection | ||
| 161 | + * @param device device name | ||
| 162 | + * @return true if the interface is up, or false | ||
| 163 | + */ | ||
| 164 | + public static boolean isInterfaceUp(Session session, String device) { | ||
| 165 | + if (session == null || !session.isConnected()) { | ||
| 166 | + return false; | ||
| 167 | + } | ||
| 168 | + | ||
| 169 | + String output = executeCommand(session, String.format(IP_LINK_SHOW, device)); | ||
| 170 | + return output != null && output.contains("UP"); | ||
| 171 | + } | ||
| 172 | + | ||
| 173 | + /** | ||
| 174 | + * Creates a new session with a given access information. | ||
| 175 | + * | ||
| 176 | + * @param sshInfo information to ssh to the remove server | ||
| 177 | + * @return ssh session, or null | ||
| 178 | + */ | ||
| 179 | + public static Session connect(SshAccessInfo sshInfo) { | ||
| 180 | + try { | ||
| 181 | + JSch jsch = new JSch(); | ||
| 182 | + jsch.addIdentity(sshInfo.privateKey()); | ||
| 183 | + | ||
| 184 | + Session session = jsch.getSession(sshInfo.user(), | ||
| 185 | + sshInfo.remoteIp().toString(), | ||
| 186 | + sshInfo.port().toInt()); | ||
| 187 | + session.setConfig(STRICT_HOST_CHECKING, DEFAULT_STRICT_HOST_CHECKING); | ||
| 188 | + session.connect(DEFAULT_SESSION_TIMEOUT); | ||
| 189 | + | ||
| 190 | + return session; | ||
| 191 | + } catch (JSchException e) { | ||
| 192 | + log.debug("Failed to connect to {} due to {}", sshInfo.toString(), e.toString()); | ||
| 193 | + return null; | ||
| 194 | + } | ||
| 195 | + } | ||
| 196 | + | ||
| 197 | + /** | ||
| 198 | + * Closes a connection. | ||
| 199 | + * | ||
| 200 | + * @param session session | ||
| 201 | + */ | ||
| 202 | + public static void disconnect(Session session) { | ||
| 203 | + if (session.isConnected()) { | ||
| 204 | + session.disconnect(); | ||
| 205 | + } | ||
| 206 | + } | ||
| 207 | + | ||
| 208 | + /** | ||
| 209 | + * Executes a given command. It opens exec channel for the command and closes | ||
| 210 | + * the channel when it's done. | ||
| 211 | + * | ||
| 212 | + * @param session ssh connection to a remote server | ||
| 213 | + * @param command command to execute | ||
| 214 | + * @return command output string if the command succeeds, or null | ||
| 215 | + */ | ||
| 216 | + private static String executeCommand(Session session, String command) { | ||
| 217 | + if (session == null || !session.isConnected()) { | ||
| 218 | + return null; | ||
| 219 | + } | ||
| 220 | + | ||
| 221 | + log.debug("Execute command {} to {}", command, session.getHost()); | ||
| 222 | + | ||
| 223 | + try { | ||
| 224 | + Channel channel = session.openChannel("exec"); | ||
| 225 | + ((ChannelExec) channel).setCommand(command); | ||
| 226 | + channel.setInputStream(null); | ||
| 227 | + InputStream output = channel.getInputStream(); | ||
| 228 | + | ||
| 229 | + channel.connect(); | ||
| 230 | + String result = CharStreams.toString(new InputStreamReader(output)); | ||
| 231 | + channel.disconnect(); | ||
| 232 | + | ||
| 233 | + return result; | ||
| 234 | + } catch (JSchException | IOException e) { | ||
| 235 | + log.debug("Failed to execute command {} due to {}", command, e.toString()); | ||
| 236 | + return null; | ||
| 237 | + } | ||
| 238 | + } | ||
| 239 | +} |
| 1 | +/* | ||
| 2 | + * Copyright 2016 Open Networking Laboratory | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | +package org.onosproject.cordvtn; | ||
| 17 | + | ||
| 18 | +import com.google.common.base.MoreObjects; | ||
| 19 | +import org.onlab.packet.Ip4Address; | ||
| 20 | +import org.onlab.packet.TpPort; | ||
| 21 | + | ||
| 22 | +import java.util.Objects; | ||
| 23 | + | ||
| 24 | +/** | ||
| 25 | + * Representation of SSH access information. | ||
| 26 | + */ | ||
| 27 | +public final class SshAccessInfo { | ||
| 28 | + | ||
| 29 | + private final Ip4Address remoteIp; | ||
| 30 | + private final TpPort port; | ||
| 31 | + private final String user; | ||
| 32 | + private final String privateKey; | ||
| 33 | + | ||
| 34 | + /** | ||
| 35 | + * Creates a new SSH access information. | ||
| 36 | + * | ||
| 37 | + * @param remoteIp ssh remote ip address | ||
| 38 | + * @param port ssh port number | ||
| 39 | + * @param user user name | ||
| 40 | + * @param privateKey path of ssh private key | ||
| 41 | + */ | ||
| 42 | + public SshAccessInfo(Ip4Address remoteIp, TpPort port, String user, String privateKey) { | ||
| 43 | + this.remoteIp = remoteIp; | ||
| 44 | + this.port = port; | ||
| 45 | + this.user = user; | ||
| 46 | + this.privateKey = privateKey; | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + /** | ||
| 50 | + * Returns the remote IP address. | ||
| 51 | + * | ||
| 52 | + * @return ip address | ||
| 53 | + */ | ||
| 54 | + public Ip4Address remoteIp() { | ||
| 55 | + return this.remoteIp; | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + /** | ||
| 59 | + * Returns the port number. | ||
| 60 | + * | ||
| 61 | + * @return ssh port | ||
| 62 | + */ | ||
| 63 | + public TpPort port() { | ||
| 64 | + return this.port; | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + /** | ||
| 68 | + * Returns the user name. | ||
| 69 | + * | ||
| 70 | + * @return user name | ||
| 71 | + */ | ||
| 72 | + public String user() { | ||
| 73 | + return this.user; | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + /** | ||
| 77 | + * Returns the private key path. | ||
| 78 | + * | ||
| 79 | + * @return privateKey | ||
| 80 | + */ | ||
| 81 | + public String privateKey() { | ||
| 82 | + return privateKey; | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + @Override | ||
| 86 | + public boolean equals(Object obj) { | ||
| 87 | + if (this == obj) { | ||
| 88 | + return true; | ||
| 89 | + } | ||
| 90 | + | ||
| 91 | + if (obj instanceof SshAccessInfo) { | ||
| 92 | + SshAccessInfo that = (SshAccessInfo) obj; | ||
| 93 | + if (Objects.equals(remoteIp, that.remoteIp) && | ||
| 94 | + Objects.equals(port, that.port) && | ||
| 95 | + Objects.equals(user, that.user) && | ||
| 96 | + Objects.equals(privateKey, that.privateKey)) { | ||
| 97 | + return true; | ||
| 98 | + } | ||
| 99 | + } | ||
| 100 | + return false; | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | + @Override | ||
| 104 | + public int hashCode() { | ||
| 105 | + return Objects.hash(remoteIp, port, user, privateKey); | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + @Override | ||
| 109 | + public String toString() { | ||
| 110 | + return MoreObjects.toStringHelper(getClass()) | ||
| 111 | + .add("remoteIp", remoteIp) | ||
| 112 | + .add("port", port) | ||
| 113 | + .add("user", user) | ||
| 114 | + .add("privateKey", privateKey) | ||
| 115 | + .toString(); | ||
| 116 | + } | ||
| 117 | +} |
-
Please register or login to post a comment