Skip to content

Commit

Permalink
Add new fixed-lifespan cloud retention strategy.
Browse files Browse the repository at this point in the history
The fixed-lifespan strategy provisions an agent for a fixed amount of time.
After the amount of time has expired, the agent is disconnected, any builds
finished, then the agent is removed.
  • Loading branch information
rhencke committed Sep 20, 2018
1 parent 38d9380 commit b38b052
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@
import hudson.model.Node.Mode;
import hudson.model.labels.LabelAtom;
import hudson.plugins.sshslaves.SSHLauncher;
import hudson.slaves.NodeProperty;
import hudson.slaves.CommandLauncher;
import hudson.slaves.ComputerLauncher;
import hudson.slaves.JNLPLauncher;
import hudson.slaves.NodeProperty;
import hudson.slaves.RetentionStrategy;
import hudson.util.FormValidation;

Expand All @@ -51,6 +51,7 @@
import jenkins.model.Jenkins;
import jenkins.slaves.JnlpSlaveAgentProtocol;

import org.jenkinsci.plugins.vsphere.FixedLifespanCloudRetentionStrategy;
import org.jenkinsci.plugins.vsphere.RunOnceCloudRetentionStrategy;
import org.jenkinsci.plugins.vsphere.VSphereCloudRetentionStrategy;
import org.jenkinsci.plugins.vsphere.VSphereConnectionConfig;
Expand Down Expand Up @@ -497,6 +498,12 @@ private RetentionStrategy<?> determineRetention() {
templateStrategy.getIdleMinutes());
return cloneStrategy;
}
if (retentionStrategy instanceof FixedLifespanCloudRetentionStrategy) {
final FixedLifespanCloudRetentionStrategy templateStrategy = (FixedLifespanCloudRetentionStrategy) retentionStrategy;
final FixedLifespanCloudRetentionStrategy cloneStrategy = new FixedLifespanCloudRetentionStrategy(
templateStrategy.getLifespanMinutes());
return cloneStrategy;
}
throw new IllegalStateException(
"Unsupported retentionStrategy (" + retentionStrategy + ") in template configuration");
}
Expand Down Expand Up @@ -606,6 +613,7 @@ public static List<Descriptor<RetentionStrategy<?>>> getRetentionStrategyDescrip
final List<Descriptor<RetentionStrategy<?>>> result = new ArrayList<>();
result.add(RunOnceCloudRetentionStrategy.DESCRIPTOR);
result.add(VSphereCloudRetentionStrategy.DESCRIPTOR);
result.add(FixedLifespanCloudRetentionStrategy.DESCRIPTOR);
return result;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package org.jenkinsci.plugins.vsphere;

import hudson.model.Descriptor;
import hudson.model.InvisibleAction;
import hudson.slaves.AbstractCloudComputer;
import hudson.slaves.AbstractCloudSlave;
import hudson.slaves.CloudRetentionStrategy;
import hudson.slaves.RetentionStrategy;
import hudson.util.TimeUnit2;

import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundConstructor;

import static java.util.logging.Level.WARNING;

public final class FixedLifespanCloudRetentionStrategy extends RetentionStrategy<AbstractCloudComputer> {

private static final Logger LOGGER = Logger.getLogger(CloudRetentionStrategy.class.getName());

private final int lifespanMinutes;

@DataBoundConstructor
public FixedLifespanCloudRetentionStrategy(int lifespanMinutes) {
this.lifespanMinutes = lifespanMinutes;
}

public int getLifespanMinutes() {
return lifespanMinutes;
}

@Override
public long check(AbstractCloudComputer c) {
final long creationTimeMillis = c.getAction(CloudComputerCreatedOnInvisibleAction.class).getCreationTimeMillis();
final long ageMillis = System.currentTimeMillis() - creationTimeMillis;
final String cname = c.getName();
if (!isAtEndOfLife() && ageMillis > TimeUnit2.MINUTES.toMillis(lifespanMinutes)) {
LOGGER.log(Level.FINE, "Will terminate {0} once idle - lifespan of {1} minutes reached.", new Object[] { cname, lifespanMinutes });
setAtEndOfLife();
final VSphereOfflineCause cause = new VSphereOfflineCause(Messages._fixedLifespanCloudRetentionStrategy_OfflineReason_LifespanReached(String.valueOf(lifespanMinutes)));
try {
c.disconnect(cause).get();
} catch (InterruptedException | ExecutionException e) {
LOGGER.log(WARNING, "Failed to disconnect " + cname, e);
}
}
if (isAtEndOfLife() && c.isIdle()) {
final AbstractCloudSlave computerNode = c.getNode();
if (computerNode != null) {
try {
LOGGER.log(Level.FINER, "Initiating termination of {0}.", cname);
computerNode.terminate();
} catch (InterruptedException | IOException e) {
LOGGER.log(WARNING, "Failed to terminate " + cname, e);
}
}
}
return 1; // re-check in 1 minute
}

@Override
public DescriptorImpl getDescriptor() {
return DESCRIPTOR;
}

@Override
public void start(AbstractCloudComputer c) {
c.addAction(new CloudComputerCreatedOnInvisibleAction(System.currentTimeMillis()));
super.start(c);
c.connect(false);
}

@Override
public boolean isAcceptingTasks(AbstractCloudComputer c) {
return !isAtEndOfLife();
}

private transient boolean atEndOfLife;

private synchronized boolean isAtEndOfLife() {
return atEndOfLife;
}

private synchronized void setAtEndOfLife() {
atEndOfLife = true;
}

@Restricted(NoExternalUse.class)
public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();

public static final class DescriptorImpl extends Descriptor<RetentionStrategy<?>> {
@Override
public String getDisplayName() {
return "vSphere Fixed Lifespan Retention Strategy";
}
}

public static class CloudComputerCreatedOnInvisibleAction extends InvisibleAction {
private final long creationTimeMillis;

CloudComputerCreatedOnInvisibleAction(long creationTimeMillis) {
this.creationTimeMillis = creationTimeMillis;
}

long getCreationTimeMillis() {
return creationTimeMillis;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:entry title="${%Lifespan Timeout}" field="lifespanMinutes">
<f:number default="60"/>
</f:entry>
</j:jelly>
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
runOnceCloudRetentionStrategy.OfflineReason.BuildHasRun=VSphere Cloud Slave configured was to run one build only
fixedLifespanCloudRetentionStrategy.OfflineReason.LifespanReached=VSphere Cloud Agent has reached the end of its configured lifespan of {0} minutes

0 comments on commit b38b052

Please sign in to comment.