diff --git a/pom.xml b/pom.xml index 98f06a00..0e1f99c9 100644 --- a/pom.xml +++ b/pom.xml @@ -151,6 +151,12 @@ THE SOFTWARE. matrix-auth test + + org.jenkins-ci.plugins.pipeline-stage-view + pipeline-stage-view + 2.13 + test + org.jenkins-ci.plugins.workflow workflow-basic-steps diff --git a/src/main/java/hudson/plugins/throttleconcurrents/ThrottleQueueTaskDispatcher.java b/src/main/java/hudson/plugins/throttleconcurrents/ThrottleQueueTaskDispatcher.java index 4da52f63..597ccfc8 100644 --- a/src/main/java/hudson/plugins/throttleconcurrents/ThrottleQueueTaskDispatcher.java +++ b/src/main/java/hudson/plugins/throttleconcurrents/ThrottleQueueTaskDispatcher.java @@ -41,6 +41,7 @@ import org.jenkinsci.plugins.workflow.graph.StepNode; import org.jenkinsci.plugins.workflow.graphanalysis.LinearBlockHoppingScanner; import org.jenkinsci.plugins.workflow.steps.StepDescriptor; +import org.jenkinsci.plugins.workflow.support.actions.PauseAction; import org.jenkinsci.plugins.workflow.support.concurrent.Timeout; import org.jenkinsci.plugins.workflow.support.steps.ExecutorStepExecution.PlaceholderTask; @@ -54,14 +55,17 @@ public class ThrottleQueueTaskDispatcher extends QueueTaskDispatcher { @Deprecated @Override public @CheckForNull CauseOfBlockage canTake(Node node, Task task) { + CauseOfBlockage cause = null; if (Jenkins.getAuthentication().equals(ACL.SYSTEM)) { - return canTakeImpl(node, task); - } - - // Throttle-concurrent-builds requires READ permissions for all projects. - try (ACLContext ctx = ACL.as(ACL.SYSTEM)) { - return canTakeImpl(node, task); + cause = canTakeImpl(node, task); + } else { + // Throttle-concurrent-builds requires READ permissions for all projects. + try (ACLContext ctx = ACL.as(ACL.SYSTEM)) { + cause = canTakeImpl(node, task); + } } + updatePauseAction(task, cause); + return cause; } private CauseOfBlockage canTakeImpl(Node node, Task task) { @@ -218,14 +222,17 @@ private boolean shouldBeThrottled(@NonNull Task task, @CheckForNull ThrottleJobP } private CauseOfBlockage canRun(Task task, ThrottleJobProperty tjp, List pipelineCategories) { + CauseOfBlockage cause = null; if (Jenkins.getAuthentication().equals(ACL.SYSTEM)) { - return canRunImpl(task, tjp, pipelineCategories); - } - - // Throttle-concurrent-builds requires READ permissions for all projects. - try (ACLContext ctx = ACL.as(ACL.SYSTEM)) { - return canRunImpl(task, tjp, pipelineCategories); + cause = canRunImpl(task, tjp, pipelineCategories); + } else { + // Throttle-concurrent-builds requires READ permissions for all projects. + try (ACLContext ctx = ACL.as(ACL.SYSTEM)) { + cause = canRunImpl(task, tjp, pipelineCategories); + } } + updatePauseAction(task, cause); + return cause; } private CauseOfBlockage canRunImpl(Task task, ThrottleJobProperty tjp, List pipelineCategories) { @@ -695,5 +702,31 @@ private int getMaxConcurrentPerNodeBasedOnMatchingLabels( return maxConcurrentPerNodeLabeledIfMatch; } + private void updatePauseAction(Task task, CauseOfBlockage cause) { + if (task instanceof PlaceholderTask) { + PlaceholderTask placeholderTask = (PlaceholderTask) task; + try { + FlowNode flowNode = placeholderTask.getNode(); + if (flowNode == null) { + return; + } + + if (cause != null) { + if (PauseAction.getCurrentPause(flowNode) == null) { + flowNode.addAction(new PauseAction(cause.getShortDescription())); + } + } else { + if (PauseAction.getCurrentPause(flowNode) != null) { + PauseAction.endCurrentPause(flowNode); + } + } + } catch (IOException | InterruptedException e) { + LOGGER.log(Level.WARNING, "Error setting pause action on pipeline {0}: {1}", new Object[] { + task.getDisplayName(), e + }); + } + } + } + private static final Logger LOGGER = Logger.getLogger(ThrottleQueueTaskDispatcher.class.getName()); } diff --git a/src/test/java/hudson/plugins/throttleconcurrents/TestUtil.java b/src/test/java/hudson/plugins/throttleconcurrents/TestUtil.java index 37929815..fd95de0e 100644 --- a/src/test/java/hudson/plugins/throttleconcurrents/TestUtil.java +++ b/src/test/java/hudson/plugins/throttleconcurrents/TestUtil.java @@ -8,6 +8,7 @@ import hudson.model.Computer; import hudson.model.Executor; import hudson.model.Node; +import hudson.model.Queue; import hudson.model.queue.CauseOfBlockage; import hudson.slaves.DumbSlave; import hudson.slaves.RetentionStrategy; @@ -17,6 +18,7 @@ import java.util.Set; import jenkins.model.queue.CompositeCauseOfBlockage; import org.jenkinsci.plugins.workflow.job.WorkflowRun; +import org.jenkinsci.plugins.workflow.support.actions.PauseAction; import org.jenkinsci.plugins.workflow.support.steps.ExecutorStepExecution; import org.junit.rules.TemporaryFolder; import org.jvnet.hudson.test.JenkinsRule; @@ -96,6 +98,12 @@ static Set getBlockageReasons(CauseOfBlockage cob) { } } + static void hasPauseActionForItem(Queue.Item item) throws Exception { + assertTrue(item.task instanceof ExecutorStepExecution.PlaceholderTask); + ExecutorStepExecution.PlaceholderTask task = (ExecutorStepExecution.PlaceholderTask) item.task; + assertNotNull(task.getNode().getAction(PauseAction.class)); + } + static void hasPlaceholderTaskForRun(Node n, WorkflowRun r) throws Exception { for (Executor exec : n.toComputer().getExecutors()) { if (exec.getCurrentExecutable() != null) { diff --git a/src/test/java/hudson/plugins/throttleconcurrents/ThrottleStepTest.java b/src/test/java/hudson/plugins/throttleconcurrents/ThrottleStepTest.java index 07cec6d8..0a8c3b3e 100644 --- a/src/test/java/hudson/plugins/throttleconcurrents/ThrottleStepTest.java +++ b/src/test/java/hudson/plugins/throttleconcurrents/ThrottleStepTest.java @@ -95,6 +95,7 @@ public void onePerNode() throws Exception { blockageReasons, hasItem(Messages._ThrottleQueueTaskDispatcher_MaxCapacityOnNode(1) .toString())); + TestUtil.hasPauseActionForItem(queuedItem); assertEquals(1, agent.toComputer().countBusy()); TestUtil.hasPlaceholderTaskForRun(agent, firstJobFirstRun); @@ -176,6 +177,8 @@ public void multipleCategories() throws Exception { WorkflowRun thirdJobFirstRun = thirdJob.scheduleBuild2(0).waitForStart(); j.waitForMessage("Still waiting to schedule task", thirdJobFirstRun); assertFalse(j.jenkins.getQueue().isEmpty()); + Queue.Item queuedItem = j.jenkins.getQueue().getItems()[0]; + TestUtil.hasPauseActionForItem(queuedItem); assertEquals(1, firstAgent.toComputer().countBusy()); TestUtil.hasPlaceholderTaskForRun(firstAgent, firstJobFirstRun); @@ -245,6 +248,9 @@ public void onePerNodeParallel() throws Exception { j.waitForMessage("Still waiting to schedule task", run1); j.waitForMessage("Still waiting to schedule task", run2); + Queue.Item queuedItem = j.jenkins.getQueue().getItems()[0]; + TestUtil.hasPauseActionForItem(queuedItem); + SemaphoreStep.success("wait-first-branch-a-job/1", null); SemaphoreStep.waitForStart("wait-first-branch-c-job/1", run1); assertEquals(1, firstAgent.toComputer().countBusy()); @@ -293,6 +299,8 @@ public void twoTotal() throws Exception { j.waitForMessage("Still waiting to schedule task", thirdJobFirstRun); j.jenkins.getQueue().maintain(); assertFalse(j.jenkins.getQueue().isEmpty()); + Queue.Item queuedItem = j.jenkins.getQueue().getItems()[0]; + TestUtil.hasPauseActionForItem(queuedItem); assertEquals(1, firstAgent.toComputer().countBusy()); TestUtil.hasPlaceholderTaskForRun(firstAgent, firstJobFirstRun); @@ -377,6 +385,8 @@ public boolean perform(AbstractBuild build, Launcher launcher, BuildListen WorkflowRun secondJobFirstRun = secondJob.scheduleBuild2(0).waitForStart(); j.waitForMessage("Still waiting to schedule task", secondJobFirstRun); assertFalse(j.jenkins.getQueue().isEmpty()); + Queue.Item queuedItem = j.jenkins.getQueue().getItems()[0]; + TestUtil.hasPauseActionForItem(queuedItem); assertEquals(1, agent.toComputer().countBusy()); for (Executor e : agent.toComputer().getExecutors()) { @@ -460,6 +470,7 @@ private String getThrottleScript(String jobName, List categories, String return "throttle([" + StringUtils.join(quoted, ", ") + "]) {\n" + + "stage('wait') {\n" + " echo 'hi there'\n" + " node('" + label @@ -468,6 +479,7 @@ private String getThrottleScript(String jobName, List categories, String + jobName + "-job'\n" + " }\n" + + "}\n" + "}\n"; }