/* * The MIT License * * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Stephen Connolly * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package hudson.slaves; import hudson.ExtensionPoint; import hudson.Util; import hudson.DescriptorExtensionList; import hudson.Extension; import hudson.model.*; import hudson.util.DescriptorList; import java.util.Collections; import java.util.HashMap; import jenkins.model.Jenkins; import org.jenkinsci.Symbol; import org.kohsuke.stapler.DataBoundConstructor; import javax.annotation.concurrent.GuardedBy; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; /** * Controls when to take {@link Computer} offline, bring it back online, or even to destroy it. * * @author Stephen Connolly * @author Kohsuke Kawaguchi */ public abstract class RetentionStrategy<T extends Computer> extends AbstractDescribableImpl<RetentionStrategy<?>> implements ExtensionPoint { /** * This method will be called periodically to allow this strategy to decide what to do with it's owning agent. * * @param c {@link Computer} for which this strategy is assigned. This computer may be online or offline. * This object also exposes a bunch of properties that the callee can use to decide what action to take. * @return The number of minutes after which the strategy would like to be checked again. The strategy may be * rechecked earlier or later that this! */ @GuardedBy("hudson.model.Queue.lock") public abstract long check(T c); /** * This method is called to determine whether manual launching of the agent is allowed at this point in time. * @param c {@link Computer} for which this strategy is assigned. This computer may be online or offline. * This object also exposes a bunch of properties that the callee can use to decide if manual launching is * allowed at this time. * @return {@code true} if manual launching of the agent is allowed at this point in time. */ public boolean isManualLaunchAllowed(T c) { return true; } /** * Returns {@code true} if the computer is accepting tasks. Needed to allow retention strategies programmatic * suspension of task scheduling that in preparation for going offline. Called by * {@link hudson.model.Computer#isAcceptingTasks()} * * @param c the computer. * @return {@code true} if the computer is accepting tasks * @see hudson.model.Computer#isAcceptingTasks() * @since 1.586 */ public boolean isAcceptingTasks(T c) { return true; } /** * Called when a new {@link Computer} object is introduced (such as when Hudson started, or when * a new agent is added.) * * <p> * The default implementation of this method delegates to {@link #check(Computer)}, * but this allows {@link RetentionStrategy} to distinguish the first time invocation from the rest. * * @since 1.275 */ public void start(final T c) { Queue.withLock(new Runnable() { @Override public void run() { check(c); } }); } /** * Returns all the registered {@link RetentionStrategy} descriptors. */ public static DescriptorExtensionList<RetentionStrategy<?>,Descriptor<RetentionStrategy<?>>> all() { return (DescriptorExtensionList) Jenkins.getInstance().getDescriptorList(RetentionStrategy.class); } /** * All registered {@link RetentionStrategy} implementations. * @deprecated as of 1.286 * Use {@link #all()} for read access, and {@link Extension} for registration. */ @Deprecated public static final DescriptorList<RetentionStrategy<?>> LIST = new DescriptorList<RetentionStrategy<?>>((Class)RetentionStrategy.class); /** * Dummy instance that doesn't do any attempt to retention. */ public static final RetentionStrategy<Computer> NOOP = new RetentionStrategy<Computer>() { @GuardedBy("hudson.model.Queue.lock") public long check(Computer c) { return 60; } @Override public void start(Computer c) { c.connect(false); } @Override public Descriptor<RetentionStrategy<?>> getDescriptor() { return DESCRIPTOR; } private final DescriptorImpl DESCRIPTOR = new DescriptorImpl(); class DescriptorImpl extends Descriptor<RetentionStrategy<?>> {} }; /** * Convenient singleton instance, since this {@link RetentionStrategy} is stateless. */ public static final Always INSTANCE = new Always(); /** * {@link RetentionStrategy} that tries to keep the node online all the time. */ public static class Always extends RetentionStrategy<SlaveComputer> { /** * Constructs a new Always. */ @DataBoundConstructor public Always() { } @GuardedBy("hudson.model.Queue.lock") public long check(SlaveComputer c) { if (c.isOffline() && !c.isConnecting() && c.isLaunchSupported()) c.tryReconnect(); return 1; } @Extension(ordinal=100) @Symbol("always") public static class DescriptorImpl extends Descriptor<RetentionStrategy<?>> { public String getDisplayName() { return Messages.RetentionStrategy_Always_displayName(); } } } /** * {@link hudson.slaves.RetentionStrategy} that tries to keep the node offline when not in use. */ public static class Demand extends RetentionStrategy<SlaveComputer> { private static final Logger logger = Logger.getLogger(Demand.class.getName()); /** * The delay (in minutes) for which the agent must be in demand before tring to launch it. */ private final long inDemandDelay; /** * The delay (in minutes) for which the agent must be idle before taking it offline. */ private final long idleDelay; @DataBoundConstructor public Demand(long inDemandDelay, long idleDelay) { this.inDemandDelay = Math.max(0, inDemandDelay); this.idleDelay = Math.max(1, idleDelay); } /** * Getter for property 'inDemandDelay'. * * @return Value for property 'inDemandDelay'. */ public long getInDemandDelay() { return inDemandDelay; } /** * Getter for property 'idleDelay'. * * @return Value for property 'idleDelay'. */ public long getIdleDelay() { return idleDelay; } @Override @GuardedBy("hudson.model.Queue.lock") public long check(final SlaveComputer c) { if (c.isOffline() && c.isLaunchSupported()) { final HashMap<Computer, Integer> availableComputers = new HashMap<Computer, Integer>(); for (Computer o : Jenkins.getInstance().getComputers()) { if ((o.isOnline() || o.isConnecting()) && o.isPartiallyIdle() && o.isAcceptingTasks()) { final int idleExecutors = o.countIdle(); if (idleExecutors>0) availableComputers.put(o, idleExecutors); } } boolean needComputer = false; long demandMilliseconds = 0; for (Queue.BuildableItem item : Queue.getInstance().getBuildableItems()) { // can any of the currently idle executors take this task? // assume the answer is no until we can find such an executor boolean needExecutor = true; for (Computer o : Collections.unmodifiableSet(availableComputers.keySet())) { Node otherNode = o.getNode(); if (otherNode != null && otherNode.canTake(item) == null) { needExecutor = false; final int availableExecutors = availableComputers.remove(o); if (availableExecutors > 1) { availableComputers.put(o, availableExecutors - 1); } else { availableComputers.remove(o); } break; } } // this 'item' cannot be built by any of the existing idle nodes, but it can be built by 'c' Node checkedNode = c.getNode(); if (needExecutor && checkedNode != null && checkedNode.canTake(item) == null) { demandMilliseconds = System.currentTimeMillis() - item.buildableStartMilliseconds; needComputer = demandMilliseconds > inDemandDelay * 1000 * 60 /*MINS->MILLIS*/; break; } } if (needComputer) { // we've been in demand for long enough logger.log(Level.INFO, "Launching computer {0} as it has been in demand for {1}", new Object[]{c.getName(), Util.getTimeSpanString(demandMilliseconds)}); c.connect(false); } } else if (c.isIdle()) { final long idleMilliseconds = System.currentTimeMillis() - c.getIdleStartMilliseconds(); if (idleMilliseconds > idleDelay * 1000 * 60 /*MINS->MILLIS*/) { // we've been idle for long enough logger.log(Level.INFO, "Disconnecting computer {0} as it has been idle for {1}", new Object[]{c.getName(), Util.getTimeSpanString(idleMilliseconds)}); c.disconnect(new OfflineCause.IdleOfflineCause()); } else { // no point revisiting until we can be confident we will be idle return TimeUnit.MILLISECONDS.toMinutes(TimeUnit.MINUTES.toMillis(idleDelay) - idleMilliseconds); } } return 1; } @Extension @Symbol("demand") public static class DescriptorImpl extends Descriptor<RetentionStrategy<?>> { @Override public String getDisplayName() { return Messages.RetentionStrategy_Demand_displayName(); } } } }