|
@@ -0,0 +1,203 @@
|
|
|
+/**
|
|
|
+ * Licensed to the Apache Software Foundation (ASF) under one
|
|
|
+ * or more contributor license agreements. See the NOTICE file
|
|
|
+ * distributed with this work for additional information
|
|
|
+ * regarding copyright ownership. The ASF licenses this file
|
|
|
+ * to you 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.apache.hadoop.util;
|
|
|
+
|
|
|
+import com.jcraft.jsch.*;
|
|
|
+import org.apache.commons.logging.LogFactory;
|
|
|
+import org.apache.commons.logging.Log;
|
|
|
+
|
|
|
+import java.io.BufferedReader;
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.InputStreamReader;
|
|
|
+import java.util.Properties;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Remote Execution of commands on a remote machine.
|
|
|
+ */
|
|
|
+
|
|
|
+public class SSHRemoteExecution implements RemoteExecution {
|
|
|
+
|
|
|
+ static final Log LOG = LogFactory.getLog(SSHRemoteExecution.class);
|
|
|
+ static final int SSH_PORT = 22;
|
|
|
+ static final String DEFAULT_IDENTITY="id_dsa";
|
|
|
+ static final String DEFAULT_KNOWNHOSTS="known_hosts";
|
|
|
+ static final String FS = System.getProperty("file.separator");
|
|
|
+ static final String LS = System.getProperty("line.separator");
|
|
|
+ private int exitCode;
|
|
|
+ private StringBuffer output;
|
|
|
+ private String commandString;
|
|
|
+
|
|
|
+ final StringBuffer errorMessage = new StringBuffer();
|
|
|
+ public SSHRemoteExecution() throws Exception {
|
|
|
+ }
|
|
|
+
|
|
|
+ protected String getHomeDir() {
|
|
|
+ String currentUser=System.getProperty("user.name");
|
|
|
+ String userHome=System.getProperty("user.home");
|
|
|
+
|
|
|
+ return userHome.substring(0, userHome.indexOf(currentUser)-1);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Execute command at remote host under given user
|
|
|
+ * @param remoteHostName remote host name
|
|
|
+ * @param user is the name of the user to be login under;
|
|
|
+ * current user will be used if this is set to <code>null</code>
|
|
|
+ * @param command to be executed remotely
|
|
|
+ * @param identityFile is the name of alternative identity file; default
|
|
|
+ * is ~user/.ssh/id_dsa
|
|
|
+ * @param portNumber remote SSH daemon port number, default is 22
|
|
|
+ * @throws Exception in case of errors
|
|
|
+ */
|
|
|
+ public void executeCommand (String remoteHostName, String user,
|
|
|
+ String command, String identityFile, int portNumber) throws Exception {
|
|
|
+ commandString = command;
|
|
|
+ String sessionUser = System.getProperty("user.name");
|
|
|
+ String userHome=System.getProperty("user.home");
|
|
|
+ if (user != null) {
|
|
|
+ sessionUser = user;
|
|
|
+ userHome = getHomeDir() + FS + user;
|
|
|
+ }
|
|
|
+ String dotSSHDir = userHome + FS + ".ssh";
|
|
|
+ String sessionIdentity = dotSSHDir + FS + DEFAULT_IDENTITY;
|
|
|
+ if (identityFile != null) {
|
|
|
+ sessionIdentity = identityFile;
|
|
|
+ }
|
|
|
+
|
|
|
+ JSch jsch = new JSch();
|
|
|
+
|
|
|
+ Session session = jsch.getSession(sessionUser, remoteHostName, portNumber);
|
|
|
+ jsch.setKnownHosts(dotSSHDir + FS + DEFAULT_KNOWNHOSTS);
|
|
|
+ jsch.addIdentity(sessionIdentity);
|
|
|
+
|
|
|
+ Properties config = new Properties();
|
|
|
+ config.put("StrictHostKeyChecking", "no");
|
|
|
+ session.setConfig(config);
|
|
|
+
|
|
|
+ session.connect(30000); // making a connection with timeout.
|
|
|
+
|
|
|
+ Channel channel=session.openChannel("exec");
|
|
|
+ ((ChannelExec)channel).setCommand(command);
|
|
|
+ channel.setInputStream(null);
|
|
|
+
|
|
|
+ final BufferedReader errReader =
|
|
|
+ new BufferedReader(
|
|
|
+ new InputStreamReader(((ChannelExec)channel).getErrStream()));
|
|
|
+ BufferedReader inReader =
|
|
|
+ new BufferedReader(new InputStreamReader(channel.getInputStream()));
|
|
|
+
|
|
|
+ channel.connect();
|
|
|
+ Thread errorThread = new Thread() {
|
|
|
+ @Override
|
|
|
+ public void run() {
|
|
|
+ try {
|
|
|
+ String line = errReader.readLine();
|
|
|
+ while((line != null) && !isInterrupted()) {
|
|
|
+ errorMessage.append(line);
|
|
|
+ errorMessage.append(LS);
|
|
|
+ line = errReader.readLine();
|
|
|
+ }
|
|
|
+ } catch(IOException ioe) {
|
|
|
+ LOG.warn("Error reading the error stream", ioe);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ try {
|
|
|
+ errorThread.start();
|
|
|
+ } catch (IllegalStateException e) {
|
|
|
+ LOG.debug(e);
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ parseExecResult(inReader);
|
|
|
+ String line = inReader.readLine();
|
|
|
+ while (line != null) {
|
|
|
+ line = inReader.readLine();
|
|
|
+ }
|
|
|
+
|
|
|
+ if(channel.isClosed()) {
|
|
|
+ exitCode = channel.getExitStatus();
|
|
|
+ LOG.debug("exit-status: " + exitCode);
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ // make sure that the error thread exits
|
|
|
+ errorThread.join();
|
|
|
+ } catch (InterruptedException ie) {
|
|
|
+ LOG.warn("Interrupted while reading the error stream", ie);
|
|
|
+ }
|
|
|
+ } catch (Exception ie) {
|
|
|
+ throw new IOException(ie.toString());
|
|
|
+ }
|
|
|
+ finally {
|
|
|
+ try {
|
|
|
+ inReader.close();
|
|
|
+ } catch (IOException ioe) {
|
|
|
+ LOG.warn("Error while closing the input stream", ioe);
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ errReader.close();
|
|
|
+ } catch (IOException ioe) {
|
|
|
+ LOG.warn("Error while closing the error stream", ioe);
|
|
|
+ }
|
|
|
+ channel.disconnect();
|
|
|
+ session.disconnect();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Execute command at remote host under given username
|
|
|
+ * Default identity is ~/.ssh/id_dsa key will be used
|
|
|
+ * Default known_hosts file is ~/.ssh/known_hosts will be used
|
|
|
+ * @param remoteHostName remote host name
|
|
|
+ * @param user is the name of the user to be login under;
|
|
|
+ * if equals to <code>null</code> then current user name will be used
|
|
|
+ * @param command to be executed remotely
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void executeCommand (String remoteHostName, String user,
|
|
|
+ String command) throws Exception {
|
|
|
+ executeCommand(remoteHostName, user, command, null, SSH_PORT);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int getExitCode() {
|
|
|
+ return exitCode;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected void parseExecResult(BufferedReader lines) throws IOException {
|
|
|
+ output = new StringBuffer();
|
|
|
+ char[] buf = new char[512];
|
|
|
+ int nRead;
|
|
|
+ while ( (nRead = lines.read(buf, 0, buf.length)) > 0 ) {
|
|
|
+ output.append(buf, 0, nRead);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Get the output of the ssh command.*/
|
|
|
+ @Override
|
|
|
+ public String getOutput() {
|
|
|
+ return (output == null) ? "" : output.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Get the String representation of ssh command */
|
|
|
+ @Override
|
|
|
+ public String getCommandString() {
|
|
|
+ return commandString;
|
|
|
+ }
|
|
|
+}
|