Skip to content

Commit 8188ae4

Browse files
Add PauseAction when throttling pipelines
The PauseAction is used by pipeline visualisers (such as pipeline-stage-view) to indicate when a stage has been paused, and for how long is was staged for. This can improve the display of stages timings, as the paused time is removed from the overall stage time. The PauseAction only applies to stages, so only applies when the throttle step is used in conjunction with a pipeline stage.
1 parent 90c127e commit 8188ae4

File tree

4 files changed

+72
-11
lines changed

4 files changed

+72
-11
lines changed

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,12 @@ THE SOFTWARE.
197197
<artifactId>pipeline-model-definition</artifactId>
198198
<scope>test</scope>
199199
</dependency>
200+
<dependency>
201+
<groupId>org.jenkins-ci.plugins.pipeline-stage-view</groupId>
202+
<artifactId>pipeline-stage-view</artifactId>
203+
<version>2.13</version>
204+
<scope>test</scope>
205+
</dependency>
200206
<dependency>
201207
<groupId>com.tngtech.jgiven</groupId>
202208
<artifactId>jgiven-junit</artifactId>

src/main/java/hudson/plugins/throttleconcurrents/ThrottleQueueTaskDispatcher.java

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.jenkinsci.plugins.workflow.graph.StepNode;
4242
import org.jenkinsci.plugins.workflow.graphanalysis.LinearBlockHoppingScanner;
4343
import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
44+
import org.jenkinsci.plugins.workflow.support.actions.PauseAction;
4445
import org.jenkinsci.plugins.workflow.support.concurrent.Timeout;
4546
import org.jenkinsci.plugins.workflow.support.steps.ExecutorStepExecution.PlaceholderTask;
4647

