Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[FIXED JENKINS-11594] use a RunListener to re-schedule the job
  • Loading branch information
ndeloof committed Nov 4, 2011
1 parent 1570930 commit 2cd6163
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 120 deletions.
@@ -0,0 +1,104 @@
package com.chikli.hudson.plugin.naginator;

import hudson.model.AbstractBuild;
import hudson.model.Result;
import hudson.model.TaskListener;
import hudson.model.listeners.RunListener;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* @author <a href="mailto:nicolas.deloof@cloudbees.com">Nicolas De loof</a>
*/
public class NaginatorListener extends RunListener<AbstractBuild<?,?>> {


@Override
public void onCompleted(AbstractBuild<?, ?> build, TaskListener listener) {
if (build.getResult() == Result.SUCCESS) {
return;
}

NaginatorPublisher naginator = build.getProject().getPublishersList().get(NaginatorPublisher.class);

// If we're not set to rerun if unstable, and the build's unstable, return true.
if ((!naginator.isRerunIfUnstable()) && (build.getResult() == Result.UNSTABLE)) {
return;
}

// If we're supposed to check for a regular expression in the build output before
// scheduling a new build, do so.
if (naginator.isCheckRegexp()) {
LOGGER.log(Level.FINEST, "Got checkRegexp == true");

String regexpForRerun = naginator.getRegexpForRerun();
if ((regexpForRerun !=null) && (!regexpForRerun.equals(""))) {
LOGGER.log(Level.FINEST, "regexpForRerun - " + regexpForRerun);

try {
// If parseLog returns false, we didn't find the regular expression,
// so return true.
if (!parseLog(build.getLogFile(), regexpForRerun)) {
LOGGER.log(Level.FINEST, "regexp not in logfile");
return;
}
} catch (IOException e) {
e.printStackTrace(listener
.error("error while parsing logs for naginator - forcing rebuild."));
}
}
}

// if a build fails for a reason that cannot be immediately fixed,
// immediate rescheduling may cause a very tight loop.
// combined with publishers like e-mail, IM, this could flood the users.
//
// so to avoid this problem, progressively introduce delay until the next build

// delay = the number of consective build problems * 5 mins
// back off at most 3 hours
int n=0;
for(AbstractBuild<?,?> b=build; b!=null && b.getResult()!=Result.SUCCESS && n<60; b=b.getPreviousBuild())
n+=5;

LOGGER.log(Level.FINE, "about to try to schedule a build");
scheduleBuild(build, n);
}


/**
* Wrapper method for mocking purposes.
*/
public boolean scheduleBuild(AbstractBuild<?, ?> build, int n) {
return build.getProject().scheduleBuild(n*60, new NaginatorCause());
}

private boolean parseLog(File logFile, String regexp) throws IOException {

if (regexp == null) {
return false;
}

// Assume default encoding and text files
String line;
Pattern pattern = Pattern.compile(regexp);
BufferedReader reader = new BufferedReader(new FileReader(logFile));
while ((line = reader.readLine()) != null) {
Matcher matcher = pattern.matcher(line);
if (matcher.find()) {
return true;
}
}
return false;
}

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

}
Expand Up @@ -37,8 +37,6 @@ public class NaginatorPublisher extends Notifier {
private final boolean rerunIfUnstable;
private final boolean checkRegexp;

private boolean debug = false;

@DataBoundConstructor
public NaginatorPublisher(String regexpForRerun,
boolean rerunIfUnstable,
Expand All @@ -60,110 +58,27 @@ public String getRegexpForRerun() {
return regexpForRerun;
}

public void setDebug(boolean debug) {
this.debug = debug;
}

@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
// If the build was successful, we don't need to Nag, so just return true
if (build.getResult() == Result.SUCCESS) {
return true;
}

// If we're not set to rerun if unstable, and the build's unstable, return true.
if ((!rerunIfUnstable) && (build.getResult() == Result.UNSTABLE)) {
return true;
}

// If we're supposed to check for a regular expression in the build output before
// scheduling a new build, do so.
if (checkRegexp) {
if (debug) LOGGER.log(Level.WARNING, "Got checkRegexp == true");

if ((regexpForRerun!=null) && (!regexpForRerun.equals(""))) {
if (debug) LOGGER.log(Level.WARNING, "regexpForRerun - " + regexpForRerun);

try {
// If parseLog returns false, we didn't find the regular expression,
// so return true.
if (!parseLog(build.getLogFile(),
regexpForRerun)) {
if (debug) LOGGER.log(Level.WARNING, "regexp not in logfile");
return true;
}
} catch (IOException e) {
e.printStackTrace(listener
.error("error while parsing logs for naginator - forcing rebuild."));
}
}
}

// if a build fails for a reason that cannot be immediately fixed,
// immediate rescheduling may cause a very tight loop.
// combined with publishers like e-mail, IM, this could flood the users.
//
// so to avoid this problem, progressively introduce delay until the next build

// delay = the number of consective build problems * 5 mins
// back off at most 3 hours
int n=0;
for(AbstractBuild<?,?> b=build; b!=null && b.getResult()!=Result.SUCCESS && n<60; b=b.getPreviousBuild())
n+=5;

if (debug) LOGGER.log(Level.WARNING, "about to try to schedule a build");
return scheduleBuild(build, n);
// Nothing to do during the build, see NaginatorListener
return true;
}


/**
* Wrapper method for mocking purposes.
*/
public boolean scheduleBuild(AbstractBuild<?, ?> build, int n) throws InterruptedException, IOException {
// Schedule a new build with the back off
if (debug) {
try {
build.setDescription("rebuild");
} catch (IOException e) {
LOGGER.log(Level.WARNING, "couldn't set description: " + e.getStackTrace());
}
return true;
}
else {
return build.getProject().scheduleBuild(n*60, new NaginatorCause());
}
}

public BuildStepMonitor getRequiredMonitorService() {
return BuildStepMonitor.BUILD;
}

private boolean parseLog(File logFile, String regexp) throws IOException,
InterruptedException {

if (regexp == null) {
return false;
}

// Assume default encoding and text files
String line;
Pattern pattern = Pattern.compile(regexp);
BufferedReader reader = new BufferedReader(new FileReader(logFile));
while ((line = reader.readLine()) != null) {
Matcher matcher = pattern.matcher(line);
if (matcher.find()) {
return true;
}
}
return false;
}

@Override
public DescriptorImpl getDescriptor() {
// see Descriptor javadoc for more about what a descriptor is.
return (DescriptorImpl) super.getDescriptor();
}

@Extension
public final static NaginatorListener LISTENER = new NaginatorListener();


/**
* Descriptor for {@link NaginatorPublisher}. Used as a singleton.
* The class is marked as public so that it can be accessed from views.
Expand Down
Expand Up @@ -5,17 +5,18 @@
import hudson.model.BuildListener;
import hudson.model.FreeStyleBuild;
import hudson.model.FreeStyleProject;
import hudson.model.Hudson;
import hudson.model.Queue;
import hudson.model.Queue.BuildableItem;
import hudson.model.Result;
import hudson.tasks.Builder;

import java.io.IOException;
import java.io.Serializable;
import java.util.List;

import org.jvnet.hudson.test.HudsonTestCase;

import static org.junit.Assert.*;

public class NaginatorPublisherTest extends HudsonTestCase {
public class NaginatorListenerTest extends HudsonTestCase {

private static final class MyBuilder extends Builder {
private final String text;
Expand All @@ -36,63 +37,58 @@ public boolean perform(AbstractBuild<?, ?> build, Launcher launcher,
return true;
}
}

public void testSuccessNoRebuild() throws Exception {
assertEquals(null, getDescription("build log", Result.SUCCESS,
"foo", false, false));
assertEquals(false, isScheduledForRetry("build log", Result.SUCCESS, "foo", false, false));
}

public void testUnstableNoRebuild() throws Exception {
assertEquals(null, getDescription("build log", Result.SUCCESS,
"foo", false, false));
assertEquals(false, isScheduledForRetry("build log", Result.SUCCESS, "foo", false, false));
}

public void testUnstableWithRebuild() throws Exception {
assertEquals("rebuild", getDescription("build log", Result.UNSTABLE,
"foo", true, false));
assertEquals(true, isScheduledForRetry("build log", Result.UNSTABLE, "foo", true, false));
}

public void testFailureWithRebuild() throws Exception {
assertEquals("rebuild", getDescription("build log", Result.FAILURE,
"foo", false, false));
assertEquals(true, isScheduledForRetry("build log", Result.FAILURE, "foo", false, false));
}

public void testFailureWithUnstableRebuild() throws Exception {
assertEquals("rebuild", getDescription("build log", Result.FAILURE,
"foo", true, false));
assertEquals(true, isScheduledForRetry("build log", Result.FAILURE, "foo", true, false));
}

public void testFailureWithoutRebuildRegexp() throws Exception {
assertEquals(null, getDescription("build log", Result.FAILURE,
"foo", false, true));
assertEquals(false, isScheduledForRetry("build log", Result.FAILURE, "foo", false, true));
}

public void testFailureWithRebuildRegexp() throws Exception {
assertEquals("rebuild", getDescription("build log foo", Result.FAILURE,
"foo", false, true));
assertEquals(true, isScheduledForRetry("build log foo", Result.FAILURE, "foo", false, true));
}

public void testUnstableWithoutRebuildRegexp() throws Exception {
assertEquals(null, getDescription("build log", Result.UNSTABLE,
"foo", true, true));
assertEquals(false, isScheduledForRetry("build log", Result.UNSTABLE, "foo", true, true));
}

public void testUnstableWithRebuildRegexp() throws Exception {
assertEquals("rebuild", getDescription("build log foo", Result.UNSTABLE,
"foo", true, true));
assertEquals(true, isScheduledForRetry("build log foo", Result.UNSTABLE, "foo", true, true));
}

private String getDescription(String buildLog, Result result, String regexpForRerun,
boolean rerunIfUnstable, boolean checkRegexp) throws Exception {

private boolean isScheduledForRetry(String buildLog, Result result, String regexpForRerun,
boolean rerunIfUnstable, boolean checkRegexp) throws Exception {
FreeStyleProject project = createFreeStyleProject();
project.getBuildersList().add(new MyBuilder(buildLog, result));
NaginatorPublisher nag = new NaginatorPublisher(regexpForRerun, rerunIfUnstable, checkRegexp);
nag.setDebug(true);
project.getPublishersList().add(nag);

FreeStyleBuild build = project.scheduleBuild2(0).get();
return build.getDescription();

Queue queue = Hudson.getInstance().getQueue();
Queue.Item[] tasks = queue.getItems();
boolean scheduled = tasks.length > 0;
queue.clear();
return scheduled;
}

}

0 comments on commit 2cd6163

Please sign in to comment.