diff --git a/core/src/main/java/hudson/model/Node.java b/core/src/main/java/hudson/model/Node.java index 74bdab88c28279502e217f30a0ae41c3d3ed68d8..8ffb617430547831fef23893476a368ecb8dc022 100644 --- a/core/src/main/java/hudson/model/Node.java +++ b/core/src/main/java/hudson/model/Node.java @@ -27,6 +27,8 @@ import hudson.ExtensionPoint; import hudson.FilePath; import hudson.FileSystemProvisioner; import hudson.Launcher; +import hudson.model.Queue.Task; +import hudson.model.queue.CauseOfBlockage; import hudson.node_monitors.NodeMonitor; import hudson.remoting.VirtualChannel; import hudson.security.ACL; @@ -221,6 +223,35 @@ public abstract class Node extends AbstractModelObject implements Describable + * To register your dispatcher implementations, put @{@link Extension} on your subtypes. + * + * @author Kohsuke Kawaguchi + * @since 1.360 + */ +public abstract class QueueTaskDispatcher implements ExtensionPoint { + /** + * Called whenever {@link Queue} is considering to execute the given task on a given node. + * + *

+ * Implementations can return null to indicate that the assignment is fine, or it can return + * a non-null instance to block the execution of the task on the given node. + * + *

+ * Queue doesn't remember/cache the response from dispatchers, and instead it'll keep asking. + * The upside of this is that it's very easy to block execution for a limited time period ( + * as you just need to return null when it's ready to execute.) The downside of this is that + * the decision needs to be made quickly. + * + *

+ * Vetos are additive. When multiple {@link QueueTaskDispatcher}s are in the system, + * the task won't run on the given node if any one of them returns a non-null value. + * (This relationship is also the same with built-in check logic.) + */ + public abstract CauseOfBlockage canTake(Node node, Task task); + + /** + * All registered {@link QueueTaskDispatcher}s. + */ + public static ExtensionList all() { + return Hudson.getInstance().getExtensionList(QueueTaskDispatcher.class); + } +} \ No newline at end of file diff --git a/core/src/main/java/hudson/slaves/NodeProperty.java b/core/src/main/java/hudson/slaves/NodeProperty.java index 0916db0875e0ae64d468114bb7b1334edead4cfc..6d6cd0e8d134011db0bf941864ec05552dfc01e5 100644 --- a/core/src/main/java/hudson/slaves/NodeProperty.java +++ b/core/src/main/java/hudson/slaves/NodeProperty.java @@ -26,6 +26,7 @@ package hudson.slaves; import hudson.ExtensionPoint; import hudson.Launcher; import hudson.DescriptorExtensionList; +import hudson.model.queue.CauseOfBlockage; import hudson.scm.SCM; import hudson.model.AbstractBuild; import hudson.model.BuildListener; @@ -33,6 +34,7 @@ import hudson.model.Describable; import hudson.model.Environment; import hudson.model.Hudson; import hudson.model.Node; +import hudson.model.Queue.Task; import java.io.IOException; import java.util.List; @@ -69,6 +71,18 @@ public abstract class NodeProperty implements Describablenull. + * + * @since 1.360 + */ + public CauseOfBlockage canTake(Task task) { + return null; + } + /** * Runs before the {@link SCM#checkout(AbstractBuild, Launcher, FilePath, BuildListener, File)} runs, and performs a set up. * Can contribute additional properties to the environment. diff --git a/core/src/main/resources/hudson/model/Messages.properties b/core/src/main/resources/hudson/model/Messages.properties index c7d594ddf2f29e867693eaa99deba2b964a05d45..6b0a092d34eb0b6b3b5503b94b17ca26d1eec6c2 100644 --- a/core/src/main/resources/hudson/model/Messages.properties +++ b/core/src/main/resources/hudson/model/Messages.properties @@ -141,6 +141,8 @@ Label.GroupOf=group of {0} Label.InvalidLabel=invalid label Label.ProvisionedFrom=Provisioned from {0} MultiStageTimeSeries.EMPTY_STRING= +Node.BecauseNodeIsReserved={0} is reserved for jobs tied to it +Node.LabelMissing={0} doesn''t have label {1} Queue.AllNodesOffline=All nodes of label ''{0}'' are offline Queue.BlockedBy=Blocked by {0} Queue.HudsonIsAboutToShutDown=Hudson is about to shut down diff --git a/test/src/test/java/hudson/slaves/NodeCanTakeTaskTest.java b/test/src/test/java/hudson/slaves/NodeCanTakeTaskTest.java new file mode 100644 index 0000000000000000000000000000000000000000..6974f7bd6cada6fa694bb87dd99280f03ba91cf4 --- /dev/null +++ b/test/src/test/java/hudson/slaves/NodeCanTakeTaskTest.java @@ -0,0 +1,90 @@ +/* + * The MIT License + * + * Copyright (c) 2010, InfraDNA, Inc. + * + * 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 java.util.List; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import hudson.model.FreeStyleBuild; +import hudson.model.FreeStyleProject; +import hudson.model.Messages; +import hudson.model.Node; +import hudson.model.Node.Mode; +import hudson.model.Project; +import hudson.model.Result; +import hudson.model.Queue.BuildableItem; +import hudson.model.Queue.Task; +import hudson.model.Slave; +import hudson.model.queue.CauseOfBlockage; + +import junit.framework.Assert; + +import org.jvnet.hudson.test.HudsonTestCase; + +public class NodeCanTakeTaskTest extends HudsonTestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + + // Set master executor count to zero to force all jobs to slaves + hudson.setNumExecutors(0); + } + + public void testTakeBlockedByProperty() throws Exception { + Slave slave = createSlave(); + FreeStyleProject project = createFreeStyleProject(); + + // First, attempt to run our project before adding the property + Future build = project.scheduleBuild2(0); + assertBuildStatus(Result.SUCCESS, build.get(10, TimeUnit.SECONDS)); + + // Add the build-blocker property and try again + slave.getNodeProperties().add(new RejectAllTasksProperty()); + + build = project.scheduleBuild2(0); + try { + build.get(10, TimeUnit.SECONDS); + fail("Expected timeout exception"); + } catch (TimeoutException e) { + List buildables = hudson.getQueue().getBuildableItems(); + Assert.assertNotNull(buildables); + Assert.assertEquals(1, buildables.size()); + + BuildableItem item = buildables.get(0); + Assert.assertEquals(project, item.task); + Assert.assertNotNull(item.getCauseOfBlockage()); + Assert.assertEquals(Messages.Queue_WaitingForNextAvailableExecutor(), item.getCauseOfBlockage().getShortDescription()); + } + } + + private static class RejectAllTasksProperty extends NodeProperty { + @Override + public CauseOfBlockage canTake(Task task) { + return CauseOfBlockage.fromMessage(null); + } + } +}