Hyunsun Moon
Committed by Gerrit Code Review

CORD-524 Added utils to execute IP commands to a remote host

Change-Id: Ie437f760a1d426cc748a7eae4ca1f5a2a1f104f5
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>
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&amp;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 +}