-
Notifications
You must be signed in to change notification settings - Fork 104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Request for feedback: Add new fixed-lifespan cloud retention strategy. #96
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Once a machine has reached end-of-life then re-checking every minute is good. |
||
} | ||
|
||
@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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not a big fan of having class fields scattered throughout the code - I much prefer to see all the fields at the top. |
||
|
||
private synchronized boolean isAtEndOfLife() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think that |
||
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> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add a linefeed to the end of the file. |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add a linefeed to the end of the file. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this fails to disconnect, it looks like
setAtEndOfLife()
will still be set so that no further attempts will be made to disconnect the slave (until Jenkins gets restarted).That doesn't seem right to me.