HADOOP-6879. Provide SSH based (Jsch) remote execution API for system tests. Contributed by Konstantin Boudnik.
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1004118 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
4f2c6bc4b2
commit
95649aca30
|
@ -255,6 +255,9 @@ Trunk (unreleased changes)
|
||||||
HADOOP-6951. Distinct minicluster services (e.g. NN and JT) overwrite each
|
HADOOP-6951. Distinct minicluster services (e.g. NN and JT) overwrite each
|
||||||
other's service policies. (Aaron T. Myers via tomwhite)
|
other's service policies. (Aaron T. Myers via tomwhite)
|
||||||
|
|
||||||
|
HADOOP-6879. Provide SSH based (Jsch) remote execution API for system
|
||||||
|
tests (cos)
|
||||||
|
|
||||||
Release 0.21.0 - Unreleased
|
Release 0.21.0 - Unreleased
|
||||||
|
|
||||||
INCOMPATIBLE CHANGES
|
INCOMPATIBLE CHANGES
|
||||||
|
|
5
ivy.xml
5
ivy.xml
|
@ -296,5 +296,10 @@
|
||||||
rev="${mockito-all.version}"
|
rev="${mockito-all.version}"
|
||||||
conf="common->default">
|
conf="common->default">
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency org="com.jcraft"
|
||||||
|
name="jsch"
|
||||||
|
rev="${jsch.version}"
|
||||||
|
conf="common->default">
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</ivy-module>
|
</ivy-module>
|
||||||
|
|
|
@ -79,3 +79,5 @@ aspectj.version=1.6.5
|
||||||
|
|
||||||
mockito-all.version=1.8.2
|
mockito-all.version=1.8.2
|
||||||
|
|
||||||
|
jsch.version=0.1.42
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
public interface RemoteExecution {
|
||||||
|
public void executeCommand (String remoteHostName, String user,
|
||||||
|
String command) throws Exception;
|
||||||
|
public int getExitCode();
|
||||||
|
public String getOutput();
|
||||||
|
public String getCommandString();
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/**
|
||||||
|
* 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 static org.junit.Assert.assertEquals;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class TestSSHRemoteExecution {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
/**
|
||||||
|
* Method: executeCommand(String remoteHostName, String user, String command)
|
||||||
|
*/
|
||||||
|
public void testExecuteCommandForRemoteHostNameUserCommand() throws Exception {
|
||||||
|
String command = "ls -l /bin";
|
||||||
|
SSHRemoteExecution sshRE = new SSHRemoteExecution();
|
||||||
|
sshRE.executeCommand("localhost", null, "ls -l /bin");
|
||||||
|
System.out.println(sshRE.getOutput());
|
||||||
|
assertEquals("Exit code should is expected to be 0", sshRE.getExitCode(), 0);
|
||||||
|
assertEquals("Mismatched command string", sshRE.getCommandString(), command);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
/**
|
||||||
|
* Method: getHomeDir()
|
||||||
|
*/
|
||||||
|
public void testGetHomeDir() throws Exception {
|
||||||
|
SSHRemoteExecution sshRE = new SSHRemoteExecution();
|
||||||
|
String ret = sshRE.getHomeDir();
|
||||||
|
assertEquals(System.getProperty("user.home"),
|
||||||
|
ret + System.getProperty("file.separator") +
|
||||||
|
System.getProperty("user.name"));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue