Skip to content

Commit

Permalink
[FIXED JENKINS-29715] [FIXED JENKINS-23984] Introduced NaginatorSched…
Browse files Browse the repository at this point in the history
…uleAction that makes Naginator to reschedule the build. This allows other plugins to trigger naginator and also allows naginator-plugin works even wrapped by other plugins.
  • Loading branch information
ikedam committed Aug 30, 2015
1 parent b99a2b5 commit 9621e5d
Show file tree
Hide file tree
Showing 7 changed files with 888 additions and 110 deletions.
161 changes: 52 additions & 109 deletions src/main/java/com/chikli/hudson/plugin/naginator/NaginatorListener.java
Expand Up @@ -6,113 +6,72 @@
import hudson.model.*;
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;

import static hudson.model.Result.SUCCESS;
import static hudson.model.Result.ABORTED;
import java.util.ArrayList;
import java.util.List;

import javax.annotation.Nonnull;

/**
* @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() == SUCCESS) || (build.getResult() == ABORTED)) {
return;
}

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

// JENKINS-13791
if (naginator == null) {
public void onCompleted(AbstractBuild<?, ?> build, @Nonnull TaskListener listener) {
// Do nothing for null or a single Matrix run. (Run only when all Matrix finishes)
if (build == null || build instanceof MatrixRun) {
return;
}

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

// Do nothing for a single Matrix run. (Run only when all Matrix finishes)
if (build instanceof MatrixRun) {
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 - {0}", 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 (canSchedule(build, naginator)) {
int n = naginator.getDelay().computeScheduleDelay(build);
LOGGER.log(Level.FINE, "about to try to schedule a build #{0} in {1} seconds for {2}",
new Object[]{build.getNumber(), n, build.getProject().getName()} );

List<Combination> combsToRerun = new ArrayList<Combination>();

if (naginator.isRerunMatrixPart()) {
if (build instanceof MatrixBuild) {
MatrixBuild mb = (MatrixBuild) build;
List<MatrixRun> matrixRuns = mb.getRuns();

for(MatrixRun r : matrixRuns) {
if (r.getNumber() == build.getNumber()) {
if ((r.getResult() == SUCCESS) || (r.getResult() == ABORTED)) {
continue;

int retryCount = calculateRetryCount(build);

List<NaginatorScheduleAction> actions = build.getActions(NaginatorScheduleAction.class);
for (NaginatorScheduleAction action : actions) {
if (action.shouldSchedule(build, listener, retryCount)) {
int n = action.getDelay().computeScheduleDelay(build);
LOGGER.log(Level.FINE, "about to try to schedule a build #{0} in {1} seconds for {2}",
new Object[]{build.getNumber(), n, build.getProject().getName()} );

List<Combination> combsToRerun = new ArrayList<Combination>();

if (action.isRerunMatrixPart()) {
if (build instanceof MatrixBuild) {
MatrixBuild mb = (MatrixBuild) build;
List<MatrixRun> matrixRuns = mb.getRuns();

for (MatrixRun r : matrixRuns) {
if (r.getNumber() == build.getNumber()) {
if (!action.shouldScheduleForMatrixRun(r, listener)) {
continue;
}

LOGGER.log(Level.FINE, "add combination to matrix rerun ({0})", r.getParent().getCombination().toString());
combsToRerun.add(r.getParent().getCombination());
}
if ((!naginator.isRerunIfUnstable()) && (r.getResult() == Result.UNSTABLE)) {
continue;
}

LOGGER.log(Level.FINE, "add combination to matrix rerun ({0})", r.getParent().getCombination().toString());
combsToRerun.add(r.getParent().getCombination());
}
}

}
}
}

if (!combsToRerun.isEmpty()) {
LOGGER.log(Level.FINE, "schedule matrix rebuild");
scheduleMatrixBuild(build, combsToRerun, n);
} else {
scheduleBuild(build, n);
if (!combsToRerun.isEmpty()) {
LOGGER.log(Level.FINE, "schedule matrix rebuild");
scheduleMatrixBuild(build, combsToRerun, n);
} else {
scheduleBuild(build, n);
}
}
} else {
LOGGER.log(Level.FINE, "max number of schedules for #{0} build, project {1}",
new Object[]{build.getNumber(), build.getProject().getName()} );
}
}

/**
* @deprecated use {@link NaginatorScheduleAction#shouldSchedule(Run, TaskListener, int)}
* to control scheduling.
*/
@Deprecated
public boolean canSchedule(Run build, NaginatorPublisher naginator) {
Run r = build;
int max = naginator.getMaxSchedule();
Expand All @@ -128,6 +87,16 @@ public boolean canSchedule(Run build, NaginatorPublisher naginator) {
return n < max;
}

private int calculateRetryCount(@Nonnull Run<?, ?> r) {
int n = 0;

while (r != null && r.getAction(NaginatorAction.class) != null) {
r = r.getPreviousBuild();
n++;
}
return n;
}

public boolean scheduleMatrixBuild(AbstractBuild<?, ?> build, List<Combination> combinations, int n) {
NaginatorMatrixAction nma = new NaginatorMatrixAction();
for (Combination c : combinations) {
Expand All @@ -143,32 +112,6 @@ public boolean scheduleBuild(AbstractBuild<?, ?> build, int n) {
return NaginatorRetryAction.scheduleBuild(build, n);
}

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 = null;
try {
reader = new BufferedReader(new FileReader(logFile));
while ((line = reader.readLine()) != null) {
Matcher matcher = pattern.matcher(line);
if (matcher.find()) {
return true;
}
}
return false;
}
finally {
if(reader != null)
reader.close();
}
}

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

}
Expand Up @@ -2,6 +2,8 @@

import hudson.Extension;
import hudson.Launcher;
import hudson.matrix.MatrixRun;
import hudson.matrix.MatrixBuild;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
Expand Down Expand Up @@ -87,7 +89,16 @@ public int getMaxSchedule() {

@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
// Nothing to do during the build, see NaginatorListener
if (build instanceof MatrixRun) {
MatrixBuild parent = ((MatrixRun)build).getParentBuild();
if (parent.getAction(NaginatorPublisherScheduleAction.class) == null) {
// No strict exclusion is required
// as it doesn't matter if the action gets duplicated.
parent.addAction(new NaginatorPublisherScheduleAction(this));
}
} else {
build.addAction(new NaginatorPublisherScheduleAction(this));
}
return true;
}

Expand Down
@@ -0,0 +1,121 @@
package com.chikli.hudson.plugin.naginator;

import hudson.matrix.MatrixRun;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.TaskListener;

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;

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

/**
* Used from {@link NaginatorPublisher} to mark a build to be reshceduled.
*/
public class NaginatorPublisherScheduleAction extends NaginatorScheduleAction {
private static final Logger LOGGER = Logger.getLogger(NaginatorPublisherScheduleAction.class.getName());

private final String regexpForRerun;
private final boolean rerunIfUnstable;
private final boolean checkRegexp;

public NaginatorPublisherScheduleAction(NaginatorPublisher publisher) {
super(publisher.getMaxSchedule(), publisher.getDelay(), publisher.isRerunMatrixPart());
this.regexpForRerun = publisher.getRegexpForRerun();
this.rerunIfUnstable = publisher.isRerunIfUnstable();
this.checkRegexp = publisher.isCheckRegexp();
}

@CheckForNull
public String getRegexpForRerun() {
return regexpForRerun;
}

public boolean isRerunIfUnstable() {
return rerunIfUnstable;
}

public boolean isCheckRegexp() {
return checkRegexp;
}

@Override
public boolean shouldSchedule(@Nonnull Run<?, ?> run, @Nonnull TaskListener listener, int retryCount) {
if ((run.getResult() == Result.SUCCESS) || (run.getResult() == Result.ABORTED)) {
return false;
}

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

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

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

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

return super.shouldSchedule(run, listener, retryCount);
}

@Override
public boolean shouldScheduleForMatrixRun(@Nonnull MatrixRun run, @Nonnull TaskListener listener) {
if ((run.getResult() == Result.SUCCESS) || (run.getResult() == Result.ABORTED)) {
return false;
}
if ((!isRerunIfUnstable()) && (run.getResult() == Result.UNSTABLE)) {
return false;
}
return true;
}

private boolean parseLog(File logFile, @Nonnull String regexp) throws IOException {
// TODO annotate `logFile` with `@Nonnull`
// after upgrading the target Jenkins to 1.568 or later.

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

0 comments on commit 9621e5d

Please sign in to comment.