Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[JEP-210] Integration tests with DurableTaskStep #21

Merged
merged 47 commits into from
Oct 4, 2018
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
bcfe6a5
First draft of integration with new watch/Handler API.
jglick Oct 4, 2016
c080e32
Integration test for remote logging.
jglick Oct 5, 2016
4ad8e75
Reminder to pick up https://github.com/jenkinsci/jenkins-test-harness…
jglick Oct 6, 2016
1991f14
[FIXED JENKINS-31096] Proving that recoding in FileMonitoringTask.Wat…
jglick Oct 6, 2016
8c2f510
Picking up 1.17 test harness.
jglick Oct 10, 2016
2c43dba
BindingStepTest equivalent demonstrating that a ConsoleLogFilter may …
jglick Oct 10, 2016
940d360
jitpack.io for upstream https://github.com/jenkinsci/maven-hpi-plugin…
jglick Dec 7, 2016
6197075
Simpler idiom for tracking the name of an agent.
jglick Dec 8, 2016
de034fa
hpi-plugin.version=1.121
jglick Dec 16, 2016
a8dafb0
Extended JENKINS-34021 coverage to watch mode.
jglick Dec 20, 2016
363bafa
getLog should take a long start argument.
jglick Dec 21, 2016
0e00b9d
Merge branch 'robustness-JENKINS-40613' into logs-JENKINS-38381
jglick Jan 3, 2017
178c4e3
Giving up on jitpack for now.
jglick Jan 3, 2017
5dbf17e
Merge branch 'master' into logs-JENKINS-38381
jglick Jan 6, 2017
e8f5ab9
New snapshots.
jglick Jan 9, 2017
6f2f928
Merge branch 'master' into logs-JENKINS-38381
jglick Feb 5, 2018
1b89cb7
Minor additional fixups.
jglick Feb 5, 2018
b983dca
Fixing minor build issues.
jglick Feb 5, 2018
4e53e76
Test from https://github.com/jenkinsci/workflow-durable-task-step-plu…
jglick Feb 5, 2018
9e36ef2
Merge branch 'watch-JENKINS-38381' into logs-JENKINS-38381
jglick Feb 9, 2018
99301e5
Merge branch 'watch-plus-UTF-8-JENKINS-31096' into logs-JENKINS-38381
jglick Feb 13, 2018
c886dbc
Merge branch 'watch-plus-UTF-8-JENKINS-31096' into logs-JENKINS-38381
jglick Jun 11, 2018
cf9703d
requireUpperBoundDeps
jglick Jun 11, 2018
34125ba
Deleting LessAbstractTaskListener as it is superseded by https://gith…
jglick Jun 11, 2018
8601150
Superfluous double build.
jglick Jun 11, 2018
74dc7c3
Suppressing a new test on Windows for the moment.
jglick Jun 11, 2018
a9ef0c6
Some updates.
jglick Jun 18, 2018
4cf6db4
Refactored to LogStorage.
jglick Jun 20, 2018
46b4f43
Bumps.
jglick Jun 21, 2018
9040974
Merge branch 'watch-JENKINS-38381' into logs-JENKINS-38381
jglick Aug 7, 2018
9594a9e
Merge branch 'watch-JENKINS-38381' into logs-JENKINS-38381
jglick Aug 8, 2018
04562f4
Adapted to https://github.com/jenkinsci/workflow-api-plugin/pull/17/c…
jglick Aug 10, 2018
7b07da9
Merge branch 'watch-JENKINS-38381' into logs-JENKINS-38381
jglick Aug 13, 2018
1a53d3d
Now using FileLogStorage.
jglick Aug 13, 2018
8668f02
Merge branch 'watch-JENKINS-38381' into logs-JENKINS-38381
jglick Aug 14, 2018
dfadac1
More interesting ShellStepTest.remoteLogger: upcase output on the rem…
jglick Aug 15, 2018
a8e9da4
Merge branch 'watch-JENKINS-38381' into logs-JENKINS-38381
jglick Aug 22, 2018
77b8bd3
Merge branch 'watch-JENKINS-38381' into logs-JENKINS-38381
jglick Sep 4, 2018
c66d650
Merge branch 'watch-JENKINS-38381' into logs-JENKINS-38381
jglick Sep 4, 2018
587b863
Bumps.
jglick Sep 4, 2018
221f1ac
Added another test demonstrating that watch mode with an external log…
jglick Sep 4, 2018
9674f84
Speeding up priming builds for ShellStepTest.remoteVoluminousLogger.
jglick Sep 5, 2018
2558f96
Merge branch 'master' into logs-JENKINS-38381
jglick Sep 13, 2018
b97162b
Merge branch 'master' into logs-JENKINS-38381
jglick Sep 25, 2018
648d2f2
Bumps.
jglick Sep 25, 2018
68614d2
Flaky test.
jglick Sep 25, 2018
d8d15fc
Using release versions.
jglick Oct 4, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 20 additions & 9 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
</pluginRepository>
</pluginRepositories>
<properties>
<jenkins.version>2.60.3</jenkins.version>
<jenkins.version>2.62</jenkins.version>
<java.level>8</java.level>
<workflow-step-api-plugin.version>2.13</workflow-step-api-plugin.version>
</properties>
Expand All @@ -75,17 +75,17 @@
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>durable-task</artifactId>
<version>1.18-20180125.194359-1</version> <!-- TODO https://github.com/jenkinsci/durable-task-plugin/pull/57 -->
<version>1.18-SNAPSHOT</version> <!-- TODO pending https://github.com/jenkinsci/durable-task-plugin/pull/29 -->
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-api</artifactId>
<version>2.22</version>
<version>2.26-SNAPSHOT</version> <!-- TODO pending https://github.com/jenkinsci/workflow-api-plugin/pull/17 -->
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-support</artifactId>
<version>2.16</version>
<version>2.19-SNAPSHOT</version> <!-- TODO pending https://github.com/jenkinsci/workflow-support-plugin/pull/15 -->
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
Expand All @@ -96,13 +96,13 @@
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-job</artifactId>
<version>2.9</version>
<version>2.18-SNAPSHOT</version> <!-- TODO pending https://github.com/jenkinsci/workflow-job-plugin/pull/27 -->
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-basic-steps</artifactId>
<version>1.15</version>
<version>2.2</version>
<scope>test</scope>
</dependency>
<dependency>
Expand All @@ -121,10 +121,16 @@
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-support</artifactId>
<version>2.13</version>
<version>2.19-SNAPSHOT</version> <!-- TODO pending https://github.com/jenkinsci/workflow-support-plugin/pull/15 -->
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>credentials-binding</artifactId>
<version>1.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-scm-step</artifactId>
Expand All @@ -134,12 +140,17 @@
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>script-security</artifactId>
<version>1.27</version>
<version>1.39</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>structs</artifactId>
<version>1.7</version>
<version>1.9</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>scm-api</artifactId>
<version>2.2.6</version>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@
import hudson.EnvVars;
import hudson.FilePath;
import hudson.Launcher;
import hudson.Main;
import hudson.model.TaskListener;
import hudson.util.DaemonThreadFactory;
import hudson.util.FormValidation;
import hudson.util.LogTaskListener;
import hudson.util.NamingThreadFactory;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
Expand All @@ -46,8 +48,10 @@
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import jenkins.util.Timer;
import org.apache.commons.io.IOUtils;
import org.jenkinsci.plugins.durabletask.Controller;
import org.jenkinsci.plugins.durabletask.DurableTask;
import org.jenkinsci.plugins.durabletask.Handler;
import org.jenkinsci.plugins.workflow.FilePathUtils;
import org.jenkinsci.plugins.workflow.steps.AbstractStepExecutionImpl;
import org.jenkinsci.plugins.workflow.steps.Step;
Expand All @@ -67,6 +71,7 @@ public abstract class DurableTaskStep extends Step {
private static final Logger LOGGER = Logger.getLogger(DurableTaskStep.class.getName());

private boolean returnStdout;
// TODO should there be an option to use the native encoding of the machine running the task? Similarly for readFile, writeFile?
private String encoding = DurableTaskStepDescriptor.defaultEncoding;
private boolean returnStatus;

Expand Down Expand Up @@ -129,15 +134,20 @@ public FormValidation doCheckReturnStatus(@QueryParameter boolean returnStdout,

}

interface ExecutionRemotable {
void exited(int code, byte[] output) throws Exception;
}

/**
* Represents one task that is believed to still be running.
*/
@SuppressFBWarnings(value="SE_TRANSIENT_FIELD_NOT_RESTORED", justification="recurrencePeriod is set in onResume, not deserialization")
static final class Execution extends AbstractStepExecutionImpl implements Runnable {
static final class Execution extends AbstractStepExecutionImpl implements Runnable, ExecutionRemotable {

private static final long MIN_RECURRENCE_PERIOD = 250; // ¼s
private static final long MAX_RECURRENCE_PERIOD = 15000; // 15s
private static final float RECURRENCE_PERIOD_BACKOFF = 1.2f;
private static final long WATCHING_RECURRENCE_PERIOD = Main.isUnitTest ? /* 5s */5_000: /* 5m */300_000;

private static final ScheduledThreadPoolExecutor THREAD_POOL = new ScheduledThreadPoolExecutor(25, new NamingThreadFactory(new DaemonThreadFactory(), DurableTaskStep.class.getName()));
static {
Expand All @@ -156,6 +166,7 @@ static final class Execution extends AbstractStepExecutionImpl implements Runnab
private boolean returnStdout; // serialized default is false
private String encoding; // serialized default is irrelevant
private boolean returnStatus; // serialized default is false
private boolean watching;

Execution(StepContext context, DurableTaskStep step) {
super(context);
Expand All @@ -173,9 +184,16 @@ static final class Execution extends AbstractStepExecutionImpl implements Runnab
if (returnStdout) {
durableTask.captureOutput();
}
controller = durableTask.launch(context.get(EnvVars.class), ws, context.get(Launcher.class), context.get(TaskListener.class));
TaskListener listener = context.get(TaskListener.class);
controller = durableTask.launch(context.get(EnvVars.class), ws, context.get(Launcher.class), listener);
this.remote = ws.getRemote();
setupTimer();
try {
controller.watch(ws, new HandlerImpl(this, ws, listener), listener);
watching = true;
} catch (UnsupportedOperationException x) {
LOGGER.log(Level.WARNING, null, x);
}
setupTimer(watching ? WATCHING_RECURRENCE_PERIOD : MIN_RECURRENCE_PERIOD);
return false;
}

Expand All @@ -186,6 +204,19 @@ static final class Execution extends AbstractStepExecutionImpl implements Runnab
LOGGER.log(Level.FINE, "Jenkins is not running, no such node {0}, or it is offline", node);
return null;
}
if (watching) {
try {
controller.watch(ws, new HandlerImpl(this, ws, listener()), listener());
recurrencePeriod = WATCHING_RECURRENCE_PERIOD;
} catch (UnsupportedOperationException x) {
getContext().onFailure(x);
} catch (Exception x) { // as below
LOGGER.log(Level.FINE, node + " is evidently offline now", x);
ws = null;
recurrencePeriod = MIN_RECURRENCE_PERIOD;
return null;
}
}
}
boolean directory;
try (Timeout timeout = Timeout.limit(10, TimeUnit.SECONDS)) {
Expand All @@ -194,6 +225,7 @@ static final class Execution extends AbstractStepExecutionImpl implements Runnab
// RequestAbortedException, ChannelClosedException, EOFException, wrappers thereof; InterruptedException if it just takes too long.
LOGGER.log(Level.FINE, node + " is evidently offline now", x);
ws = null;
recurrencePeriod = MIN_RECURRENCE_PERIOD;
if (!printedCannotContactMessage) {
listener().getLogger().println("Cannot contact " + node + ": " + x);
printedCannotContactMessage = true;
Expand All @@ -203,6 +235,7 @@ static final class Execution extends AbstractStepExecutionImpl implements Runnab
if (!directory) {
throw new AbortException("missing workspace " + remote + " on " + node);
}
LOGGER.log(Level.FINE, "{0} seems to be online", node);
return ws;
}

Expand Down Expand Up @@ -309,10 +342,20 @@ private void check() {
return;
}
if (workspace == null) {
recurrencePeriod = Math.min((long) (recurrencePeriod * RECURRENCE_PERIOD_BACKOFF), MAX_RECURRENCE_PERIOD);
return; // slave not yet ready, wait for another day
}
TaskListener listener = listener();
try (Timeout timeout = Timeout.limit(10, TimeUnit.SECONDS)) {
if (watching) {
Integer exitCode = controller.exitStatus(workspace, launcher());
if (exitCode == null) {
LOGGER.log(Level.FINE, "still running in {0} on {1}", new Object[] {remote, node});
} else {
LOGGER.log(Level.FINE, "exited with {0} in {1} on {2}; expect asynchronous exit soon", new Object[] {exitCode, remote, node});
// TODO if we get here again and exited has still not been called, assume we lost the notification somehow and end the step
}
} else { // legacy mode
if (controller.writeLog(workspace, listener.getLogger())) {
getContext().saveState();
recurrencePeriod = MIN_RECURRENCE_PERIOD; // got output, maybe we will get more soon
Expand All @@ -337,6 +380,7 @@ private void check() {
recurrencePeriod = 0;
controller.cleanup(workspace);
}
}
} catch (Exception x) {
LOGGER.log(Level.FINE, "could not check " + workspace, x);
ws = null;
Expand All @@ -347,17 +391,67 @@ private void check() {
}
}

// called remotely from HandlerImpl
@Override public void exited(int exitCode, byte[] output) throws Exception {
try {
getContext().get(TaskListener.class);
} catch (IOException | InterruptedException x) {
LOGGER.log(Level.FINE, "asynchronous exit notification with code " + exitCode + " in " + remote + " on " + node + " ignored since step already seems dead", x);
return;
}
LOGGER.log(Level.FINE, "asynchronous exit notification with code {0} in {1} on {2}", new Object[] {exitCode, remote, node});
if (returnStdout && output == null) {
getContext().onFailure(new IllegalStateException("expected output but got none"));
return;
} else if (!returnStdout && output != null) {
getContext().onFailure(new IllegalStateException("did not expect output but got some"));
return;
}
recurrencePeriod = 0;
if (returnStatus || exitCode == 0) {
getContext().onSuccess(returnStatus ? exitCode : returnStdout ? new String(output, encoding) : null);
} else {
if (returnStdout) {
listener().getLogger().write(output); // diagnostic
}
getContext().onFailure(new AbortException("script returned exit code " + exitCode));
}
}

@Override public void onResume() {
setupTimer();
ws = null; // find it from scratch please, rewatching as needed
setupTimer(MIN_RECURRENCE_PERIOD);
}

private void setupTimer() {
recurrencePeriod = MIN_RECURRENCE_PERIOD;
private void setupTimer(long initialRecurrencePeriod) {
recurrencePeriod = initialRecurrencePeriod;
task = THREAD_POOL.schedule(this, recurrencePeriod, TimeUnit.MILLISECONDS);
}

private static final long serialVersionUID = 1L;

}

private static class HandlerImpl extends Handler {

private static final long serialVersionUID = 1L;

private final ExecutionRemotable execution;
private final TaskListener listener;

HandlerImpl(Execution execution, FilePath workspace, TaskListener listener) {
this.execution = workspace.getChannel().export(ExecutionRemotable.class, execution);
this.listener = listener;
}

@Override public void output(InputStream stream) throws Exception {
IOUtils.copy(stream, listener.getLogger());
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be interrupted somehow when stop is called.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(from #63)

}

@Override public void exited(int code, byte[] output) throws Exception {
execution.exited(code, output);
}

}

}
Loading