package com.nirima.jenkins.plugins.docker.strategy;
import hudson.Extension;
import hudson.model.*;
import hudson.slaves.*;
import hudson.util.TimeUnit2;
import jenkins.model.Jenkins;
import org.jenkinsci.plugins.durabletask.executors.ContinuableExecutable;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundConstructor;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Mix of {@link org.jenkinsci.plugins.durabletask.executors.OnceRetentionStrategy} (1.3) and {@link CloudRetentionStrategy}
* that allows configure it parameters and has Descriptor.
* <p/>
* Retention strategy that allows a cloud slave to run only a single build before disconnecting.
* A {@link ContinuableExecutable} does not trigger termination.
*/
public class DockerOnceRetentionStrategy extends CloudRetentionStrategy implements ExecutorListener {
private static final Logger LOGGER = Logger.getLogger(DockerOnceRetentionStrategy.class.getName());
private int idleMinutes = 10;
private transient boolean terminating;
/**
* Creates the retention strategy.
*
* @param idleMinutes number of minutes of idleness after which to kill the slave; serves a backup in case the strategy fails to detect the end of a task
*/
@DataBoundConstructor
public DockerOnceRetentionStrategy(int idleMinutes) {
super(idleMinutes);
this.idleMinutes = idleMinutes;
}
public int getIdleMinutes() {
return idleMinutes;
}
@Override
public long check(final AbstractCloudComputer c) {
// When the slave is idle we should disable accepting tasks and check to see if it is already trying to
// terminate. If it's not already trying to terminate then lets terminate manually.
if (c.isIdle() && !disabled) {
final long idleMilliseconds = System.currentTimeMillis() - c.getIdleStartMilliseconds();
if (idleMilliseconds > TimeUnit2.MINUTES.toMillis(idleMinutes)) {
LOGGER.log(Level.FINE, "Disconnecting {0}", c.getName());
done(c);
}
}
// Return one because we want to check every minute if idle.
return 1;
}
@Override
public void start(AbstractCloudComputer c) {
if (c.getNode() instanceof EphemeralNode) {
throw new IllegalStateException("May not use OnceRetentionStrategy on an EphemeralNode: " + c);
}
super.start(c);
}
@Override
public void taskAccepted(Executor executor, Queue.Task task) {
}
@Override
public void taskCompleted(Executor executor, Queue.Task task, long durationMS) {
done(executor);
}
@Override
public void taskCompletedWithProblems(Executor executor, Queue.Task task, long durationMS, Throwable problems) {
done(executor);
}
private void done(Executor executor) {
final AbstractCloudComputer<?> c = (AbstractCloudComputer) executor.getOwner();
Queue.Executable exec = executor.getCurrentExecutable();
if (exec instanceof ContinuableExecutable && ((ContinuableExecutable) exec).willContinue()) {
LOGGER.log(Level.FINE, "not terminating {0} because {1} says it will be continued", new Object[]{c.getName(), exec});
return;
}
LOGGER.log(Level.FINE, "terminating {0} since {1} seems to be finished", new Object[]{c.getName(), exec});
done(c);
}
private void done(final AbstractCloudComputer<?> c) {
c.setAcceptingTasks(false); // just in case
synchronized (this) {
if (terminating) {
return;
}
terminating = true;
}
Computer.threadPoolForRemoting.submit(new Runnable() {
@Override
public void run() {
Queue.withLock( new Runnable() {
@Override
public void run() {
try {
AbstractCloudSlave node = c.getNode();
if (node != null) {
node.terminate();
}
} catch (InterruptedException | IOException e) {
LOGGER.log(Level.WARNING, "Failed to terminate " + c.getName(), e);
synchronized (DockerOnceRetentionStrategy.this) {
terminating = false;
}
}
}
});
}
});
}
@Override
public DescriptorImpl getDescriptor() {
return DESCRIPTOR;
}
@Restricted(NoExternalUse.class)
public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();
@Extension
public static final class DescriptorImpl extends Descriptor<RetentionStrategy<?>> {
@Override
public String getDisplayName() {
return "Docker Once Retention Strategy";
}
}
}