/*********************************************************************************************
* Copyright (c) 2014-2015 Software Behaviour Analysis Lab, Concordia University, Montreal, Canada
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of Eclipse Public License v1.0 License which
* accompanies this distribution, and is available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Syed Shariyar Murtaza -- Initial design and implementation
**********************************************************************************************/
package org.eclipse.tracecompass.internal.totalads.ssh;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import org.eclipse.osgi.util.NLS;
import org.eclipse.tracecompass.totalads.algorithms.IAlgorithmOutStream;
import org.eclipse.tracecompass.totalads.exceptions.TotalADSNetException;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;
import com.jcraft.jsch.UserInfo;
/**
* This class connects to a remote system using SSH and collects an LTTng trace
* Some of the code in this class is extracted from the following link:
* http://www.jcraft.com/fJsch/examples/Sudo.java.html
*
* @author <p>
* Syed Shariyar Murtaza justsshary@hotmail.com
* </p>
*
*/
public class SSHConnector {
private JSch fJsch;
private Integer fPort;
private UserInfo fUi;
private String fUser;
private String fHost;
private Session fSession;
private IAlgorithmOutStream fOutStream;
private Integer fSnapshotDuration;
/**
* Constructor
*/
public SSHConnector() {
fJsch = new JSch();
}
/**
* Returns the path of the downloaded LTTng trace from the remote system
*
* @return The path of a trace
*
* public String getTrace(){ return fTotalADSLocalDir; }
*/
/**
* Opens an SSH connection using a password, executes LTTng commands on a
* remote system and
*
* @param userAtHost
* User and host as user@hostname
* @param password
* Password
* @param portToConnect
* Port
* @param outStream
* An object which will print the output
* @param snapshotDurationInSeconds
* Snapshot duration in seconds
* @throws TotalADSNetException
* A network related exception
*/
public void openSSHConnectionUsingPassword(String userAtHost, String password, Integer portToConnect,
IAlgorithmOutStream outStream, Integer snapshotDurationInSeconds) throws TotalADSNetException {
try {
fPort = portToConnect;
fUser = userAtHost.substring(0, userAtHost.indexOf('@'));
fHost = userAtHost.substring(userAtHost.indexOf('@') + 1);
this.fOutStream = outStream;
this.fSnapshotDuration = snapshotDurationInSeconds;
// password will be given via UserInfo interface.
fUi = new UserInfoSSH(password, outStream);
fSession = fJsch.getSession(fUser, fHost, fPort);
fSession.setUserInfo(fUi);
fSession.connect();
outStream.addOutputEvent(Messages.SSHConnector_ConnectionEstablish);
outStream.addNewLine();
} catch (JSchException e) {
throw new TotalADSNetException(Messages.SSHConnector_CommunicationError + e.getMessage());
}
}
/**
* Connects with an SSH server using a private Key file present on the hard
* disk (Public name should be present on the server)
*
* @param userAtHost
* User and host (user@host)
* @param pathToPrivateKey
* Private Key
* @param portToConnect
* Port
* @param outStream
* An outStream which will print the output
* @param snapshotDurationInSeconds
* Snapshot Duration
* @throws TotalADSNetException
* A network related exception
*/
public void openSSHConnectionUsingPrivateKey(String userAtHost, String pathToPrivateKey, Integer portToConnect,
IAlgorithmOutStream outStream, Integer snapshotDurationInSeconds) throws TotalADSNetException {
try {
fPort = portToConnect;
fUser = userAtHost.substring(0, userAtHost.indexOf('@'));
fHost = userAtHost.substring(userAtHost.indexOf('@') + 1);
this.fOutStream = outStream;
this.fSnapshotDuration = snapshotDurationInSeconds;
fJsch.addIdentity(pathToPrivateKey);
outStream.addOutputEvent(Messages.SSHConnector_IdentityAdded);
outStream.addNewLine();
fSession = fJsch.getSession(fUser, fHost, fPort);
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no"); //$NON-NLS-1$ //$NON-NLS-2$
fSession.setConfig(config);
fSession.setUserInfo(fUi);
fSession.connect();
outStream.addOutputEvent(Messages.SSHConnector_ConnectionEstablish);
outStream.addNewLine();
} catch (JSchException e) {
throw new TotalADSNetException(Messages.SSHConnector_CommunicationError + e.getMessage());
}
}
/**
*
* @param traceStorageDir
* Directory to store traces
* @return Path to a trace
* @throws TotalADSNetException
* Network exception
*/
public String collectATrace(String traceStorageDir) throws TotalADSNetException {
String totalADSRemoteDir = "/tmp/totalads"; //$NON-NLS-1$
String totalADSRemoteTrace = totalADSRemoteDir + "/kernel/"; //$NON-NLS-1$
String sessionName = "totalads-trace-" + getCurrentTimeStamp(); //$NON-NLS-1$
// If an exception occurs, don't execute further commands and let the
// exception be thrown
executeCommand("rm -rf " + totalADSRemoteTrace); //$NON-NLS-1$
executeCommand("mkdir -p " + totalADSRemoteDir); //$NON-NLS-1$
executeCommand("lttng create " + sessionName + " -o " + totalADSRemoteDir); //$NON-NLS-1$ //$NON-NLS-2$
executeCommand("lttng enable-event -a -k"); //$NON-NLS-1$
executeCommand("lttng start"); //$NON-NLS-1$
// Wait for these many seconds and then stop the trace
try {
fOutStream.addOutputEvent(NLS.bind(Messages.SSHConnector_TraceDuration, fSnapshotDuration));
fOutStream.addNewLine();
TimeUnit.SECONDS.sleep(fSnapshotDuration);
} catch (InterruptedException ee) {
}
executeCommand("lttng stop"); //$NON-NLS-1$
executeCommand("lttng destroy " + sessionName); //$NON-NLS-1$
String trace = "trace-" + getCurrentTimeStamp(); //$NON-NLS-1$
File localDir = new File(traceStorageDir + File.separator + trace);
localDir.mkdir();
downloadTrace(fSession, totalADSRemoteTrace, localDir.getPath());
executeCommand("rm -rf " + totalADSRemoteTrace); //$NON-NLS-1$
return localDir.getPath();
}
/**
* Executes a command
*
* @param command
* Command to execute
*
*/
private void executeCommand(String command) throws TotalADSNetException {
Channel channel = null;
String msg = null;
try {
channel = fSession.openChannel("exec"); //$NON-NLS-1$
((ChannelExec) channel).setCommand(command);
((ChannelExec) channel).setErrStream(System.err);
try (InputStream in = channel.getInputStream(); OutputStream out = channel.getOutputStream()) {
channel.connect();
displayStream(in, channel);
}
} catch (IOException e) {
fOutStream.addOutputEvent(Messages.SSHConnector_Error + e.getMessage());
fOutStream.addNewLine();
msg = e.getMessage();
} catch (JSchException e) {
fOutStream.addOutputEvent(Messages.SSHConnector_Error + e.getMessage());
fOutStream.addNewLine();
msg = e.getMessage();
} finally {
if (channel != null) {
channel.disconnect();
}
}
if (msg != null)
{
throw new TotalADSNetException(msg);// Don't continue further
}
}
/**
* Executes LTTng commands on a remote system and downloads the trace in a
* local folder
*
* @param sudoPassword
* Sudo password
* @param traceStorageDir
* Trace directory
* @return Path of the trace
* @throws TotalADSNetException
* A network related exception
*/
public String collectATrace(String sudoPassword, String traceStorageDir) throws TotalADSNetException {
String totalADSRemoteDir = "/tmp/totalads"; //$NON-NLS-1$
String totalADSRemoteTrace = totalADSRemoteDir + "/kernel/"; //$NON-NLS-1$
String sessionName = "totalads-trace-" + getCurrentTimeStamp(); //$NON-NLS-1$
// If an exception occurs, don't execute further commands and let the
// exception be thrown
executeSudoCommand("sudo -S -p '' rm -rf " + totalADSRemoteTrace, sudoPassword); //$NON-NLS-1$
executeSudoCommand("sudo -S -p '' mkdir -p " + totalADSRemoteDir, sudoPassword); //$NON-NLS-1$
executeSudoCommand("sudo -S -p '' lttng create " + sessionName + " -o " + totalADSRemoteDir, sudoPassword); //$NON-NLS-1$ //$NON-NLS-2$
executeSudoCommand("sudo -S -p '' lttng enable-event -a -k", sudoPassword); //$NON-NLS-1$
executeSudoCommand("sudo -S -p '' lttng start", sudoPassword); //$NON-NLS-1$
// Wait for these many seconds and then stop the trace
try {
fOutStream.addOutputEvent(NLS.bind(Messages.SSHConnector_TraceDuration, fSnapshotDuration));
fOutStream.addNewLine();
TimeUnit.SECONDS.sleep(fSnapshotDuration);
} catch (InterruptedException ee) {
}
executeSudoCommand("sudo -S -p '' lttng stop", sudoPassword); //$NON-NLS-1$
executeSudoCommand("sudo -S -p '' lttng destroy " + sessionName, sudoPassword); //$NON-NLS-1$
executeSudoCommand("sudo -S -p '' chmod -R 777 " + totalADSRemoteDir, sudoPassword); //$NON-NLS-1$
String trace = "trace-" + getCurrentTimeStamp(); //$NON-NLS-1$
File localDir = new File(traceStorageDir + File.separator + trace);
localDir.mkdir();
downloadTrace(fSession, totalADSRemoteTrace, localDir.getPath());
executeSudoCommand("sudo -S -p '' rm -rf " + totalADSRemoteTrace, sudoPassword); //$NON-NLS-1$
return localDir.getPath();
}
/**
* Executes a sudo (root) command
*
* @param command
* @param sudoPass
*
*/
private void executeSudoCommand(String command, String sudoPass) throws TotalADSNetException {
Channel channel = null;
String msg = null;
try {
channel = fSession.openChannel("exec"); //$NON-NLS-1$
((ChannelExec) channel).setCommand(command);
((ChannelExec) channel).setErrStream(System.err);
try (InputStream in = channel.getInputStream();
OutputStream out = channel.getOutputStream();) {
channel.connect();
out.write((sudoPass + "\n").getBytes()); //$NON-NLS-1$
out.flush();
displayStream(in, channel);
}
} catch (IOException e) {
fOutStream.addOutputEvent(Messages.SSHConnector_Error + e.getMessage());
fOutStream.addNewLine();
msg = e.getMessage();
} catch (JSchException e) {
fOutStream.addOutputEvent(Messages.SSHConnector_Error + e.getMessage());
fOutStream.addNewLine();
msg = e.getMessage();
} finally {
if (channel != null) {
channel.disconnect();
}
}
if (msg != null)
{
throw new TotalADSNetException(msg);// Don't continue further
}
}
/**
* Display the output of a command on a remote system
*
* @param in
* @param channel
* @throws IOException
*/
private void displayStream(InputStream in, Channel channel) throws IOException {
byte[] tmp = new byte[1024];
while (true) {
while (in.available() > 0) {
int i = in.read(tmp, 0, 1024);
if (i < 0) {
break;
}
// System.out.print(new String(tmp, 0, i));
fOutStream.addOutputEvent(new String(tmp, 0, i));
fOutStream.addNewLine();
}
if (channel.isClosed()) {
if (in.available() > 0) {
continue;
}
fOutStream.addOutputEvent(Messages.SSHConnector_ExitStatus + channel.getExitStatus());
fOutStream.addNewLine();
break;
}
try {
Thread.sleep(1000); // Wait for some time to get more data over
// network stream
} catch (Exception ex) {
}
}
}
/**
* This functions downloads the trace collected at the remote system
*
* @param fSession
* @param remoteFolder
* @throws IOException
*/
private void downloadTrace(Session session, String remoteFolder, String localDownloadFolder) throws TotalADSNetException {
ChannelSftp sftpChannel = null;
try {
sftpChannel = (ChannelSftp) session.openChannel("sftp"); //$NON-NLS-1$
sftpChannel.connect();
downloadRecursively(sftpChannel, localDownloadFolder, remoteFolder);
} catch (SftpException e) {
fOutStream.addOutputEvent(Messages.SSHConnector_Error + e.getCause().getMessage()); // Exception
// printed
fOutStream.addNewLine();
throw new TotalADSNetException(e);// Don't continue further
} catch (JSchException e) {
fOutStream.addOutputEvent(Messages.SSHConnector_Error + e.getCause().getMessage()); // Exception
// printed
fOutStream.addNewLine();
throw new TotalADSNetException(e);// Don't continue further
} finally {
if (sftpChannel != null) {
sftpChannel.exit();
}
}
}
/**
* Recursively downloads folders, sub folders and all the files in it
*
* @param sftpChannel
* @param localDownloadFolder
* @param remoteFolder
* @throws SftpException
*/
private void downloadRecursively(ChannelSftp sftpChannel, String localDownloadFolder, String remoteFolder) throws SftpException {
sftpChannel.cd(remoteFolder);
java.util.Vector<ChannelSftp.LsEntry> list = sftpChannel.ls("*"); //$NON-NLS-1$
for (ChannelSftp.LsEntry entry : list) {
fOutStream.addOutputEvent(Messages.SSHConnector_ProcessRemote + entry.getFilename()); // actually
// Downloading
fOutStream.addNewLine();
if (entry.getAttrs().isDir()) {// check for a folder
fOutStream.addOutputEvent(Messages.SSHConnector_Downloading + entry.getFilename());
fOutStream.addNewLine();
File subDir = new File(localDownloadFolder + File.separator + entry.getFilename());
subDir.mkdir();
String subRemoteDir = remoteFolder + File.separator + entry.getFilename();
// recursively download all the contents of sub folders
downloadRecursively(sftpChannel, subDir.getPath(), subRemoteDir);
sftpChannel.cd(remoteFolder); // Change back to current
// directory
} else {
sftpChannel.get(entry.getFilename(), localDownloadFolder + File.separator + entry.getFilename());
}
}
}
/**
* Closes the SSH connection and clears the trace from the local drive
*/
public void close() {
// deleteFolderContents(new File(this.totalADSLocalDir));
fSession.disconnect();
}
/**
* Get current time stamp
*/
private static String getCurrentTimeStamp() {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); //$NON-NLS-1$
// get current date time with Date()
Date date = new Date();
return dateFormat.format(date);
}
}