/***************************************************************************
* Copyright (c) 2013 VMware, Inc. All Rights Reserved.
* Licensed 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.
***************************************************************************/
/***************************************************************************
* Copyright (c) 2012 VMware, Inc. All Rights Reserved.
* Licensed 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 com.vmware.vhadoop.adaptor.hadoop;
import static com.vmware.vhadoop.adaptor.hadoop.HadoopErrorCodes.SUCCESS;
import static com.vmware.vhadoop.adaptor.hadoop.HadoopErrorCodes.UNKNOWN_ERROR;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.UserInfo;
import com.vmware.vhadoop.adaptor.hadoop.HadoopConnection.HadoopCredentials;
import com.vmware.vhadoop.adaptor.hadoop.HadoopConnection.SshUtils;
/**
* An implementation of SshUtils defined by HadoopConnection
* This is marked clearly as being not thread-safe,
* so instances of this class should not be shared by multiple HadoopConnections
*
*/
public class NonThreadSafeSshUtils implements SshUtils {
private JSch _jsch = new JSch();
private static final String SCP_COMMAND = "scp -t ";
private static final int INPUTSTREAM_TIMEOUT = 100;
@Override
public ChannelExec createChannel(Logger logger, final HadoopCredentials credentials, String host, int port) {
try {
Session session = _jsch.getSession(credentials.getSshUsername(), host, port);
// If private key file is specified, use that as identity; else use password.
String prvkeyFile = credentials.getSshPrvkeyFile();
if (prvkeyFile != null) {
_jsch.addIdentity(prvkeyFile); //Setup SSH identity using private key file
} else {
session.setPassword(credentials.getSshPassword());
UserInfo ui = new SSHUserInfo(credentials.getSshPassword());
session.setUserInfo(ui);
}
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no"); /* TODO: Necessary? Hack? Security hole?? */
session.setConfig(config);
// JG: Adding session timeout...
session.setTimeout(15000);
session.connect();
return (ChannelExec)session.openChannel("exec");
} catch (JSchException e) {
logger.log(Level.SEVERE, "Could not create ssh channel (e.g., wrong ip addr/username/password/prvkey) on host "+host, e);
return null;
}
}
@Override
public int exec(Logger logger, ChannelExec channel, OutputStream out, String command) {
int exitStatus = UNKNOWN_ERROR;
try {
logger.log(Level.FINE, "About to execute: " + command);
// Executing all commands as root
channel.setPty(true); //to enable sudo
channel.setCommand("sudo " + command);
channel.setOutputStream(out);
channel.setInputStream(null); /* TODO: Why? */
InputStream in = channel.getInputStream();
channel.connect();
logger.log(Level.FINE, "Finished channel connection in exec");
if (!testChannel(logger, channel)) {
return UNKNOWN_ERROR; /* TODO: Improve */
}
int numSecs = 0;
byte[] tmp = new byte[1024];
while (true) {
while (in.available() > 0) {
int i = in.read(tmp, 0, 1024);
if (i < 0)
break;
out.write(tmp);
}
if (channel.isClosed()) {
exitStatus = channel.getExitStatus();
logger.log(Level.SEVERE, "Exit status from exec is: " + channel.getExitStatus());
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
logger.log(Level.SEVERE, "Excpetion while sleeping in exec");
}
numSecs++;
if (numSecs == INPUTSTREAM_TIMEOUT) {
logger.log(Level.SEVERE, "No input was received for " + numSecs + "s while running remote exec!");
break;
}
}
} catch (JSchException e) {
logger.log(Level.SEVERE, "Unexpected JSch exception executing over SSH", e);
} catch (IOException e) {
logger.log(Level.SEVERE, "Unexpected IOException executing over SSH", e);
}
logger.log(Level.FINE, "Exit status from exec is: " + exitStatus);
return exitStatus;
}
@Override
public int scpBytes(Logger logger, ChannelExec channel, byte[] data,
String remotePath, String remoteFileName, String perms) {
try {
channel.setCommand(SCP_COMMAND + remotePath + remoteFileName);
OutputStream out = channel.getOutputStream();
InputStream in = channel.getInputStream();
channel.connect();
if (!testChannel(logger, channel)) {
return UNKNOWN_ERROR; /* TODO: Improve */
}
if (!waitForInputStream(logger, in)) {
return UNKNOWN_ERROR; /* TODO: Improve */
}
// send "C$perms filesize filename", where filename should not include
StringBuilder params = new StringBuilder("C0").append(perms);
params.append(" ").append(data.length).append(" ");
params.append(remoteFileName).append("\n");
out.write(params.toString().getBytes());
out.flush();
if (!waitForInputStream(logger, in)) {
cleanup(logger, out, channel);
logger.log(Level.SEVERE, "Error before writing SCP bytes");
return UNKNOWN_ERROR; /* TODO: Improve */
}
out.write(data);
out.write(new byte[]{0}, 0, 1);
out.flush();
if (!waitForInputStream(logger, in)) {
logger.log(Level.SEVERE, "Error after writing SCP bytes");
cleanup(logger, out, channel);
return -1;
}
out.close();
} catch (Exception e) {
logger.log(Level.SEVERE, "Unexpected Exception copying data to Job Tracker", e);
}
return SUCCESS;
}
private boolean waitForInputStream(Logger log, InputStream in) throws IOException {
int b = in.read();
if (b <= 0) {
boolean result = (b == 0);
if (!result) {
log.log(Level.SEVERE, "SSH Channel failed test. First byte returned < 0 result");
}
return result;
} else {
StringBuffer sb = new StringBuffer();
int c;
do {
c = in.read();
sb.append((char) c);
} while (c != '\n');
log.log(Level.SEVERE, "SSH Channel failed test. Msg="+sb.toString());
return false;
}
}
@Override
public boolean testChannel(Logger log, ChannelExec channel) {
if (channel == null) {
return false;
}
if (channel.isConnected()) {
return true;
}
try {
channel.connect();
} catch (JSchException e) {
log.log(Level.SEVERE, "SSH Channel failed test. Could not connect", e);
}
return channel.isConnected();
}
@Override
public void cleanup(Logger log, OutputStream out, ChannelExec channel) {
Session session = null;
if (out != null) {
try {
out.flush();
out.close();
} catch (IOException e) {
log.log(Level.WARNING, "Unexpected exception in SSH OutputStream cleanup", e);
}
}
if (channel != null && channel.isConnected()) {
try {
session = channel.getSession();
} catch (JSchException e) {
log.log(Level.WARNING, "Unexpected exception in SSH channel cleanup", e);
}
channel.disconnect();
}
if (session != null && session.isConnected()) {
session.disconnect();
}
}
private class SSHUserInfo implements UserInfo {
String _password;
public SSHUserInfo(String password) {
_password = password;
}
@Override
public String getPassword() {
return _password;
}
@Override
public String getPassphrase() {
return null;
}
@Override
public boolean promptPassphrase(String arg0) {
return false;
}
@Override
public boolean promptPassword(String arg0) {
return false;
}
@Override
public boolean promptYesNo(String arg0) {
return false;
}
@Override
public void showMessage(String arg0) {
System.out.println(arg0);
}
}
}