/*
* Sun Public License
*
* The contents of this file are subject to the Sun Public License Version
* 1.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is available at http://www.sun.com/
*
* The Original Code is the SLAMD Distributed Load Generation Engine.
* The Initial Developer of the Original Code is Neil A. Wilson.
* Portions created by Neil A. Wilson are Copyright (C) 2004-2010.
* Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc.
* All Rights Reserved.
*
* Contributor(s): Neil A. Wilson
*/
package com.slamd.jobs;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.StringTokenizer;
import com.slamd.job.JobClass;
import com.slamd.job.UnableToRunException;
import com.slamd.parameter.BooleanParameter;
import com.slamd.parameter.InvalidValueException;
import com.slamd.parameter.MultiLineTextParameter;
import com.slamd.parameter.Parameter;
import com.slamd.parameter.ParameterList;
import com.slamd.parameter.PlaceholderParameter;
import com.slamd.parameter.StringParameter;
import com.slamd.stat.StatTracker;
/**
* This class defines a job that has the ability to execute a command on the
* client system.
*
*
* @author Neil A. Wilson
*/
public class ExecJobClass
extends JobClass
{
/**
* The size to use for the read buffer.
*/
public static final int READ_BUFFER_SIZE = 4096;
// The parameter that indicates whether to log command output.
private BooleanParameter logOutputParameter =
new BooleanParameter("log_output", "Log Command Output",
"Indicates whether the output of the command " +
"should be logged.", false);
// Specifies the list of environment variables to define for the job
// execution.
private MultiLineTextParameter environmentParameter =
new MultiLineTextParameter("env_variables", "Environment Variables",
"A set of environment variables that " +
"should be defined when the job is " +
"executed. The environment variables " +
"should be specified one per line, using " +
"the format name=value", null, false);
// The parameter that specifies the command to execute.
private StringParameter commandParameter =
new StringParameter("command", "Command to Execute",
"Specifies the command to execute.", true, "");
// THe parameter that specifies the working directory.
private StringParameter workingDirParameter =
new StringParameter("working_dir", "Working Directory",
"The path to the working directory to use when " +
"executing the command.", false, "");
// Indicates whether the output of the command should be captured and logged.
private static boolean logOutput;
// The placeholder parameter.
private PlaceholderParameter placeholder = new PlaceholderParameter();
// The command to be executed.
private static String command;
// The path to the working directory for the job.
private static File workingDir;
// The environment variables to use for the job.
private static String[] environmentVariables;
// The buffer used to hold data read from the process output.
private byte[] readBuffer;
/**
* The default constructor used to create a new instance of the job class.
* The only thing it should do is to invoke the superclass constructor. All
* other initialization should be performed in the <CODE>initialize</CODE>
* method.
*/
public ExecJobClass()
{
super();
}
/**
* {@inheritDoc}
*/
@Override()
public String getJobName()
{
return "Exec";
}
/**
* {@inheritDoc}
*/
@Override()
public String getShortDescription()
{
return "Execute a command on client systems.";
}
/**
* {@inheritDoc}
*/
@Override()
public String[] getLongDescription()
{
return new String[]
{
"This job can be used to execute a specified command on client " +
"systems. If desired, the output of the command can be captured and " +
"logged as part of the job data."
};
}
/**
* {@inheritDoc}
*/
@Override()
public String getJobCategoryName()
{
return "Utility";
}
/**
* {@inheritDoc}
*/
@Override()
public ParameterList getParameterStubs()
{
Parameter[] parameters = new Parameter[]
{
placeholder,
commandParameter,
workingDirParameter,
environmentParameter,
logOutputParameter
};
return new ParameterList(parameters);
}
/**
* {@inheritDoc}
*/
@Override()
public StatTracker[] getStatTrackerStubs(String clientID, String threadID,
int collectionInterval)
{
return new StatTracker[0];
}
/**
* {@inheritDoc}
*/
@Override()
public StatTracker[] getStatTrackers()
{
return new StatTracker[0];
}
/**
* {@inheritDoc}
*/
@Override()
public void validateJobInfo(int numClients, int threadsPerClient,
int threadStartupDelay, Date startTime,
Date stopTime, int duration,
int collectionInterval, ParameterList parameters)
throws InvalidValueException
{
// See if the environment variables were specified. If so, make sure they
// were specified properly.
MultiLineTextParameter envParameter =
parameters.getMultiLineTextParameter(environmentParameter.getName());
if (envParameter != null)
{
String[] lines = envParameter.getNonBlankLines();
for (int i=0; ((lines != null) && (i < lines.length)); i++)
{
if (lines[i].indexOf('=') <= 0)
{
throw new InvalidValueException("Invalid environment variable " +
"specified: \"" + lines[i] +
"\". Expected {name}={value}");
}
}
}
}
/**
* {@inheritDoc}
*/
@Override()
public void initializeClient(String clientID, ParameterList parameters)
throws UnableToRunException
{
command = null;
commandParameter =
parameters.getStringParameter(commandParameter.getName());
if (commandParameter != null)
{
command = commandParameter.getStringValue();
}
workingDir = null;
workingDirParameter =
parameters.getStringParameter(workingDirParameter.getName());
if ((workingDirParameter != null) && (workingDirParameter.hasValue()))
{
String workingDirStr = workingDirParameter.getStringValue();
workingDir = new File(workingDirStr);
try
{
if (! workingDir.exists())
{
throw new UnableToRunException("Working directory \"" +
workingDirStr + "\" does not exist.");
}
if (! workingDir.isDirectory())
{
throw new UnableToRunException("Working directory \"" +
workingDirStr +
"\" is not a directory.");
}
}
catch (UnableToRunException utre)
{
throw utre;
}
catch (Exception e)
{
throw new UnableToRunException("Unable to verify existence of " +
"working directory \"" + workingDirStr +
"\": " + e, e);
}
}
environmentVariables = null;
environmentParameter =
parameters.getMultiLineTextParameter(environmentParameter.getName());
if (environmentParameter != null)
{
environmentVariables = environmentParameter.getNonBlankLines();
}
logOutput = false;
logOutputParameter =
parameters.getBooleanParameter(logOutputParameter.getName());
if (logOutputParameter != null)
{
logOutput = logOutputParameter.getBooleanValue();
}
}
/**
* {@inheritDoc}
*/
@Override()
public void initializeThread(String clientID, String threadID,
int collectionInterval, ParameterList parameters)
throws UnableToRunException
{
// Initialize the read buffer.
readBuffer = new byte[READ_BUFFER_SIZE];
}
/**
* {@inheritDoc}
*/
@Override()
public void runJob()
{
Runtime runtime = Runtime.getRuntime();
Process process = null;
try
{
process = runtime.exec(command, environmentVariables, workingDir);
}
catch (IOException ioe)
{
logMessage("Unable to execute command \"" + command + "\": " + ioe);
indicateStoppedDueToError();
return;
}
BufferedInputStream stdOutStream =
new BufferedInputStream(process.getInputStream());
BufferedInputStream stdErrStream =
new BufferedInputStream(process.getErrorStream());
while (true)
{
try
{
if (logOutput)
{
if (stdOutStream.available() > 0)
{
while ((! shouldStop()) && (stdOutStream.available() > 0))
{
int bytesRead = stdOutStream.read(readBuffer);
String[] outputStrs = byteArrayToStrings(readBuffer, bytesRead);
for (int i=0; i < outputStrs.length; i++)
{
logMessage("STDOUT: " + outputStrs[i]);
}
}
}
if (stdErrStream.available() > 0)
{
while ((! shouldStop()) && (stdErrStream.available() > 0))
{
int bytesRead = stdErrStream.read(readBuffer);
String[] errorStrs = byteArrayToStrings(readBuffer, bytesRead);
for (int i=0; i < errorStrs.length; i++)
{
logMessage("STDERR: " + errorStrs[i]);
}
}
}
}
if (shouldStop())
{
try
{
stdOutStream.close();
stdErrStream.close();
} catch (Exception e) {}
process.destroy();
logMessage("Terminated process because the client determined it " +
"should stop running.");
return;
}
try
{
int returnCode = process.exitValue();
if (returnCode == 0)
{
logMessage("Command completed successfully (exit code 0)");
}
else
{
logMessage("Command completed abnormally (exit code " +
returnCode + ')');
indicateCompletedWithErrors();
}
try
{
stdOutStream.close();
stdErrStream.close();
} catch (Exception e) {}
return;
} catch (IllegalThreadStateException itse) {}
try
{
Thread.sleep(100);
} catch (InterruptedException ie) {}
}
catch (IOException ioe)
{
// This could mean that the command is done or that some other error
// occurred. Try to get the return code to see if it completed.
boolean completedSuccessfully = false;
try
{
int returnCode = process.exitValue();
completedSuccessfully = (returnCode == 0);
if (completedSuccessfully)
{
logMessage("Command completed successfully (exit code 0)");
}
else
{
logMessage("Command completed abnormally (exit code " + returnCode +
')');
indicateCompletedWithErrors();
}
}
catch (IllegalThreadStateException itse)
{
logMessage("Attempt to read process output failed: " + ioe);
indicateCompletedWithErrors();
}
return;
}
}
}
/**
* Converts the provided byte array into an array of strings, with one string
* per line.
*
* @param byteArray The byte array containing the data to convert to an
* array of strings.
* @param length The number of bytes to actually use in the byte array.
*
* @return The array of strings containing the data from the provided byte
* array.
*/
private static String[] byteArrayToStrings(byte[] byteArray, int length)
{
ArrayList<String> stringList = new ArrayList<String>();
String byteStr = new String(byteArray, 0, length);
StringTokenizer tokenizer = new StringTokenizer(byteStr, "\r\n");
while (tokenizer.hasMoreTokens())
{
stringList.add(tokenizer.nextToken());
}
String[] returnStrings = new String[stringList.size()];
stringList.toArray(returnStrings);
return returnStrings;
}
}