Add build descriptions to Jenkins.

Zuul will continue to update descriptions as it receives more
information.

Change-Id: I29c79068ff6c4cb1de40b98d2a9e8a0d3d52b635
This commit is contained in:
James E. Blair 2012-07-05 17:50:05 -07:00
parent 7e530ad37b
commit 11700c3787
3 changed files with 184 additions and 10 deletions

View File

@ -300,6 +300,16 @@ for build %s" % (item['id'], build))
pass
return url
def setBuildDescription(self, build, description):
if not build.number:
return
try:
self.jenkins.set_build_description(build.job.name, build.number,
description)
except:
self.log.exception("Exception setting build description for %s" %
build)
def onBuildCompleted(self, uuid, status, url, number):
self.log.info("Build %s #%s complete, status %s" % (
uuid, number, status))
@ -308,9 +318,10 @@ for build %s" % (item['id'], build))
self.log.debug("Found build %s" % build)
del self.builds[uuid]
if url:
build.base_url = url
url = self.getBestBuildURL(url)
build.url = url
build.result = status
build.url = url
build.number = number
self.sched.onBuildCompleted(build)
else:
@ -323,6 +334,7 @@ for build %s" % (item['id'], build))
self.log.debug("Found build %s" % build)
build.url = url
build.number = number
self.sched.onBuildStarted(build)
else:
self.log.error("Unable to find build %s" % uuid)

View File

@ -77,6 +77,7 @@ class Build(object):
def __init__(self, job, uuid):
self.job = job
self.uuid = uuid
self.base_url = None
self.url = None
self.number = None
self.result = None
@ -86,6 +87,104 @@ class Build(object):
def __repr__(self):
return '<Build %s of %s>' % (self.uuid, self.job.name)
def formatDescription(self):
concurrent_changes = ''
concurrent_builds = ''
other_builds = ''
for change in self.build_set.other_changes:
concurrent_changes += '<li><a href="{change.url}">\
{change.number},{change.patchset}</a></li>'.format(
change=change)
change = self.build_set.change
for build in self.build_set.getBuilds():
if build.base_url:
concurrent_builds += """\
<li>
<a href="{build.base_url}">
{build.job.name} #{build.number}</a>: {build.result}
</li>
""".format(build=build)
else:
concurrent_builds += """\
<li>
{build.job.name}: {build.result}
</li>""".format(build=build)
if self.build_set.previous_build_set:
build = self.build_set.previous_build_set.getBuild(self.job.name)
if build:
other_builds += """\
<li>
Preceded by: <a href="{build.base_url}">
{build.job.name} #{build.number}</a>
</li>
""".format(build=build)
if self.build_set.next_build_set:
build = self.build_set.next_build_set.getBuild(self.job.name)
if build:
other_builds += """\
<li>
Succeeded by: <a href="{build.base_url}">
{build.job.name} #{build.number}</a>
</li>
""".format(build=build)
result = self.build_set.result
if change.number:
ret = """\
<p>
Triggered by change:
<a href="{change.url}">{change.number},{change.patchset}</a><br/>
Branch: <b>{change.branch}</b><br/>
Pipeline: <b>{change.queue_name}</b>
</p>"""
else:
ret = """\
<p>
Triggered by reference:
{change.ref}</a><br/>
Old revision: <b>{change.oldrev}</b><br/>
New revision: <b>{change.newrev}</b><br/>
Pipeline: <b>{change.queue_name}</b>
</p>"""
if concurrent_changes:
ret += """\
<p>
Other changes tested concurrently with this change:
<ul>{concurrent_changes}</ul>
</p>
"""
if concurrent_builds:
ret += """\
<p>
All builds for this change set:
<ul>{concurrent_builds}</ul>
</p>
"""
if other_builds:
ret += """\
<p>
Other build sets for this change:
<ul>{other_builds}</ul>
</p>
"""
if result:
ret += """\
<p>
Reported result: <b>{result}</b>
</p>
"""
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):

View File

@ -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, \