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"; } } }