У меня есть Java-программа, которая работает на Tomcat и должна выполнять несколько команд ssh и scp, а также несколько простых команд, таких как ls, на локальном компьютере. У меня возникли проблемы с моим текущим подходом в том, что я получаю время ожидания каждый раз, когда я выполняю команду ssh. Я могу без проблем запустить команду ssh в командной строке, но когда она запускается из моей программы на Java, время ожидания истекает. Я запускаю веб-приложение, в котором команды ssh выполняются от имени пользователя root (т. Е. Я запускаю Tomcat от имени пользователя root, а код моего веб-приложения развертывается в виде файла WAR), и, насколько я знаю, правильные ключи сертификации настроены на обоих локальные и удаленные машины, по крайней мере, я могу выполнять команды ssh в командной строке от имени пользователя root без необходимости вводить имя пользователя или пароль. Я не указываю имя пользователя или пароль в команде ssh, которая выполняется моей программой Java, так как я предполагаю, что я могу выполнить ту же команду ssh в своем коде Java, которую я могу выполнить в командной строке, но, возможно, это неверно предположение и является причиной моей неприятности.
Java-код, который я разработал для выполнения команд, выглядит следующим образом:
public class ProcessUtility
{
static Log log = LogFactory.getLog(ProcessUtility.class);
/**
* Thread class to be used as a worker
*/
private static class Worker
extends Thread
{
private final Process process;
private volatile Integer exitValue;
Worker(final Process process)
{
this.process = process;
}
public Integer getExitValue()
{
return exitValue;
}
@Override
public void run()
{
try
{
exitValue = process.waitFor();
}
catch (InterruptedException ignore)
{
return;
}
}
}
/**
* Executes a command.
*
* @param args command + arguments
*/
public static void execCommand(final String[] args)
{
try
{
Runtime.getRuntime().exec(args);
}
catch (IOException e)
{
// swallow it
}
}
/**
* Executes a command.
*
* @param command
* @param printOutput
* @param printError
* @param timeOut
* @return
* @throws java.io.IOException
* @throws java.lang.InterruptedException
*/
public static int executeCommand(final String command,
final boolean printOutput,
final boolean printError,
final long timeOut)
{
return executeCommandWithWorker(command, printOutput, printError, timeOut);
}
/**
* Executes a command and returns its output or error stream.
*
* @param command
* @return the command's resulting output or error stream
*/
public static String executeCommandReceiveOutput(final String command)
{
try
{
// create the process which will run the command
Runtime runtime = Runtime.getRuntime();
final Process process = runtime.exec(command);
try
{
// consume the error and output streams
StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(), "OUTPUT", false);
StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), "ERROR", false);
outputGobbler.start();
errorGobbler.start();
// execute the command
if (process.waitFor() == 0)
{
return outputGobbler.getInput();
}
return errorGobbler.getInput();
}
finally
{
process.destroy();
}
}
catch (InterruptedException ex)
{
String errorMessage = "The command [" + command + "] did not complete due to an unexpected interruption.";
log.error(errorMessage, ex);
throw new RuntimeException(errorMessage, ex);
}
catch (IOException ex)
{
String errorMessage = "The command [" + command + "] did not complete due to an IO error.";
log.error(errorMessage, ex);
throw new RuntimeException(errorMessage, ex);
}
}
/**
* Executes a command.
*
* @param command
* @param printOutput
* @param printError
* @param timeOut
* @return
* @throws java.io.IOException
* @throws java.lang.InterruptedException
*/
@SuppressWarnings("unused")
private static int executeCommandWithExecutors(final String command,
final boolean printOutput,
final boolean printError,
final long timeOut)
{
// validate the system and command line and get a system-appropriate command line
String massagedCommand = validateSystemAndMassageCommand(command);
try
{
// create the process which will run the command
Runtime runtime = Runtime.getRuntime();
final Process process = runtime.exec(massagedCommand);
// consume and display the error and output streams
StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(), "OUTPUT", printOutput);
StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), "ERROR", printError);
outputGobbler.start();
errorGobbler.start();
// create a Callable for the command's Process which can be called by an Executor
Callable<Integer> call = new Callable<Integer>()
{
public Integer call()
throws Exception
{
process.waitFor();
return process.exitValue();
}
};
// submit the command's call via an Executor and get the result from a Future
ExecutorService executorService = Executors.newSingleThreadExecutor();
try
{
Future<Integer> futureResultOfCall = executorService.submit(call);
int exitValue = futureResultOfCall.get(timeOut, TimeUnit.MILLISECONDS);
return exitValue;
}
catch (TimeoutException ex)
{
String errorMessage = "The command [" + command + "] timed out.";
log.error(errorMessage, ex);
throw new RuntimeException(errorMessage, ex);
}
catch (ExecutionException ex)
{
String errorMessage = "The command [" + command + "] did not complete due to an execution error.";
log.error(errorMessage, ex);
throw new RuntimeException(errorMessage, ex);
}
finally
{
executorService.shutdown();
process.destroy();
}
}
catch (InterruptedException ex)
{
String errorMessage = "The command [" + command + "] did not complete due to an unexpected interruption.";
log.error(errorMessage, ex);
throw new RuntimeException(errorMessage, ex);
}
catch (IOException ex)
{
String errorMessage = "The command [" + command + "] did not complete due to an IO error.";
log.error(errorMessage, ex);
throw new RuntimeException(errorMessage, ex);
}
}
/**
* Executes a command.
*
* @param command
* @param printOutput
* @param printError
* @param timeOut
* @return
* @throws java.io.IOException
* @throws java.lang.InterruptedException
*/
private static int executeCommandWithWorker(final String command,
final boolean printOutput,
final boolean printError,
final long timeOut)
{
// validate the system and command line and get a system-appropriate command line
String massagedCommand = validateSystemAndMassageCommand(command);
try
{
// create the process which will run the command
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(massagedCommand);
// consume and display the error and output streams
StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(), "OUTPUT", printOutput);
StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), "ERROR", printError);
outputGobbler.start();
errorGobbler.start();
// create and start a Worker thread which this thread will join for the timeout period
Worker worker = new Worker(process);
worker.start();
try
{
worker.join(timeOut);
Integer exitValue = worker.getExitValue();
if (exitValue != null)
{
// the worker thread completed within the timeout period
// stop the output and error stream gobblers
outputGobbler.stopGobbling();
errorGobbler.stopGobbling();
return exitValue;
}
// if we get this far then we never got an exit value from the worker thread as a result of a timeout
String errorMessage = "The command [" + command + "] timed out.";
log.error(errorMessage);
throw new RuntimeException(errorMessage);
}
catch (InterruptedException ex)
{
worker.interrupt();
Thread.currentThread().interrupt();
throw ex;
}
finally
{
process.destroy();
}
}
catch (InterruptedException ex)
{
String errorMessage = "The command [" + command + "] did not complete due to an unexpected interruption.";
log.error(errorMessage, ex);
throw new RuntimeException(errorMessage, ex);
}
catch (IOException ex)
{
String errorMessage = "The command [" + command + "] did not complete due to an IO error.";
log.error(errorMessage, ex);
throw new RuntimeException(errorMessage, ex);
}
}
/**
* Validates that the system is running a supported OS and returns a system-appropriate command line.
*
* @param originalCommand
* @return
*/
private static String validateSystemAndMassageCommand(final String originalCommand)
{
// make sure that we have a command
if (originalCommand.isEmpty() || (originalCommand.length() < 1))
{
String errorMessage = "Missing or empty command line parameter.";
log.error(errorMessage);
throw new RuntimeException(errorMessage);
}
// make sure that we are running on a supported system, and if so set the command line appropriately
String massagedCommand;
String osName = System.getProperty("os.name");
if (osName.equals("Windows XP"))
{
massagedCommand = "cmd.exe /C " + originalCommand;
}
else if (osName.equals("Solaris") || osName.equals("SunOS") || osName.equals("Linux"))
{
massagedCommand = originalCommand;
}
else
{
String errorMessage = "Unable to run on this system which is not Solaris, Linux, or Windows XP (actual OS type: \'" +
osName + "\').";
log.error(errorMessage);
throw new RuntimeException(errorMessage);
}
return massagedCommand;
}
}
class StreamGobbler
extends Thread
{
static private Log log = LogFactory.getLog(StreamGobbler.class);
private InputStream inputStream;
private String streamType;
private boolean displayStreamOutput;
private final StringBuffer inputBuffer = new StringBuffer();
private boolean keepGobbling = true;
/**
* Constructor.
*
* @param inputStream the InputStream to be consumed
* @param streamType the stream type (should be OUTPUT or ERROR)
* @param displayStreamOutput whether or not to display the output of the stream being consumed
*/
StreamGobbler(final InputStream inputStream,
final String streamType,
final boolean displayStreamOutput)
{
this.inputStream = inputStream;
this.streamType = streamType;
this.displayStreamOutput = displayStreamOutput;
}
/**
* Returns the output stream of the
*
* @return
*/
public String getInput()
{
return inputBuffer.toString();
}
/**
* Consumes the output from the input stream and displays the lines consumed if configured to do so.
*/
@Override
public void run()
{
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
try
{
String line = null;
while (keepGobbling && inputStreamReader.ready() && ((line = bufferedReader.readLine()) != null))
{
inputBuffer.append(line);
if (displayStreamOutput)
{
System.out.println(streamType + ">" + line);
}
}
}
catch (IOException ex)
{
log.error("Failed to successfully consume and display the input stream of type " + streamType + ".", ex);
ex.printStackTrace();
}
finally
{
try
{
bufferedReader.close();
inputStreamReader.close();
}
catch (IOException e)
{
// swallow it
}
}
}
public void stopGobbling()
{
keepGobbling = false;
}
}
Я выполняю команды ssh в моей Java-программе следующим образом:
ProcessUtility.executeCommand ("ssh" + PhysicalHostIpAddress + "virsh list \ | grep" +
newDomUName, false, false, 3600000)
Кто-нибудь может увидеть, что я делаю не так? Кстати, приведенный выше код был разработан с использованием этой статьи в качестве руководства: http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html. Я не очень разбираюсь в параллельном программировании, так что, возможно, я делаю что-то безумное - не стесняйтесь указывать на это, если так.
Заранее большое спасибо за любые предложения, идеи и т. Д.
- Джеймс
Обновление : Теперь я воспользовался советом полезных людей, которые ответили на мой оригинальный вопрос и написали класс, который предоставляет методы для выполнения вызовов ssh и scp, реализованные с использованием двух библиотек Java ssh jsch ( jsch-0.1.31) и sshtools (j2ssh-core-0.2.9). Тем не менее, ни одна из этих реализаций не работает, так как они оба терпят неудачу на этапе подключения, прежде чем я даже получу возможность выполнить аутентификацию. Я ожидаю, что столкнулся с некоторой проблемой конфигурации на серверах, на которых выполняю коды, хотя это не очевидно, поскольку я могу без проблем выполнять команды ssh и scp на этих серверах, когда я выполняю команды ssh или scp на командная строка. Серверы Solaris, на которых я тестирую свой код, в результате выполнения команды ssh -V показывают следующее:
Sun_SSH_1.3, протоколы SSH 1.5 / 2.0, OpenSSL 0x0090801f
Ниже приведен код Java, который я написал для этой цели - если кто-нибудь увидит, что я делаю неправильно на уровне кода Java, пожалуйста, дайте мне знать, и если да, то большое спасибо за вашу помощь.
public class SecureCommandUtility
{
static Log log = LogFactory.getLog(SecureCommandUtility.class);
/**
* Performs a secure copy of a single file (using scp).
*
* @param localFilePathName
* @param username
* @param password
* @param remoteHost
* @param remoteFilePathName
* @param timeout
*/
public static void secureCopySingleFile(final String localFilePathName,
final String username,
final String password,
final String remoteHost,
final String remoteFilePathName,
final int timeout)
{
// basic validation of the parameters
if ((localFilePathName == null) || localFilePathName.isEmpty())
{
// log the error and throw an exception
String errorMessage = "Error executing the secure copy -- the supplied local file path name parameter is null or empty.";
log.error(errorMessage);
throw new LifecycleException(errorMessage);
}
if ((username == null) || username.isEmpty())
{
// log the error and throw an exception
String errorMessage = "Error executing the secure copy -- the supplied user name parameter is null or empty.";
log.error(errorMessage);
throw new LifecycleException(errorMessage);
}
if ((password == null) || password.isEmpty())
{
// log the error and throw an exception
String errorMessage = "Error executing the secure copy -- the supplied password parameter is null or empty.";
log.error(errorMessage);
throw new LifecycleException(errorMessage);
}
if ((remoteHost == null) || remoteHost.isEmpty())
{
// log the error and throw an exception
String errorMessage = "Error executing the secure copy -- the supplied remote host parameter is null or empty.";
log.error(errorMessage);
throw new LifecycleException(errorMessage);
}
if ((remoteFilePathName == null) || remoteFilePathName.isEmpty())
{
// log the error and throw an exception
String errorMessage = "Error executing the secure copy -- the supplied remote file path name parameter is null or empty.";
log.error(errorMessage);
throw new LifecycleException(errorMessage);
}
if (timeout < 1000)
{
// log the error and throw an exception
String errorMessage = "Error executing the secure copy -- the supplied timeout parameter is less than one second.";
log.error(errorMessage);
throw new LifecycleException(errorMessage);
}
//secureCopySingleFileJSch(localFilePathName, username, password, remoteHost, remoteFilePathName);
secureCopySingleFileJ2Ssh(localFilePathName, username, password, remoteHost, remoteFilePathName, timeout);
}
/**
*
* @param user
* @param password
* @param remoteHost
* @param command
* @return exit status of the command
*/
public static int secureShellCommand(final String user,
final String password,
final String remoteHost,
final String command,
final int timeout)
{
// basic validation of the parameters
if ((user == null) || user.isEmpty())
{
// log the error and throw an exception
String errorMessage = "Error executing the ssh command \'" + command +
"\': the supplied user name parameter is null or empty.";
log.error(errorMessage);
throw new LifecycleException(errorMessage);
}
if ((password == null) || password.isEmpty())
{
// log the error and throw an exception
String errorMessage = "Error executing the ssh command \'" + command +
"\': the supplied password parameter is null or empty.";
log.error(errorMessage);
throw new LifecycleException(errorMessage);
}
if ((remoteHost == null) || remoteHost.isEmpty())
{
// log the error and throw an exception
String errorMessage = "Error executing the ssh command \'" + command +
"\': the supplied remote host parameter is null or empty.";
log.error(errorMessage);
throw new LifecycleException(errorMessage);
}
if ((command == null) || command.isEmpty())
{
// log the error and throw an exception
String errorMessage = "Error executing the ssh command: the supplied command parameter is null or empty.";
log.error(errorMessage);
throw new LifecycleException(errorMessage);
}
if (timeout < 1000)
{
// log the error and throw an exception
String errorMessage = "Error executing the ssh command \'" + command +
"\': the supplied timeout parameter is less than one second.";
log.error(errorMessage);
throw new LifecycleException(errorMessage);
}
//return secureShellCommandJsch(user, password, remoteHost, command, timeout);
return secureShellCommandJ2Ssh(user, password, remoteHost, command, timeout);
}
/**
* Performs a secure copy of a single file (using scp).
*
* @param localFilePathName
* @param username
* @param password
* @param remoteHost
* @param remoteFilePathName
* @param timeout
*/
private static void secureCopySingleFileJ2Ssh(final String localFilePathName,
final String username,
final String password,
final String remoteHost,
final String remoteFilePathName,
final int timeout)
{
SshClient sshClient = null;
try
{
// create and connect client
sshClient = new SshClient();
sshClient.setSocketTimeout(timeout);
sshClient.connect(remoteHost, 22, new IgnoreHostKeyVerification());
// perform password-based authentication
PasswordAuthenticationClient passwordAuthenticationClient = new PasswordAuthenticationClient();
passwordAuthenticationClient.setUsername(username);
passwordAuthenticationClient.setPassword(password);
if (sshClient.authenticate(passwordAuthenticationClient) != AuthenticationProtocolState.COMPLETE)
{
// log the error and throw an exception
String errorMessage = "Failed to copy \'" + localFilePathName + "\' to \'" + remoteHost + ":" +
remoteFilePathName + "\' -- failed to authenticate using username/password \'" +
username + "\'/\'" + password + "\'.";
log.error(errorMessage);
throw new LifecycleException(errorMessage);
}
// perform the copy
sshClient.openScpClient().put(localFilePathName, remoteFilePathName, false);
}
catch (Exception ex)
{
// log the error and throw an exception
String errorMessage = "Failed to copy \'" + localFilePathName + "\' to \'" + remoteHost + ":" +
remoteFilePathName + "\'.";
log.error(errorMessage, ex);
throw new LifecycleException(errorMessage, ex);
}
finally
{
if ((sshClient != null) && sshClient.isConnected())
{
sshClient.disconnect();
}
}
}
/**
* Performs a secure copy of a single file (using scp).
*
* @param localFilePathName
* @param user
* @param password
* @param remoteHost
* @param remoteFilePathName
*/
@SuppressWarnings("unused")
private static void secureCopySingleFileJSch(final String localFilePathName,
final String user,
final String password,
final String remoteHost,
final String remoteFilePathName)
{
Session session = null;
Channel channel = null;
FileInputStream fileInputStream = null;
try
{
// create and connect Jsch session
JSch jsch = new JSch();
session = jsch.getSession(user, remoteHost, 22);
session.setPassword(password);
session.connect();
// exec 'scp -p -t remoteFilePathName' remotely
String command = "scp -p -t " + remoteFilePathName;
channel = session.openChannel("exec");
((ChannelExec) channel).setCommand(command);
// get the I/O streams for the remote scp
OutputStream outputStream = channel.getOutputStream();
InputStream inputStream = channel.getInputStream();
// connect the channel
channel.connect();
int ackCheck = checkAck(inputStream);
if (checkAck(inputStream) != 0)
{
// log the error and throw an exception
String errorMessage = "The scp command failed -- input stream ACK check failed with the following result: " +
ackCheck;
log.error(errorMessage);
throw new LifecycleException(errorMessage);
}
// send "C0644 filesize filename", where filename should not include '/'
long filesize = (new File(localFilePathName)).length();
command = "C0644 " + filesize + " ";
if (localFilePathName.lastIndexOf('/') > 0)
{
command += localFilePathName.substring(localFilePathName.lastInde