Skip to content

Commit

Permalink
[FIXED JENKINS-33721] - Add contextual term/kill in side panel.
Browse files Browse the repository at this point in the history
  • Loading branch information
abayer committed Nov 16, 2016
1 parent ea8fa1f commit 4a4d384
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 7 deletions.
44 changes: 40 additions & 4 deletions src/main/java/org/jenkinsci/plugins/workflow/job/WorkflowRun.java
Expand Up @@ -121,6 +121,14 @@ public final class WorkflowRun extends Run<WorkflowJob,WorkflowRun> implements F

private static final Logger LOGGER = Logger.getLogger(WorkflowRun.class.getName());

private enum StopState {
TERM, KILL;

public String url() {
return this.name().toLowerCase();
}
}

/** null until started, or after serious failures or hard kill */
private @CheckForNull FlowExecution execution;

Expand All @@ -136,6 +144,10 @@ public final class WorkflowRun extends Run<WorkflowJob,WorkflowRun> implements F
};
private transient StreamBuildListener listener;

private transient boolean allowTerm;

private transient boolean allowKill;

/**
* Flag for whether or not the build has completed somehow.
* Non-null soon after the build starts or is reloaded from disk.
Expand Down Expand Up @@ -260,7 +272,7 @@ private AsynchronousExecution sleep() {
LOGGER.log(Level.WARNING, null, x);
}
executor.recordCauseOfInterruption(WorkflowRun.this, listener);
printLater("term", "Click here to forcibly terminate running steps");
printLater(StopState.TERM, "Click here to forcibly terminate running steps");
}
});
}
Expand Down Expand Up @@ -302,13 +314,21 @@ private AsynchronousExecution sleep() {
return asynchronousExecution;
}

private void printLater(final String url, final String message) {
private void printLater(final StopState state, final String message) {
Timer.get().schedule(new Runnable() {
@Override public void run() {
if (!isInProgress()) {
return;
}
listener.getLogger().println(POSTHyperlinkNote.encodeTo("/" + getUrl() + url, message));
switch (state) {
case TERM:
setAllowTerm(true);
break;
case KILL:
setAllowKill(true);
break;
}
listener.getLogger().println(POSTHyperlinkNote.encodeTo("/" + getUrl() + state.url(), message));
}
}, 15, TimeUnit.SECONDS);
}
Expand Down Expand Up @@ -338,7 +358,7 @@ public void doTerm() {
}
@Override public void onFailure(Throwable t) {}
});
printLater("kill", "Click here to forcibly kill entire build");
printLater(StopState.KILL, "Click here to forcibly kill entire build");
}

/** Immediately kills the build. */
Expand Down Expand Up @@ -371,6 +391,22 @@ public void doKill() {
return env;
}

public void setAllowTerm(boolean b) {
this.allowTerm = b;
}

public boolean hasAllowTerm() {
return allowTerm && !allowKill;
}

public void setAllowKill(boolean b) {
this.allowKill = b;
}

public boolean hasAllowKill() {
return allowKill;
}

@GuardedBy("completed")
private void copyLogs() {
if (logsToCopy == null) { // finished
Expand Down
Expand Up @@ -34,6 +34,30 @@
<p:console-link/>
<l:task icon="images/24x24/notepad.png" href="${buildUrl.baseUrl}/configure" title="${h.hasPermission(it,it.UPDATE)?'%Edit Build Information':'%View Build Information'}"/>
<st:include page="delete.jelly"/>
<j:if test="${it.building}">
<j:choose>
<j:when test="${it.hasAllowTerm()}">
<l:task icon="images/24x24/edit-delete.png"
href="${buildUrl.baseUrl}/term"
title="${%Abort Running Steps}"
contextMenu="false"
post="true"
permission="${it.CANCEL}"
requiresConfirmation="true"
confirmationMessage="Really forcibly abort running steps?"/>
</j:when>
<j:when test="${it.hasAllowKill()}">
<l:task icon="images/24x24/edit-delete.png"
href="${buildUrl.baseUrl}/kill"
title="${%Force Abort of Entire Build}"
contextMenu="false"
post="true"
permission="${it.CANCEL}"
requiresConfirmation="true"
confirmationMessage="Really forcibly abort entire build?"/>
</j:when>
</j:choose>
</j:if>
<st:include page="actions.jelly"/>
<t:actions actions="${it.transientActions}"/>
<j:if test="${it.previousBuild!=null}">
Expand Down
Expand Up @@ -29,8 +29,6 @@
import hudson.model.Result;
import hudson.model.TaskListener;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl;
import org.jenkinsci.plugins.workflow.steps.AbstractStepExecutionImpl;
import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl;
Expand Down Expand Up @@ -84,6 +82,51 @@ public class WorkflowRunRestartTest {
}
});
}

@Issue("JENKINS-33721")
@Test public void termAndKillInSidePanel() throws Exception {
story.addStep(new Statement() {
@Override public void evaluate() throws Throwable {
WorkflowJob p = story.j.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition("def seq = 0; retry (99) {zombie id: ++seq}"));
WorkflowRun b = p.scheduleBuild2(0).waitForStart();
story.j.waitForMessage("[1] undead", b);
Executor ex = b.getExecutor();
assertNotNull(ex);
ex.interrupt();
story.j.waitForMessage("[1] bwahaha FlowInterruptedException #1", b);
ex.interrupt();
story.j.waitForMessage("[1] bwahaha FlowInterruptedException #2", b);
assertFalse(hasTermOrKillLink(b, "term"));
assertFalse(hasTermOrKillLink(b, "kill"));
story.j.waitForMessage("Click here to forcibly terminate running steps", b);
assertTrue(hasTermOrKillLink(b, "term"));
assertFalse(hasTermOrKillLink(b, "kill"));
b.doTerm();
story.j.waitForMessage("[2] undead", b);
story.j.waitForMessage("Click here to forcibly kill entire build", b);
assertFalse(hasTermOrKillLink(b, "term"));
assertTrue(hasTermOrKillLink(b, "kill"));
b.doKill();
story.j.waitForMessage("Hard kill!", b);
story.j.waitForCompletion(b);
story.j.assertBuildStatus(Result.ABORTED, b);
}
});
story.addStep(new Statement() {
@Override public void evaluate() throws Throwable {
WorkflowJob p = story.j.jenkins.getItemByFullName("p", WorkflowJob.class);
WorkflowRun b = p.getBuildByNumber(1);
assertFalse(b.isBuilding());
}
});
}

private boolean hasTermOrKillLink(WorkflowRun b, String termOrKill) throws Exception {
return !story.j.createWebClient().getPage(b)
.getByXPath("//a[@href = '#' and contains(@onclick, '/" + b.getUrl() + termOrKill + "')]").isEmpty();
}

public static class Zombie extends AbstractStepImpl {
@DataBoundSetter public int id;
@DataBoundConstructor public Zombie() {}
Expand All @@ -102,7 +145,7 @@ public static class Execution extends AbstractStepExecutionImpl {
}

}
@TestExtension("hardKill") public static class DescriptorImpl extends AbstractStepDescriptorImpl {
@TestExtension public static class DescriptorImpl extends AbstractStepDescriptorImpl {
public DescriptorImpl() {
super(Execution.class);
}
Expand Down

0 comments on commit 4a4d384

Please sign in to comment.