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