Cancel jobs behind a failed change.
Jobs behind a failed change will need to be rerun so cancel them
to free up resources at the front of the change queue. When a change
at the front of the queue fails it is dequeued and all the jobs behind
it are rerun. When a change in the middle of the queue fails all the
jobs behind it are cancelled. Then the jobs will be rerun when a
change at the front of the queue is dequeued for failure (which may
end up being the change that caused the initial cancellation, or it
may be a change further up in the queue).
Change-Id: Ic0ebe15ec1a8d3a21ff04a6769243729683807ed
diff --git a/zuul/model.py b/zuul/model.py
index c4efa39..b292b06 100644
--- a/zuul/model.py
+++ b/zuul/model.py
@@ -449,6 +449,14 @@
return False
return True
+ def didAnyJobFail(self):
+ tree = self.project.getJobTreeForQueue(self.queue_name)
+ for job in self._filterJobs(tree.getJobs()):
+ build = self.current_build_set.getBuild(job.name)
+ if build and build.result == 'FAILURE':
+ return True
+ return False
+
def delete(self):
if self.change_behind:
self.change_behind.change_ahead = None
diff --git a/zuul/scheduler.py b/zuul/scheduler.py
index f0262c8..40a21c4 100644
--- a/zuul/scheduler.py
+++ b/zuul/scheduler.py
@@ -609,10 +609,11 @@
change.change_behind, change))
self.launchJobs(change.change_behind)
- def cancelJobs(self, change):
+ def cancelJobs(self, change, prime=True):
self.log.debug("Cancel jobs for change %s" % change)
to_remove = []
- change.resetAllBuilds()
+ if prime:
+ change.resetAllBuilds()
for build, build_change in self.building_jobs.items():
if build_change == change:
self.log.debug("Found build %s for change %s to cancel" % (
@@ -630,7 +631,20 @@
if change.change_behind:
self.log.debug("Canceling jobs for change %s, \
behind change %s" % (change.change_behind, change))
- self.cancelJobs(change.change_behind)
+ self.cancelJobs(change.change_behind, prime=prime)
+
+ def onBuildCompleted(self, build):
+ change = self.building_jobs.get(build)
+ if not super(DependentQueueManager, self).onBuildCompleted(build):
+ return False
+ if change and change.didAnyJobFail():
+ # This or some other build failed. All changes behind this change
+ # will need to be retested. To free up resources cancel the builds
+ # behind this one as they will be rerun anyways.
+ self.cancelJobs(change.change_behind, prime=False)
+ self.log.debug("Canceling builds behind change: %s due to"
+ " failure." % change)
+ return True
def possiblyReportChange(self, change):
self.log.debug("Possibly reporting change %s" % change)