package com.amazon.jenkins.ec2fleet; import hudson.model.Computer; import hudson.model.Node; import hudson.slaves.RetentionStrategy; import hudson.slaves.SlaveComputer; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; /** * @see EC2FleetCloud */ public class IdleRetentionStrategy extends RetentionStrategy<SlaveComputer> { private static final int RE_CHECK_IN_MINUTE = 1; private static final Logger LOGGER = Logger.getLogger(IdleRetentionStrategy.class.getName()); /** * Will be called under {@link hudson.model.Queue#withLock(Runnable)} * * @param computer computer * @return delay in min before next run */ @Override public long check(final SlaveComputer computer) { final EC2FleetNodeComputer fc = (EC2FleetNodeComputer) computer; final EC2FleetCloud cloud = fc.getCloud(); LOGGER.log(Level.INFO, "Check if node idle " + computer.getName()); // in some multi-thread edge cases cloud could be null for some time, just be ok with that if (cloud == null) { LOGGER.warning("Edge case cloud is null for computer " + fc.getDisplayName() + " should be autofixed in a few minutes, if no please create issue for plugin"); return RE_CHECK_IN_MINUTE; } // Ensure that the EC2FleetCloud cannot be mutated from under us while // we're doing this check // Ensure nobody provisions onto this node until we've done // checking boolean shouldAcceptTasks = fc.isAcceptingTasks(); boolean justTerminated = false; fc.setAcceptingTasks(false); try { if (fc.isIdle() && isIdleForTooLong(cloud, fc)) { // Find instance ID Node compNode = fc.getNode(); if (compNode == null) { return 0; } final String instanceId = compNode.getNodeName(); if (cloud.scheduleToTerminate(instanceId)) { // Instance successfully terminated, so no longer accept tasks shouldAcceptTasks = false; justTerminated = true; } } if (cloud.isAlwaysReconnect() && !justTerminated && fc.isOffline() && !fc.isConnecting() && fc.isLaunchSupported()) { LOGGER.log(Level.INFO, "Reconnecting to instance: " + fc.getDisplayName()); fc.tryReconnect(); } } finally { fc.setAcceptingTasks(shouldAcceptTasks); } return RE_CHECK_IN_MINUTE; } @Override public void start(SlaveComputer c) { LOGGER.log(Level.INFO, "Connecting to instance: " + c.getDisplayName()); c.connect(false); } private boolean isIdleForTooLong(final EC2FleetCloud cloud, final Computer computer) { final int idleMinutes = cloud.getIdleMinutes(); if (idleMinutes <= 0) return false; final long idleTime = System.currentTimeMillis() - computer.getIdleStartMilliseconds(); final long maxIdle = TimeUnit.MINUTES.toMillis(idleMinutes); LOGGER.log(Level.INFO, "Instance: " + computer.getDisplayName() + " Age: " + idleTime + " Max Age:" + maxIdle); return idleTime > maxIdle; } }