blob: 0c6caff74e4e5571bc32b5debd1d9a7d8abfd2ff [file] [log] [blame]
jaegonkimdcf7c822019-02-06 15:00:14 +09001/*
2 * Copyright 2019-present Open Networking Foundation
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 */
16package org.onosproject.ofoverlay.impl.util;
17
18import com.google.common.io.CharStreams;
19import com.jcraft.jsch.Channel;
20import com.jcraft.jsch.ChannelExec;
21import com.jcraft.jsch.JSch;
22import com.jcraft.jsch.JSchException;
23import com.jcraft.jsch.Session;
24import org.onlab.packet.IpAddress;
25import org.onosproject.workflow.api.WorkflowException;
26import org.onosproject.workflow.model.accessinfo.SshAccessInfo;
27import org.slf4j.Logger;
28
29import java.io.BufferedReader;
30import java.io.IOException;
31import java.io.InputStream;
32import java.io.InputStreamReader;
33import java.nio.charset.StandardCharsets;
34import java.util.Collections;
35import java.util.Set;
36import java.util.regex.Pattern;
37import java.util.stream.Collectors;
38
39import static org.onosproject.workflow.api.CheckCondition.check;
40import static org.slf4j.LoggerFactory.getLogger;
41
42/**
43 * Class for SSH utilities.
44 */
45public final class SshUtil {
46
47 protected static final Logger log = getLogger(SshUtil.class);
48
49 private static final String STRICT_HOST_CHECKING = "StrictHostKeyChecking";
50 private static final String DEFAULT_STRICT_HOST_CHECKING = "no";
51 private static final int DEFAULT_SESSION_TIMEOUT = 30000; // milliseconds
52
53 private static final String SPACESEPERATOR = " ";
54
55 /**
56 * Default constructor.
57 */
58 private SshUtil() {
59 }
60
61 /**
62 * Creates a new session with a given ssh access information.
63 *
64 * @param sshInfo information to ssh to the remote server
65 * @return ssh session, or null
66 */
67 public static Session connect(SshAccessInfo sshInfo) {
68 Session session;
69
70 try {
71 JSch jsch = new JSch();
72 jsch.addIdentity(sshInfo.privateKey());
73
74 session = jsch.getSession(sshInfo.user(),
75 sshInfo.remoteIp().toString(),
76 sshInfo.port().toInt());
77 session.setConfig(STRICT_HOST_CHECKING, DEFAULT_STRICT_HOST_CHECKING);
78 session.connect(DEFAULT_SESSION_TIMEOUT);
79
80 } catch (JSchException e) {
81 log.warn("Failed to connect to {}", sshInfo.toString(), e);
82 session = authUserPwd(sshInfo);
83 }
84 return session;
85 }
86
87 /**
88 * Creates a new session with ssh access info.
89 *
90 * @param sshInfo information to ssh to the remote server
91 * @return ssh session, or null
92 */
93 public static Session authUserPwd(SshAccessInfo sshInfo) {
94 log.info("Retrying Session with {}", sshInfo);
95 try {
96 JSch jsch = new JSch();
97
98 Session session = jsch.getSession(sshInfo.user(),
99 sshInfo.remoteIp().toString(),
100 sshInfo.port().toInt());
101 session.setPassword(sshInfo.password());
102 session.setConfig(STRICT_HOST_CHECKING, DEFAULT_STRICT_HOST_CHECKING);
103 session.connect(DEFAULT_SESSION_TIMEOUT);
104
105 return session;
106 } catch (JSchException e) {
107 log.warn("Failed to connect to {} due to {}", sshInfo.toString(), e);
108 return null;
109 }
110 }
111
112 /**
113 * Closes a connection.
114 *
115 * @param session session ssh session
116 */
117 public static void disconnect(Session session) {
118 if (session.isConnected()) {
119 session.disconnect();
120 }
121 }
122
123 /**
124 * Fetches last term after executing command.
125 * @param session ssh session
126 * @param command command to execute
127 * @return last term, or null
128 */
129 public static String fetchLastTerm(Session session, String command) {
130 if (session == null || !session.isConnected()) {
131 log.error("Invalid session({})", session);
132 return null;
133 }
134
135 log.info("fetchLastTerm: ssh command {} to {}", command, session.getHost());
136
137 try {
138 Channel channel = session.openChannel("exec");
139 if (channel == null) {
140 log.error("Invalid channel of session({}) for command({})", session, command);
141 return null;
142 }
143
144 ((ChannelExec) channel).setCommand(command);
145 channel.setInputStream(null);
146 InputStream output = channel.getInputStream();
147 channel.connect();
148 String[] lineList = null;
149
150 try (BufferedReader reader = new BufferedReader(new InputStreamReader(output, StandardCharsets.UTF_8))) {
151 lineList = reader.lines().findFirst().get().split(SPACESEPERATOR);
152 } catch (IOException e) {
153 log.error("Exception in fetchLastTerm", e);
154 } finally {
155 channel.disconnect();
156 output.close();
157 }
158
159 if (lineList.length > 0) {
160 return lineList[lineList.length - 1];
161 } else {
162 return null;
163 }
164
165 } catch (JSchException | IOException e) {
166 log.error("Exception in fetchLastTerm", e);
167 return null;
168 }
169 }
170
171 /**
172 * Executes a given command. It opens exec channel for the command and closes
173 * the channel when it's done.
174 *
175 * @param session ssh connection to a remote server
176 * @param command command to execute
177 * @return command output string if the command succeeds, or null
178 */
179 public static String executeCommand(Session session, String command) {
180 if (session == null || !session.isConnected()) {
181 log.error("Invalid session({})", session);
182 return null;
183 }
184
185 log.info("executeCommand: ssh command {} to {}", command, session.getHost());
186
187 try {
188 Channel channel = session.openChannel("exec");
189
190 if (channel == null) {
191 log.debug("Invalid channel of session({}) for command({})", session, command);
192 return null;
193 }
194
195 ((ChannelExec) channel).setCommand(command);
196 channel.setInputStream(null);
197 InputStream output = channel.getInputStream();
198
199 channel.connect();
200 String result = CharStreams.toString(new InputStreamReader(output, StandardCharsets.UTF_8));
201 log.trace("SSH result(on {}): {}", session.getHost(), result);
202 channel.disconnect();
203
204 return result;
205 } catch (JSchException | IOException e) {
206 log.debug("Failed to execute command {} due to {}", command, e);
207 return null;
208 }
209 }
210
211 /**
212 * Fetches OVS version information.
213 * @param session Jsch session
214 * @return OVS version
215 * @throws WorkflowException workflow exception
216 */
217 public static OvsVersion fetchOvsVersion(Session session) throws WorkflowException {
218
219 OvsVersion devOvsVersion;
220
221 String ovsVersionStr = fetchLastTerm(session, "ovs-vswitchd --version");
222 if (ovsVersionStr == null) {
223 log.error("Failed to get ovs Version String for ssh session:{}", session);
224 throw new WorkflowException("Failed to get ovs Version String");
225 }
226
227 devOvsVersion = OvsVersion.build(ovsVersionStr);
228 if (devOvsVersion == null) {
229 log.error("Failed to build OVS version for {}", ovsVersionStr);
230 throw new WorkflowException("Failed to build OVS version");
231 }
232
233 return devOvsVersion;
234 }
235
236 private static final String IP_PATTERN = "^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
237 "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
238 "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
239 "([01]?\\d\\d?|2[0-4]\\d|25[0-5])$";
240
241 private static boolean isIPv6(String address) {
242 boolean isCorrect = true;
243 try {
244 IpAddress.valueOf(address);
245 } catch (IllegalArgumentException e) {
246 log.debug("Exception Occurred {}", e.toString());
247 isCorrect = false;
248 }
249 return isCorrect;
250 }
251
252 private static boolean isCidr(String s) {
253 String[] splits = s.split("/");
254 return splits.length == 2 &&
255 (splits[0].matches(IP_PATTERN) || isIPv6(splits[0]));
256 }
257
258 /**
259 * Adds IP address on the interface.
260 * @param session SSH session
261 * @param ifname interface name
262 * @param address network address
263 * @throws WorkflowException workflow exception
264 */
265 public static void addIpAddrOnInterface(Session session, String ifname, NetworkAddress address)
266 throws WorkflowException {
267
268 executeCommand(session, String.format("ip addr add %s dev %s", address.cidr(), ifname));
269
270 Set<NetworkAddress> result = getIpAddrOfInterface(session, ifname);
271 if (!result.contains(address)) {
272 throw new WorkflowException("Failed to set ip(" + address + ") on " + ifname + ", result: " + result);
273 }
274 }
275
276 /**
277 * Gets IP addresses of interface.
278 * @param session SSH session
279 * @param ifname interface name
280 * @return IP addresses of interface
281 */
282 public static Set<NetworkAddress> getIpAddrOfInterface(Session session, String ifname) {
283
284 String output = executeCommand(session, String.format("ip addr show %s", ifname));
285
286 if (output == null) {
287 return Collections.emptySet();
288 }
289
290 Set<NetworkAddress> result = Pattern.compile(" ")
291 .splitAsStream(output)
292 .filter(SshUtil::isCidr)
293 .map(NetworkAddress::valueOf)
294 .collect(Collectors.toSet());
295 return result;
296 }
297
298 /**
299 * Returns whether the interface has IP address.
300 * @param session SSH session
301 * @param ifname interface name
302 * @param addr network address
303 * @return whether the interface has IP address
304 */
305 public static boolean hasIpAddrOnInterface(Session session, String ifname, NetworkAddress addr) {
306
307 Set<NetworkAddress> phyBrIps = getIpAddrOfInterface(session, ifname);
308
309 return phyBrIps.stream()
310 .anyMatch(ip -> addr.ip().equals(ip.ip()));
311 }
312
313 /**
314 * Sets IP link UP on the interface.
315 * @param session SSH session
316 * @param ifname interface name
317 * @throws WorkflowException workflow exception
318 */
319 public static void setIpLinkUpOnInterface(Session session, String ifname)
320 throws WorkflowException {
321
322 executeCommand(session, String.format("ip link set %s up", ifname));
323
324 if (!isIpLinkUpOnInterface(session, ifname)) {
325 throw new WorkflowException("Failed to set UP on " + ifname);
326 }
327 }
328
329 /**
330 * Returns whether the link of the interface is up.
331 * @param session SSH session
332 * @param ifname interface name
333 * @return whether the link of the interface is up
334 */
335 public static boolean isIpLinkUpOnInterface(Session session, String ifname) {
336 String output = executeCommand(session, String.format("ip link show %s", ifname));
337
338 return output != null && output.contains("UP");
339 }
340
341 /**
342 * Executes SSH behavior.
343 * @param sshAccessInfo SSH Access information
344 * @param behavior SSH behavior
345 * @param <R> Return type of SSH behavior
346 * @return return of SSH behavior
347 * @throws WorkflowException workflow exception
348 */
349 public static <R> R exec(SshAccessInfo sshAccessInfo, SshBehavior<R> behavior)
350 throws WorkflowException {
351
352 check(sshAccessInfo != null, "Invalid sshAccessInfo");
353 Session session = connect(sshAccessInfo);
354 if (session == null || !session.isConnected()) {
355 log.error("Failed to get session for ssh:{}", sshAccessInfo);
356 throw new WorkflowException("Failed to get session for ssh:" + sshAccessInfo);
357 }
358
359 try {
360 return behavior.apply(session);
361 } finally {
362 disconnect(session);
363 }
364 }
365
366}