package hudson.plugins.batch_task;
import hudson.EnvVars;
import hudson.Launcher;
import hudson.Util;
import hudson.AbortException;
import hudson.FilePath;
import hudson.model.AbstractBuild;
import hudson.model.Actionable;
import hudson.model.BallColor;
import hudson.model.BuildableItemWithBuildWrappers;
import hudson.model.Cause;
import hudson.model.CauseAction;
import hudson.model.Environment;
import hudson.model.EnvironmentContributingAction;
import hudson.model.Executor;
import hudson.model.Hudson;
import hudson.model.Node;
import hudson.model.Queue.Executable;
import hudson.model.Result;
import hudson.model.StreamBuildListener;
import hudson.slaves.NodeProperty;
import hudson.slaves.WorkspaceList.Lease;
import hudson.tasks.BatchFile;
import hudson.tasks.BuildWrapper;
import hudson.tasks.CommandInterpreter;
import hudson.tasks.Shell;
import hudson.util.Iterators;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.framework.io.LargeText;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Record of {@link BatchTask} execution.
*
* @author Kohsuke Kawaguchi
*/
public final class BatchRun extends Actionable implements Executable, Comparable<BatchRun> {
/**
* Build result.
* If null, we are still building.
*/
protected Result result;
public final Calendar timestamp;
protected transient BatchRunAction parent;
/**
* Unique number that identifie this record among {@link BatchRunAction}.
*/
public final int id;
/**
* Pointer that connects us back to {@link BatchTask}
* @see #getParent()
*/
public final String taskName;
/**
* Number of milli-seconds it took to run this build.
*/
protected long duration;
protected BatchRun(Calendar timestamp, BatchRunAction parent, int id, BatchTask task) {
this.timestamp = timestamp;
this.parent = parent;
this.id = id;
this.taskName = task.name;
}
public Result getResult() {
return result;
}
/**
* Is this task still running?
*/
public boolean isRunning() {
return result==null;
}
/**
* Gets the string that says how long since this run has started.
*
* @return
* string like "3 minutes" "1 day" etc.
*/
public String getTimestampString() {
long time = new GregorianCalendar().getTimeInMillis()-timestamp.getTimeInMillis();
return Util.getTimeSpanString(time);
}
/**
* Gets the log file that stores the execution result.
*/
public File getLogFile() {
return new File(parent.owner.getRootDir(),"task-"+id+".log");
}
public BatchTask getParent() {
BatchTaskAction jta = parent.owner.getProject().getAction(BatchTaskAction.class);
if(jta==null) return null;
return jta.getTask(taskName);
}
public BatchRunAction getOwner() {
return parent;
}
/**
* Gets the icon color for display.
*/
public BallColor getIconColor() {
if(!isRunning()) {
// already built
return getResult().color;
}
// a new build is in progress
BatchRun previous = getPrevious();
BallColor baseColor;
if(previous==null)
baseColor = BallColor.GREY_ANIME;
else
baseColor = previous.getIconColor();
return baseColor.anime();
}
public String getBuildStatusUrl() {
return getIconColor().getImage();
}
/**
* Obtains the previous execution record, or null if no such record is available.
*/
public BatchRun getPrevious() {
// check siblings
for( AbstractBuild<?,?> b=parent.owner; b!=null; b=b.getPreviousBuild()) {
BatchRunAction records = b.getAction(BatchRunAction.class);
if(records==null) continue;
for (BatchRun r : records.records) {
if( r.taskName.equals(taskName)
&& r.timestamp.compareTo(this.timestamp)<0 ) // must be older than this
return r;
}
}
return null;
}
/**
* Obtains the next execution record, or null if no such record is available.
*/
public BatchRun getNext() {
// check siblings
for( AbstractBuild<?,?> b=parent.owner; b!=null; b=b.getNextBuild()) {
BatchRunAction records = b.getAction(BatchRunAction.class);
if(records==null) continue;
for (BatchRun r : Iterators.reverse(records.records)) {
if (r.taskName.equals(taskName)
&& r.timestamp.compareTo(this.timestamp) > 0) // must be newer than this
return r;
}
}
return null;
}
/**
* Gets the URL (under the context root) that points to this record.
*
* @return
* URL like "job/foo/53/batchTasks/0"
*/
public String getUrl() {
return parent.owner.getUrl()+"batchTasks/"+id;
}
public String getSearchUrl() {
return getUrl();
}
public String getDisplayName() {
return taskName+' '+getBuildNumber();
}
public String getNumber() {
return parent.owner.getNumber()+"-"+id;
}
public String getBuildNumber() {
return "#"+parent.owner.getNumber()+'-'+id;
}
/**
* Gets the string that says how long the build took to run.
*/
public String getDurationString() {
if(isRunning())
return Util.getTimeSpanString(System.currentTimeMillis()-timestamp.getTimeInMillis())+" and counting";
return Util.getTimeSpanString(duration);
}
/**
* Gets the millisecond it took to build.
*/
@Exported
public long getDuration() {
return duration;
}
public void run() {
StreamBuildListener listener=null;
try {
long start = System.currentTimeMillis();
listener = new StreamBuildListener(new FileOutputStream(getLogFile()));
Node node = Executor.currentExecutor().getOwner().getNode();
Launcher launcher = node.createLauncher(listener);
BatchTask task = getParent();
if (task==null)
throw new AbortException("ERROR: undefined task \""+taskName+"\"");
AbstractBuild<?,?> lb = task.owner.getLastBuild();
FilePath ws = lb.getWorkspace();
if (ws==null)
throw new AbortException(lb.getFullDisplayName()+" doesn't have a workspace.");
try {
// Copying some logic from AbstractBuild.AbstractRunner.createLauncher().
// buildEnvironments are discarded after the build runs, so we need to follow the
// same model here.. applying node properties, but leaving out build wrappers.
final ArrayList<Environment> buildEnvironments = new ArrayList<Environment>();
for (NodeProperty nodeProperty : Hudson.getInstance().getGlobalNodeProperties()) {
Environment environment = nodeProperty.setUp(lb, launcher, listener);
if (environment != null) buildEnvironments.add(environment);
}
for (NodeProperty nodeProperty : node.getNodeProperties()) {
Environment environment = nodeProperty.setUp(lb, launcher, listener);
if (environment != null) buildEnvironments.add(environment);
}
// Not sure if tasks should use all build wrappers (xvnc for example),
// but look for one in particular, from setenv plugin.
if (task.owner instanceof BuildableItemWithBuildWrappers)
for (BuildWrapper bw : ((BuildableItemWithBuildWrappers)task.owner).getBuildWrappersList())
if ("hudson.plugins.setenv.SetEnvBuildWrapper".equals(bw.getClass().getName())) {
Environment environment = bw.setUp(lb, launcher, listener);
if (environment != null) buildEnvironments.add(environment);
}
// This is the only way I found to inject things into the environment of
// CommandInterpreter.perform().. temporarily attach an action to the build.
// (if BatchTask/BatchRun are converted to extend AbstractProject/AbstractBuild,
// BatchRun will use AbstractRunner and get global/node properties w/o extra code)
EnvironmentContributingAction envAct = new EnvironmentContributingAction() {
public void buildEnvVars(AbstractBuild<?,?> build, EnvVars env) {
// Apply global and node properties
for (Environment e : buildEnvironments) e.buildEnvVars(env);
// Our task id
env.put("TASK_ID", getNumber());
// User who triggered this task run, if applicable
out: for (CauseAction ca : getActions(CauseAction.class))
for (Cause c : ca.getCauses())
if (c instanceof Cause.UserCause) {
env.put("HUDSON_USER", ((Cause.UserCause)c).getUserName());
break out;
}
}
public String getDisplayName() { return null; }
public String getIconFileName() { return null; }
public String getUrlName() { return null; }
};
CommandInterpreter batchRunner;
if (launcher.isUnix())
batchRunner = new Shell(task.script);
else
batchRunner = new BatchFile(task.script);
Lease wsLease = null;
try {
// Lock the workspace
wsLease = lb.getBuiltOn().toComputer().getWorkspaceList().acquire(ws,
!task.owner.isConcurrentBuild());
// Add environment to build so it will apply when task runs
lb.getActions().add(envAct);
// Run the task
result = batchRunner.perform(lb,launcher,listener) ? Result.SUCCESS : Result.FAILURE;
} finally {
if (wsLease != null) wsLease.release();
lb.getActions().remove(envAct);
for (Environment e : buildEnvironments) e.tearDown(lb, listener);
}
} catch (InterruptedException e) {
listener.getLogger().println("ABORTED");
result = Result.ABORTED;
}
duration = System.currentTimeMillis()-start;
// save the build result
parent.owner.save();
} catch (AbortException e) {
result = Result.FAILURE;
listener.error(e.getMessage());
} catch (IOException e) {
result = Result.FAILURE;
LOGGER.log(Level.SEVERE, "Failed to write "+getLogFile(),e);
} finally {
if(listener!=null)
listener.getLogger().close();
if (result==null)
result = Result.FAILURE;
}
}
/**
* Handles incremental log output.
*/
public void doProgressiveLog( StaplerRequest req, StaplerResponse rsp) throws IOException {
new LargeText(getLogFile(),!isRunning()).doProgressText(req,rsp);
}
// used by the executors listing
@Override public String toString() {
return parent.owner.toString()+'-'+id;
}
/**
* Newer records should appear before older records.
*/
public int compareTo(BatchRun that) {
return that.timestamp.compareTo(this.timestamp);
}
private static final Logger LOGGER = Logger.getLogger(BatchRun.class.getName());
}