Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #16 from ikedam/feature/JENKINS-29715_ControlWithA…
…ction

[JENKINS-29715] Control rescheduling with action
  • Loading branch information
KostyaSha committed Aug 30, 2015
2 parents b99a2b5 + 9621e5d commit b928d55
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 b928d55

Please sign in to comment.