From 11700c3787c136939a86da0d4a78140a4e197000 Mon Sep 17 00:00:00 2001
From: "James E. Blair"
+ Triggered by change:
+ {change.number},{change.patchset}
+ Triggered by reference:
+ {change.ref}
+ Other changes tested concurrently with this change:
+
+ Branch: {change.branch}
+ Pipeline: {change.queue_name}
+
+ Old revision: {change.oldrev}
+ New revision: {change.newrev}
+ Pipeline: {change.queue_name}
+{concurrent_changes}
+
+ All builds for this change set: +
+ Other build sets for this change: +
+ Reported result: {result} +
+""" + + ret = ret.format(**locals()) + return ret + class JobTree(object): """ A JobTree represents an instance of one Job, and holds JobTrees @@ -150,16 +249,35 @@ class Project(object): class BuildSet(object): - def __init__(self): + def __init__(self, change): + self.change = change + self.other_changes = [] self.builds = {} + self.result = None + self.next_build_set = None + self.previous_build_set = None def addBuild(self, build): self.builds[build.job.name] = build build.build_set = self + # The change isn't enqueued until after it's created + # so we don't know what the other changes ahead will be + # until jobs start. + if not self.other_changes: + next_change = self.change.change_ahead + while next_change: + self.other_changes.append(next_change) + next_change = next_change.change_ahead + def getBuild(self, job_name): return self.builds.get(job_name) + def getBuilds(self): + keys = self.builds.keys() + keys.sort() + return [self.builds.get(x) for x in keys] + class Change(object): def __init__(self, queue_name, project, event): @@ -186,10 +304,10 @@ class Change(object): self.newrev = event.newrev self.build_sets = [] - self.current_build_set = BuildSet() - self.build_sets.append(self.current_build_set) self.change_ahead = None self.change_behind = None + self.current_build_set = BuildSet(self) + self.build_sets.append(self.current_build_set) def _id(self): if self.number: @@ -248,8 +366,15 @@ class Change(object): ret += '- %s : %s\n' % (url, result) return ret + def setReportedResult(self, result): + self.current_build_set.result = result + def resetAllBuilds(self): - self.current_build_set = BuildSet() + old = self.current_build_set + self.current_build_set.result = 'CANCELED' + self.current_build_set = BuildSet(self) + old.next_build_set = self.current_build_set + self.current_build_set.previous_build_set = old self.build_sets.append(self.current_build_set) def addBuild(self, build): diff --git a/zuul/scheduler.py b/zuul/scheduler.py index 6d94f10527..740c2bf72f 100644 --- a/zuul/scheduler.py +++ b/zuul/scheduler.py @@ -149,9 +149,14 @@ class Scheduler(threading.Thread): self.trigger_event_queue.put(event) self.wake_event.set() + def onBuildStarted(self, build): + self.log.debug("Adding start event for build: %s" % build) + self.result_event_queue.put(('started', build)) + self.wake_event.set() + def onBuildCompleted(self, build): - self.log.debug("Adding result event for build: %s" % build) - self.result_event_queue.put(build) + self.log.debug("Adding complete event for build: %s" % build) + self.result_event_queue.put(('completed', build)) self.wake_event.set() def reconfigure(self, config): @@ -231,11 +236,15 @@ class Scheduler(threading.Thread): def process_result_queue(self): self.log.debug("Fetching result event") - build = self.result_event_queue.get() + event_type, build = self.result_event_queue.get() self.log.debug("Processing result event %s" % build) for manager in self.queue_managers.values(): - if manager.onBuildCompleted(build): - return + if event_type == 'started': + if manager.onBuildStarted(build): + return + elif event_type == 'completed': + if manager.onBuildCompleted(build): + return self.log.warning("Build %s not found by any queue manager" % (build)) def formatStatusHTML(self): @@ -336,6 +345,27 @@ class BaseQueueManager(object): self.log.exception("Exception while launching job %s \ for change %s:" % (job, change)) + def updateBuildDescriptions(self, build_set): + for build in build_set.getBuilds(): + desc = build.formatDescription() + self.sched.launcher.setBuildDescription(build, desc) + + if build_set.previous_build_set: + for build in build_set.previous_build_set.getBuilds(): + desc = build.formatDescription() + self.sched.launcher.setBuildDescription(build, desc) + + def onBuildStarted(self, build): + self.log.debug("Build %s started" % build) + if build not in self.building_jobs: + self.log.debug("Build %s not found" % (build)) + # Or triggered externally, or triggered before zuul started, + # or restarted + return False + + self.updateBuildDescriptions(build.build_set) + return True + def onBuildCompleted(self, build): self.log.debug("Build %s completed" % build) if build not in self.building_jobs: @@ -361,6 +391,8 @@ for change %s:" % (job, change)) self.log.debug("All jobs for change %s are not yet complete" % ( change)) self.launchJobs(change) + + self.updateBuildDescriptions(build.build_set) return True def possiblyReportChange(self, change): @@ -372,8 +404,10 @@ for change %s:" % (job, change)) ret = None if change.didAllJobsSucceed(): action = self.success_action + change.setReportedResult('SUCCESS') else: action = self.failure_action + change.setReportedResult('FAILURE') try: self.log.info("Reporting change %s, action: %s" % ( change, action)) @@ -384,6 +418,8 @@ for change %s:" % (job, change)) change, ret)) except: self.log.exception("Exception while reporting:") + change.setReportedResult('ERROR') + self.updateBuildDescriptions(change.current_build_set) return ret def formatStatusHTML(self): @@ -504,6 +540,7 @@ for change %s" % (build, change)) to_remove.append(build) for build in to_remove: self.log.debug("Removing build %s from running builds" % build) + build.result = 'CANCELED' del self.building_jobs[build] if change.change_behind: self.log.debug("Canceling jobs for change %s, \