diff --git a/src/main/java/org/jenkinsci/plugins/workflow/cps/CpsScmFlowDefinition.java b/src/main/java/org/jenkinsci/plugins/workflow/cps/CpsScmFlowDefinition.java index 5d63f0cc6..6ca5727fa 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/cps/CpsScmFlowDefinition.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/cps/CpsScmFlowDefinition.java @@ -27,6 +27,7 @@ import hudson.AbortException; import hudson.Extension; import hudson.FilePath; +import hudson.Functions; import hudson.model.Action; import hudson.model.Computer; import hudson.model.Item; @@ -34,12 +35,14 @@ import hudson.model.Node; import hudson.model.Queue; import hudson.model.Run; +import hudson.model.Run.RunnerAbortedException; import hudson.model.TaskListener; import hudson.model.TopLevelItem; import hudson.scm.SCM; import hudson.scm.SCMDescriptor; import hudson.slaves.WorkspaceList; import java.io.IOException; +import java.io.InterruptedIOException; import java.util.Collection; import java.util.List; import jenkins.model.Jenkins; @@ -125,7 +128,7 @@ public boolean isLightweight() { dir = new FilePath(owner.getRootDir()); } listener.getLogger().println("Checking out " + scm.getKey() + " into " + dir + " to read " + scriptPath); - String script; + String script = null; Computer computer = node.toComputer(); if (computer == null) { throw new IOException(node.getDisplayName() + " may be offline"); @@ -135,7 +138,30 @@ public boolean isLightweight() { delegate.setChangelog(true); FilePath acquiredDir; try (WorkspaceList.Lease lease = computer.getWorkspaceList().acquire(dir)) { - delegate.checkout(build, dir, listener, node.createLauncher(listener)); + for (int retryCount = Jenkins.getInstance().getScmCheckoutRetryCount(); retryCount >= 0; retryCount--) { + try { + delegate.checkout(build, dir, listener, node.createLauncher(listener)); + break; + } catch (AbortException e) { + // abort exception might have a null message. + // If so, just skip echoing it. + if (e.getMessage() != null) { + listener.error(e.getMessage()); + } + } catch (InterruptedIOException e) { + throw e; + } catch (IOException e) { + // checkout error not yet reported + listener.error("Checkout failed").println(Functions.printThrowable(e).trim()); // TODO 2.43+ use Functions.printStackTrace + } + + if (retryCount == 0) // all attempts failed + throw new AbortException("Maximum checkout retry attempts reached, aborting"); + + listener.getLogger().println("Retrying after 10 seconds"); + Thread.sleep(10000); + } + FilePath scriptFile = dir.child(scriptPath); if (!scriptFile.absolutize().getRemote().replace('\\', '/').startsWith(dir.absolutize().getRemote().replace('\\', '/') + '/')) { // TODO JENKINS-26838 throw new IOException(scriptFile + " is not inside " + dir); diff --git a/src/test/java/org/jenkinsci/plugins/workflow/cps/CpsScmFlowDefinitionTest.java b/src/test/java/org/jenkinsci/plugins/workflow/cps/CpsScmFlowDefinitionTest.java index c71559a3c..0c44e4bdb 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/cps/CpsScmFlowDefinitionTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/cps/CpsScmFlowDefinitionTest.java @@ -26,6 +26,7 @@ import hudson.model.ParametersAction; import hudson.model.ParametersDefinitionProperty; +import hudson.model.Result; import hudson.model.StringParameterDefinition; import hudson.model.StringParameterValue; import hudson.plugins.git.BranchSpec; @@ -59,6 +60,7 @@ public class CpsScmFlowDefinitionTest { @ClassRule public static BuildWatcher buildWatcher = new BuildWatcher(); @Rule public JenkinsRule r = new JenkinsRule(); @Rule public GitSampleRepoRule sampleRepo = new GitSampleRepoRule(); + @Rule public GitSampleRepoRule invalidRepo = new GitSampleRepoRule(); @Test public void configRoundtrip() throws Exception { sampleRepo.init(); @@ -82,6 +84,7 @@ public class CpsScmFlowDefinitionTest { // TODO currently the log text is in Run.log, but not on FlowStartNode/LogAction, so not visible from Workflow Steps etc. r.assertLogContains("hello from SCM", b); r.assertLogContains("Staging flow.groovy", b); + r.assertLogNotContains("Retrying after 10 seconds", b); FlowGraphWalker w = new FlowGraphWalker(b.getExecution()); int workspaces = 0; for (FlowNode n : w) { @@ -105,6 +108,7 @@ public class CpsScmFlowDefinitionTest { WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0)); r.assertLogContains("Cloning the remote Git repository", b); r.assertLogContains("version one", b); + r.assertLogNotContains("Retrying after 10 seconds", b); sampleRepo.write("flow.groovy", "echo 'version two'"); sampleRepo.git("add", "flow.groovy"); sampleRepo.git("commit", "--message=next"); @@ -113,6 +117,7 @@ public class CpsScmFlowDefinitionTest { assertEquals(2, b.number); r.assertLogContains("Fetching changes from the remote Git repository", b); r.assertLogContains("version two", b); + r.assertLogNotContains("Retrying after 10 seconds", b); List> changeSets = b.getChangeSets(); assertEquals(1, changeSets.size()); ChangeLogSet changeSet = changeSets.get(0); @@ -138,6 +143,7 @@ public class CpsScmFlowDefinitionTest { WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0)); r.assertLogContains("Cloning the remote Git repository", b); r.assertLogContains("version one", b); + r.assertLogNotContains("Retrying after 10 seconds", b); b = r.assertBuildStatusSuccess(p.scheduleBuild2(0)); assertEquals(2, b.number); List> changeSets = b.getChangeSets(); @@ -157,9 +163,24 @@ public class CpsScmFlowDefinitionTest { p.setDefinition(def); WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0)); r.assertLogNotContains("Cloning the remote Git repository", b); + r.assertLogNotContains("Retrying after 10 seconds", b); r.assertLogContains("Obtained flow.groovy from git " + sampleRepo, b); r.assertLogContains("version one", b); } + + @Issue("JENKINS-39194") + @Test public void retry() throws Exception { + // We use an un-initialized repo here to test retry + WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p"); + GitStep step = new GitStep(invalidRepo.toString()); + CpsScmFlowDefinition def = new CpsScmFlowDefinition(step.createSCM(), "flow.groovy"); + def.setLightweight(false); + p.setDefinition(def); + r.jenkins.setScmCheckoutRetryCount(1); + WorkflowRun b = r.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0)); + r.assertLogContains("Could not read from remote repository", b); + r.assertLogContains("Retrying after 10 seconds", b); + } @Issue("JENKINS-28447") @Test public void usingParameter() throws Exception {