Keep build information around longer.

Preparing for leaving informative messages on build descriptions.

I'd like to be able to leave descriptions that include all the
builds that happened in conjunction with any given build.  But
Zuul forgets about completed builds very quickly, so we may not
have all the information when needed.  This adds build sets,
which keep track of all the builds that happened at once.

This makes some of our recent status output changes a little
easier/cleaner as well.

This also adds the jenkins method to set a build description,
which is unused in this change, but will be in a follow-on.

Change-Id: I78936dc9284ef0a88272ffdef962718b20485ec7
This commit is contained in:
James E. Blair 2012-07-03 16:12:28 -07:00
parent 5ffe811ef1
commit 7e530ad37b
2 changed files with 69 additions and 36 deletions

View File

@ -22,6 +22,7 @@ from paste import httpserver
from uuid import uuid1
import jenkins
import json
import urllib # for extending jenkins lib
import urllib2 # for extending jenkins lib
import urlparse
import logging
@ -100,6 +101,7 @@ class JenkinsCleanup(threading.Thread):
STOP_BUILD = 'job/%(name)s/%(number)s/stop'
CANCEL_QUEUE = 'queue/item/%(number)s/cancelQueue'
BUILD_INFO = 'job/%(name)s/%(number)s/api/json?depth=0'
BUILD_DESCRIPTION = 'job/%(name)s/%(number)s/submitDescription'
class ExtendedJenkins(jenkins.Jenkins):
@ -153,6 +155,22 @@ class ExtendedJenkins(jenkins.Jenkins):
return json.loads(self.jenkins_open(urllib2.Request(
self.server + BUILD_INFO % locals())))
def set_build_description(self, name, number, description):
'''
Get information for a build.
@param name: Name of Jenkins job
@type name: str
@param number: Jenkins build number for the job
@type number: int
@param description: Bulid description to set
@type description: str
'''
params = urllib.urlencode({'description': description})
self.jenkins_open(urllib2.Request(self.server +
BUILD_DESCRIPTION % locals(),
params))
class Jenkins(object):
log = logging.getLogger("zuul.Jenkins")

View File

@ -77,10 +77,10 @@ class Build(object):
def __init__(self, job, uuid):
self.job = job
self.uuid = uuid
self.status = None
self.url = None
self.number = None
self.result = None
self.build_set = None
self.launch_time = time.time()
def __repr__(self):
@ -149,6 +149,18 @@ class Project(object):
return tree.getJobs()
class BuildSet(object):
def __init__(self):
self.builds = {}
def addBuild(self, build):
self.builds[build.job.name] = build
build.build_set = self
def getBuild(self, job_name):
return self.builds.get(job_name)
class Change(object):
def __init__(self, queue_name, project, event):
self.queue_name = queue_name
@ -173,11 +185,11 @@ class Change(object):
self.oldrev = event.oldrev
self.newrev = event.newrev
self.jobs = {}
self.job_urls = {}
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.running_builds = []
def _id(self):
if self.number:
@ -187,14 +199,6 @@ class Change(object):
def __repr__(self):
return '<Change 0x%x %s>' % (id(self), self._id())
def _getBuild(self, job):
# We don't gurantee that we'll keep track of builds, but
# we might happen to have one running; useful for status.
for build in self.running_builds:
if build.job == job:
return build
return None
def formatStatus(self, indent=0, html=False):
indent_str = ' ' * indent
ret = ''
@ -208,14 +212,17 @@ class Change(object):
self.project.name,
self._id())
for job in self.project.getJobs(self.queue_name):
result = self.jobs.get(job.name)
build = self.current_build_set.getBuild(job.name)
if build:
result = build.result
else:
result = None
job_name = job.name
if html:
build = self._getBuild(job)
if build:
url = build.url
else:
url = self.job_urls.get(job.name, None)
url = None
if url is not None:
job_name = '<a href="%s">%s</a>' % (url, job_name)
ret += '%s %s: %s' % (indent_str, job_name, result)
@ -233,44 +240,47 @@ class Change(object):
ret += 'Build failed\n\n'
for job in self.project.getJobs(self.queue_name):
result = self.jobs.get(job.name)
url = self.job_urls.get(job.name, job.name)
build = self.current_build_set.getBuild(job.name)
result = build.result
url = build.url
if not url:
url = job.name
ret += '- %s : %s\n' % (url, result)
return ret
def resetAllBuilds(self):
self.jobs = {}
self.job_urls = {}
self.running_builds = []
self.current_build_set = BuildSet()
self.build_sets.append(self.current_build_set)
def addBuild(self, build):
self.running_builds.append(build)
self.current_build_set.addBuild(build)
def setResult(self, build):
self.running_builds.remove(build)
self.jobs[build.job.name] = build.result
if build.url:
self.job_urls[build.job.name] = build.url
if build.result != 'SUCCESS':
# Get a JobTree from a Job so we can find only its dependent jobs
root = self.project.getJobTreeForQueue(self.queue_name)
tree = root.getJobTreeForJob(build.job)
for job in tree.getJobs():
self.jobs[job.name] = 'SKIPPED'
fakebuild = Build(job, None)
fakebuild.result = 'SKIPPED'
self.addBuild(fakebuild)
def _findJobsToRun(self, job_trees):
torun = []
for tree in job_trees:
job = tree.job
result = None
if job:
result = self.jobs.get(job.name, None)
else:
# This is a null job tree, run all of its jobs
result = 'SUCCESS'
if not result:
if job not in [b.job for b in self.running_builds]:
build = self.current_build_set.getBuild(job.name)
if build:
result = build.result
else:
# There is no build for the root of this job tree,
# so we should run it.
torun.append(job)
elif result == 'SUCCESS':
# If there is no job, this is a null job tree, and we should
# run all of its jobs.
if result == 'SUCCESS' or not job:
torun.extend(self._findJobsToRun(tree.job_trees))
return torun
@ -283,13 +293,18 @@ class Change(object):
def areAllJobsComplete(self):
tree = self.project.getJobTreeForQueue(self.queue_name)
for job in tree.getJobs():
if not job.name in self.jobs:
build = self.current_build_set.getBuild(job.name)
if not build or not build.result:
return False
return True
def didAllJobsSucceed(self):
for result in self.jobs.values():
if result != 'SUCCESS':
tree = self.project.getJobTreeForQueue(self.queue_name)
for job in tree.getJobs():
build = self.current_build_set.getBuild(job.name)
if not build:
return False
if build.result != 'SUCCESS':
return False
return True