Skip to content
Permalink
Browse files

Merge pull request #75 from svanoort/disable-pipeline-resume-JENKINS-…

…33761

Provide job property for durability hints & add ability to disable pipeline resume [JENKINS-33761]
  • Loading branch information...
svanoort committed Jan 22, 2018
2 parents 2dfc94a + fe629db commit 5d3b91a68514d74422cc4ec5bc67d99418d7962c
42 pom.xml
@@ -65,7 +65,7 @@
<jenkins.version>2.62</jenkins.version>
<java.level>8</java.level>
<no-test-jar>false</no-test-jar>
<workflow-support-plugin.version>2.16</workflow-support-plugin.version>
<workflow-support-plugin.version>2.17</workflow-support-plugin.version>
<scm-api-plugin.version>2.1.1</scm-api-plugin.version>
<git-plugin.version>3.2.0</git-plugin.version>
</properties>
@@ -78,17 +78,24 @@
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-api</artifactId>
<version>2.22</version>
<version>2.25</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-support</artifactId>
<version>${workflow-support-plugin.version}</version>
</dependency>
<dependency>
<!-- Satisfy upper bound dependencies -->
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>script-security</artifactId>
<version>1.39</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-cps</artifactId>
<version>2.29</version>
<version>2.43</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -141,6 +148,14 @@
<version>${git-plugin.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</exclusion>
<exclusion> <!-- TODO pending https://github.com/jenkinsci/git-client-plugin/pull/264 -->
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
@@ -153,6 +168,16 @@
<version>${git-plugin.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
@@ -167,4 +192,15 @@
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<reuseForks>false</reuseForks> <!-- TODO reuse seems to cause problems in memory tests -->
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -88,9 +88,12 @@
import jenkins.model.lazy.LazyBuildMixIn;
import jenkins.triggers.SCMTriggerItem;
import net.sf.json.JSONObject;
import org.acegisecurity.Authentication;
import org.jenkinsci.plugins.workflow.flow.BlockableResume;
import org.jenkinsci.plugins.workflow.flow.FlowDefinition;
import org.jenkinsci.plugins.workflow.flow.FlowDefinitionDescriptor;
import org.jenkinsci.plugins.workflow.job.properties.DisableConcurrentBuildsJobProperty;
import org.jenkinsci.plugins.workflow.job.properties.DisableResumeJobProperty;
import org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
@@ -100,7 +103,7 @@
import org.kohsuke.stapler.export.Exported;

@SuppressWarnings({"unchecked", "rawtypes"})
public final class WorkflowJob extends Job<WorkflowJob,WorkflowRun> implements LazyBuildMixIn.LazyLoadingJob<WorkflowJob,WorkflowRun>, ParameterizedJobMixIn.ParameterizedJob<WorkflowJob, WorkflowRun>, TopLevelItem, Queue.FlyweightTask, SCMTriggerItem {
public final class WorkflowJob extends Job<WorkflowJob,WorkflowRun> implements LazyBuildMixIn.LazyLoadingJob<WorkflowJob,WorkflowRun>, ParameterizedJobMixIn.ParameterizedJob<WorkflowJob, WorkflowRun>, TopLevelItem, Queue.FlyweightTask, SCMTriggerItem, BlockableResume {

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

@@ -113,6 +116,7 @@
private transient LazyBuildMixIn<WorkflowJob,WorkflowRun> buildMixIn;
/** @deprecated replaced by {@link DisableConcurrentBuildsJobProperty} */
private @CheckForNull Boolean concurrentBuild;

/**
* Map from {@link SCM#getKey} to last version we encountered during polling.
* TODO is it important to persist this? {@link hudson.model.AbstractProject#pollingBaseline} is not persisted.
@@ -323,6 +327,32 @@ public void setQuietPeriod(Integer seconds) throws IOException {
return getProperty(DisableConcurrentBuildsJobProperty.class) == null;
}

@Exported
public boolean isResumeBlocked() {
return getProperty(DisableResumeJobProperty.class) != null;
}

public void setResumeBlocked(boolean resumeBlocked) {
try {
boolean previousState = isResumeBlocked();
if (resumeBlocked != previousState) {
BulkChange bc = new BulkChange(this);
try {
removeProperty(DisableResumeJobProperty.class);
if (resumeBlocked) {
addProperty(new DisableResumeJobProperty());
}
bc.commit();
} finally {
bc.abort();
}
}
} catch (IOException ioe) {
LOGGER.log(Level.WARNING, "Error persisting resume property statue", ioe);
}

}

public void setConcurrentBuild(boolean b) throws IOException {
concurrentBuild = null;

@@ -690,6 +720,8 @@ public String getIconFilePathPattern() {
return DescriptorVisibilityFilter.apply(context, ExtensionList.lookup(FlowDefinitionDescriptor.class));
}



}

}
@@ -34,6 +34,7 @@
import com.google.common.util.concurrent.SettableFuture;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.AbortException;
import hudson.BulkChange;
import hudson.EnvVars;
import hudson.Extension;
import hudson.FilePath;
@@ -54,6 +55,7 @@
import hudson.model.User;
import hudson.model.listeners.RunListener;
import hudson.model.listeners.SCMListener;
import hudson.model.listeners.SaveableListener;
import hudson.scm.ChangeLogSet;
import hudson.scm.SCM;
import hudson.scm.SCMRevisionState;
@@ -106,8 +108,11 @@
import org.jenkinsci.plugins.workflow.actions.LogAction;
import org.jenkinsci.plugins.workflow.actions.ThreadNameAction;
import org.jenkinsci.plugins.workflow.actions.TimingAction;
import org.jenkinsci.plugins.workflow.flow.BlockableResume;
import org.jenkinsci.plugins.workflow.flow.DurabilityHintProvider;
import org.jenkinsci.plugins.workflow.flow.FlowCopier;
import org.jenkinsci.plugins.workflow.flow.FlowDefinition;
import org.jenkinsci.plugins.workflow.flow.FlowDurabilityHint;
import org.jenkinsci.plugins.workflow.flow.FlowExecution;
import org.jenkinsci.plugins.workflow.flow.FlowExecutionList;
import org.jenkinsci.plugins.workflow.flow.FlowExecutionListener;
@@ -118,9 +123,11 @@
import org.jenkinsci.plugins.workflow.graph.FlowEndNode;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
import org.jenkinsci.plugins.workflow.job.console.WorkflowConsoleLogger;
import org.jenkinsci.plugins.workflow.job.properties.DurabilityHintJobProperty;
import org.jenkinsci.plugins.workflow.steps.FlowInterruptedException;
import org.jenkinsci.plugins.workflow.steps.StepContext;
import org.jenkinsci.plugins.workflow.steps.StepExecution;
import org.jenkinsci.plugins.workflow.support.PipelineIOUtils;
import org.jenkinsci.plugins.workflow.support.concurrent.Futures;
import org.jenkinsci.plugins.workflow.support.concurrent.WithThreadName;
import org.jenkinsci.plugins.workflow.support.steps.input.POSTHyperlinkNote;
@@ -259,9 +266,22 @@ public WorkflowRun(WorkflowJob job, File dir) throws IOException {
if (definition == null) {
throw new AbortException("No flow definition, cannot run");
}

Owner owner = new Owner(this);

FlowExecution newExecution = definition.create(owner, listener, getAllActions());

boolean loggedHintOverride = false;
if (getParent().isResumeBlocked()) {
if (newExecution instanceof BlockableResume) {
((BlockableResume) newExecution).setResumeBlocked(true);
listener.getLogger().println("Resume disabled by user, switching to high-performance, low-durability mode.");
loggedHintOverride = true;
}
}
if (!loggedHintOverride) { // Avoid double-logging
listener.getLogger().println("Running in Durability level: "+DurabilityHintProvider.suggestedFor(this.project));
}

FlowExecutionList.get().register(owner);
newExecution.addListener(new GraphL());
completed = new AtomicBoolean();
@@ -520,7 +540,9 @@ private void copyLogs() {
}
if (modified) {
try {
save();
if (this.execution != null && this.execution.getDurabilityHint().isPersistWithEveryStep()) {
save();
}
} catch (IOException x) {
LOGGER.log(Level.WARNING, null, x);
}
@@ -604,20 +626,25 @@ private String key() {
if (completed != null) {
throw new IllegalStateException("double onLoad of " + this);
}
if (execution != null) {
FlowExecution fetchedExecution = execution;
if (fetchedExecution != null) {
try {
execution.onLoad(new Owner(this));
if (getParent().isResumeBlocked() && execution instanceof BlockableResume) {
((BlockableResume) execution).setResumeBlocked(true);
}
fetchedExecution.onLoad(new Owner(this));
} catch (Exception x) {
LOGGER.log(Level.WARNING, null, x);
execution = null; // probably too broken to use
}
}
if (execution != null) {
execution.addListener(new GraphL());
executionPromise.set(execution);
if (!execution.isComplete()) {
fetchedExecution = execution;
if (fetchedExecution != null) {
fetchedExecution.addListener(new GraphL());
executionPromise.set(fetchedExecution);
if (!fetchedExecution.isComplete()) {
// we've been restarted while we were running. let's get the execution going again.
FlowExecutionListener.fireResumed(execution);
FlowExecutionListener.fireResumed(fetchedExecution);

try {
OutputStream logger = new FileOutputStream(getLogFile(), true);
@@ -969,7 +996,10 @@ public int hashCode() {
copyLogs();
logsToCopy.put(node.getId(), 0L);
}
node.addAction(new TimingAction());

if (node.getPersistentAction(TimingAction.class) == null) {
node.addAction(new TimingAction());
}

logNodeMessage(node);
if (node instanceof FlowEndNode) {
@@ -1021,6 +1051,23 @@ static void alias() {
((WorkflowRun)copy).checkouts(null).addAll(((WorkflowRun)original).checkouts(null));
}
}
}

@Override
public synchronized void save() throws IOException {

if(BulkChange.contains(this)) return;
File loc = new File(getRootDir(),"build.xml");
XmlFile file = new XmlFile(XSTREAM,loc);

boolean isAtomic = true;

if (this.execution != null) {
FlowDurabilityHint hint = this.execution.getDurabilityHint();
isAtomic = hint.isAtomicWrite();
}

PipelineIOUtils.writeByXStream(this, loc, XSTREAM2, isAtomic);
SaveableListener.fireOnChange(this, file);
}
}
@@ -0,0 +1,46 @@
package org.jenkinsci.plugins.workflow.job.properties;

import hudson.Extension;
import hudson.model.Item;
import jenkins.model.OptionalJobProperty;
import org.jenkinsci.Symbol;
import org.jenkinsci.plugins.workflow.flow.DurabilityHintProvider;
import org.jenkinsci.plugins.workflow.flow.FlowDurabilityHint;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.kohsuke.stapler.DataBoundConstructor;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;

/**
* Disables resuming a pipeline if the master restarts - the run will simply fail instead, just like a FreeStyle job.
* @author Sam Van Oort
*/
public class DisableResumeJobProperty extends OptionalJobProperty<WorkflowJob> {
@DataBoundConstructor
public DisableResumeJobProperty(){ }

@Extension
@Symbol("disableResume")
public static class DescriptorImpl extends OptionalJobPropertyDescriptor implements DurabilityHintProvider{

@Override public String getDisplayName() {
return "Do not allow the pipeline to resume if the master restarts.";
}

@Override
public int ordinal() {
return 50;
}

@CheckForNull
@Override
public FlowDurabilityHint suggestFor(@Nonnull Item x) {
if (x instanceof WorkflowJob) {
DisableResumeJobProperty prop = ((WorkflowJob) x).getProperty(DisableResumeJobProperty.class);
return (prop != null) ? FlowDurabilityHint.PERFORMANCE_OPTIMIZED : null;
}
return null;
}
}
}

0 comments on commit 5d3b91a

Please sign in to comment.
You can’t perform that action at this time.