@@ -58,14 +59,17 @@ public class ThrottleQueueTaskDispatcher extends QueueTaskDispatcher {
5859
@Deprecated
5960
@Override
6061
public @CheckForNull CauseOfBlockage canTake(Node node, Task task) {
62+
CauseOfBlockage cause = null;
6163
if (Jenkins.getAuthentication().equals(ACL.SYSTEM)) {
62-
return canTakeImpl(node, task);
63-
}
64-
65-
// Throttle-concurrent-builds requires READ permissions for all projects.
66-
try (ACLContext ctx = ACL.as(ACL.SYSTEM)) {
67-
return canTakeImpl(node, task);
64+
cause = canTakeImpl(node, task);
65+
} else {
66+
// Throttle-concurrent-builds requires READ permissions for all projects.
67+
try (ACLContext ctx = ACL.as(ACL.SYSTEM)) {
68+
cause = canTakeImpl(node, task);
69+
}
6870
}
71+
updatePauseAction(task, cause);
72+
return cause;
6973
}
7074

7175
private CauseOfBlockage canTakeImpl(Node node, Task task) {
@@ -214,14 +218,17 @@ private boolean shouldBeThrottled(@NonNull Task task, @CheckForNull ThrottleJobP
214218
}
215219

216220
private CauseOfBlockage canRun(Task task, ThrottleJobProperty tjp, List<String> pipelineCategories) {
221+
CauseOfBlockage cause = null;
217222
if (Jenkins.getAuthentication().equals(ACL.SYSTEM)) {
218-
return canRunImpl(task, tjp, pipelineCategories);
219-
}
220-
223+
cause = canRunImpl(task, tjp, pipelineCategories);
224+
} else {
221225
// Throttle-concurrent-builds requires READ permissions for all projects.
222-
try (ACLContext ctx = ACL.as(ACL.SYSTEM)) {
223-
return canRunImpl(task, tjp, pipelineCategories);
226+
try (ACLContext ctx = ACL.as(ACL.SYSTEM)) {
227+
cause = canRunImpl(task, tjp, pipelineCategories);
228+
}
224229
}
230+
updatePauseAction(task, cause);
231+
return cause;
225232
}
226233

227234
private CauseOfBlockage canRunImpl(Task task, ThrottleJobProperty tjp, List<String> pipelineCategories) {
@@ -682,5 +689,32 @@ private int getMaxConcurrentPerNodeBasedOnMatchingLabels(
682689
return maxConcurrentPerNodeLabeledIfMatch;
683690
}
684691

692+
private void updatePauseAction(Task task, CauseOfBlockage cause) {
693+
if (task instanceof PlaceholderTask) {
694+
PlaceholderTask placeholderTask = (PlaceholderTask)task;
695+
try {
696+
FlowNode flowNode = placeholderTask.getNode();
697+
if (flowNode == null) {
698+
return;
699+
}
700+
701+
if (cause != null) {
702+
if (PauseAction.getCurrentPause(flowNode) == null) {
703+
flowNode.addAction(new PauseAction(cause.getShortDescription()));
704+
}
705+
} else {
706+
if (PauseAction.getCurrentPause(flowNode) != null) {
707+
PauseAction.endCurrentPause(flowNode);
708+
}
709+
}
710+
} catch (IOException|InterruptedException e) {
711+
LOGGER.log(
712+
Level.WARNING,
713+
"Error setting pause action on pipeline {0}: {1}",
714+
new Object[] {task.getDisplayName(), e});
715+
}
716+
}
717+
}
718+
685719
private static final Logger LOGGER = Logger.getLogger(ThrottleQueueTaskDispatcher.class.getName());
686720
}

src/test/java/hudson/plugins/throttleconcurrents/TestUtil.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88
import hudson.model.Computer;
99
import hudson.model.Executor;
1010
import hudson.model.Node;
11+
import hudson.model.Queue;
1112
import hudson.model.queue.CauseOfBlockage;
1213
import hudson.slaves.DumbSlave;
1314
import hudson.slaves.RetentionStrategy;
1415

1516
import jenkins.model.queue.CompositeCauseOfBlockage;
1617

1718
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
19+
import org.jenkinsci.plugins.workflow.support.actions.PauseAction;
1820
import org.jenkinsci.plugins.workflow.support.steps.ExecutorStepExecution;
1921
import org.junit.rules.TemporaryFolder;
2022
import org.jvnet.hudson.test.JenkinsRule;
@@ -106,6 +108,13 @@ static Set<String> getBlockageReasons(CauseOfBlockage cob) {
106108
}
107109
}
108110

111+
static void hasPauseActionForItem(Queue.Item item) throws Exception {
112+
assertTrue(item.task instanceof ExecutorStepExecution.PlaceholderTask);
113+
ExecutorStepExecution.PlaceholderTask task =
114+
(ExecutorStepExecution.PlaceholderTask)item.task;
115+
assertNotNull(task.getNode().getAction(PauseAction.class));
116+
}
117+
109118
static void hasPlaceholderTaskForRun(Node n, WorkflowRun r) throws Exception {
110119
for (Executor exec : n.toComputer().getExecutors()) {
111120
if (exec.getCurrentExecutable() != null) {

src/test/java/hudson/plugins/throttleconcurrents/ThrottleStepTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ public void onePerNode() throws Exception {
9393
assertThat(
9494
blockageReasons,
9595
hasItem(Messages._ThrottleQueueTaskDispatcher_MaxCapacityOnNode(1).toString()));
96+
TestUtil.hasPauseActionForItem(queuedItem);
9697
assertEquals(1, agent.toComputer().countBusy());
9798
TestUtil.hasPlaceholderTaskForRun(agent, firstJobFirstRun);
9899

@@ -188,6 +189,8 @@ public void multipleCategories() throws Exception {
188189
WorkflowRun thirdJobFirstRun = thirdJob.scheduleBuild2(0).waitForStart();
189190
j.waitForMessage("Still waiting to schedule task", thirdJobFirstRun);
190191
assertFalse(j.jenkins.getQueue().isEmpty());
192+
Queue.Item queuedItem = j.jenkins.getQueue().getItems()[0];
193+
TestUtil.hasPauseActionForItem(queuedItem);
191194
assertEquals(1, firstAgent.toComputer().countBusy());
192195
TestUtil.hasPlaceholderTaskForRun(firstAgent, firstJobFirstRun);
193196

@@ -277,6 +280,9 @@ public void onePerNodeParallel() throws Exception {
277280
j.waitForMessage("Still waiting to schedule task", run1);
278281
j.waitForMessage("Still waiting to schedule task", run2);
279282

283+
Queue.Item queuedItem = j.jenkins.getQueue().getItems()[0];
284+
TestUtil.hasPauseActionForItem(queuedItem);
285+
280286
SemaphoreStep.success("wait-first-branch-a-job/1", null);
281287
SemaphoreStep.waitForStart("wait-first-branch-c-job/1", run1);
282288
assertEquals(1, firstAgent.toComputer().countBusy());
@@ -330,6 +336,8 @@ public void twoTotal() throws Exception {
330336
j.waitForMessage("Still waiting to schedule task", thirdJobFirstRun);
331337
j.jenkins.getQueue().maintain();
332338
assertFalse(j.jenkins.getQueue().isEmpty());
339+
Queue.Item queuedItem = j.jenkins.getQueue().getItems()[0];
340+
TestUtil.hasPauseActionForItem(queuedItem);
333341
assertEquals(1, firstAgent.toComputer().countBusy());
334342
TestUtil.hasPlaceholderTaskForRun(firstAgent, firstJobFirstRun);
335343

@@ -421,6 +429,8 @@ public boolean perform(
421429
WorkflowRun secondJobFirstRun = secondJob.scheduleBuild2(0).waitForStart();
422430
j.waitForMessage("Still waiting to schedule task", secondJobFirstRun);
423431
assertFalse(j.jenkins.getQueue().isEmpty());
432+
Queue.Item queuedItem = j.jenkins.getQueue().getItems()[0];
433+
TestUtil.hasPauseActionForItem(queuedItem);
424434

425435
assertEquals(1, agent.toComputer().countBusy());
426436
for (Executor e : agent.toComputer().getExecutors()) {
@@ -503,6 +513,7 @@ private String getThrottleScript(String jobName, List<String> categories, String
503513
return "throttle(["
504514
+ StringUtils.join(quoted, ", ")
505515
+ "]) {\n"
516+
+ "stage('wait') {\n"
506517
+ " echo 'hi there'\n"
507518
+ " node('"
508519
+ label
@@ -511,6 +522,7 @@ private String getThrottleScript(String jobName, List<String> categories, String
511522
+ jobName
512523
+ "-job'\n"
513524
+ " }\n"
525+
+ "}\n"
514526
+ "}\n";
515527
}
516528

0 commit comments

Comments
 (0)