Have zuul handle merge failures.

If Zuul is unable to merge a change, don't run any jobs, and report
the merge failure to gerrit directly (but still observing the
dependent change queue, in case a change ahead caused the merge
failure).

Adds a test for this situation.

Change-Id: I1ee2a8846b159db385019352cc04af2140db81af
Reviewed-on: https://review.openstack.org/11421
Reviewed-by: Clark Boylan <clark.boylan@gmail.com>
Approved: James E. Blair <corvus@inaugust.com>
Tested-by: Jenkins
This commit is contained in:
James E. Blair 2012-08-15 10:19:43 -07:00 committed by Jenkins
parent 4886cc186f
commit 973721faaf
4 changed files with 86 additions and 19 deletions

View File

@ -74,7 +74,7 @@ def init_repo(project):
repo.create_tag('init')
def add_fake_change_to_repo(project, branch, change_num, patchset, msg):
def add_fake_change_to_repo(project, branch, change_num, patchset, msg, fn):
path = os.path.join("/tmp/zuul-test/upstream", project)
repo = git.Repo(path)
ref = ChangeReference.create(repo, '1/%s/%s' % (change_num,
@ -85,9 +85,9 @@ def add_fake_change_to_repo(project, branch, change_num, patchset, msg):
repo.git.clean('-x', '-f', '-d')
path = os.path.join("/tmp/zuul-test/upstream", project)
fn = os.path.join(path, '%s-%s' % (branch, change_num))
fn = os.path.join(path, fn)
f = open(fn, 'w')
f.write("test\n")
f.write("test %s %s %s\n" % (branch, change_num, patchset))
f.close()
repo.index.add([fn])
repo.index.commit(msg)
@ -177,9 +177,14 @@ class FakeChange(object):
self.data['currentPatchSet'] = d
self.patchsets.append(d)
self.data['submitRecords'] = self.getSubmitRecords()
if files:
fn = files[0]
else:
fn = '%s-%s' % (self.branch, self.number)
add_fake_change_to_repo(self.project, self.branch,
self.number, self.latest_patchset,
self.subject + '-' + str(self.latest_patchset))
self.subject + '-' + str(self.latest_patchset),
fn)
def addApproval(self, category, value):
approval = {'description': self.categories[category][0],
@ -1009,6 +1014,7 @@ class testScheduler(unittest.TestCase):
ref = jobs[-1].parameters['ZUUL_REF']
self.fake_jenkins.hold_jobs_in_queue = False
self.fake_jenkins.fakeRelease()
self.waitUntilSettled()
path = os.path.join("/tmp/zuul-test/git/org/project")
repo = git.Repo(path)
@ -1017,3 +1023,39 @@ class testScheduler(unittest.TestCase):
print ' repo messages :', repo_messages
correct_messages = ['initial commit', 'A-1', 'B-1', 'C-1']
assert repo_messages == correct_messages
def test_build_configuration_conflict(self):
"Test that merge conflicts are handled"
self.fake_jenkins.hold_jobs_in_queue = True
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
A.addPatchset(['conflict'])
B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
B.addPatchset(['conflict'])
C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
A.addApproval('CRVW', 2)
B.addApproval('CRVW', 2)
C.addApproval('CRVW', 2)
self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
self.waitUntilSettled()
jobs = self.fake_jenkins.all_jobs
self.fake_jenkins.fakeRelease('.*-merge')
self.waitUntilSettled()
self.fake_jenkins.fakeRelease('.*-merge')
self.waitUntilSettled()
self.fake_jenkins.fakeRelease('.*-merge')
self.waitUntilSettled()
ref = jobs[-1].parameters['ZUUL_REF']
self.fake_jenkins.hold_jobs_in_queue = False
self.fake_jenkins.fakeRelease()
self.waitUntilSettled()
assert A.data['status'] == 'MERGED'
assert B.data['status'] == 'NEW'
assert C.data['status'] == 'MERGED'
assert A.reported == 2
assert B.reported == 2
assert C.reported == 2

View File

@ -120,7 +120,7 @@ class Merger(object):
repo.cherryPick(change.refspec)
repo.setZuulRef(change.branch + '/' + target_ref, 'HEAD')
except:
self.log.exception("Unable to merge %s" % change)
self.log.info("Unable to merge %s" % change)
return False
return True

View File

@ -164,17 +164,22 @@ class Pipeline(object):
else:
ret += 'Build failed\n\n'
for job in self.getJobs(changeish):
build = changeish.current_build_set.getBuild(job.name)
result = build.result
if result == 'SUCCESS' and job.success_message:
result = job.success_message
elif result == 'FAILURE' and job.failure_message:
result = job.failure_message
url = build.url
if not url:
url = job.name
ret += '- %s : %s\n' % (url, result)
if changeish.current_build_set.unable_to_merge:
ret += "This change was unable to be automatically merged "\
"with the current state of the repository. Please "\
"rebase your change and upload a new patchset."
else:
for job in self.getJobs(changeish):
build = changeish.current_build_set.getBuild(job.name)
result = build.result
if result == 'SUCCESS' and job.success_message:
result = job.success_message
elif result == 'FAILURE' and job.failure_message:
result = job.failure_message
url = build.url
if not url:
url = job.name
ret += '- %s : %s\n' % (url, result)
return ret
def formatDescription(self, build):
@ -287,6 +292,14 @@ class Pipeline(object):
fakebuild.result = 'SKIPPED'
changeish.addBuild(fakebuild)
def setUnableToMerge(self, changeish):
changeish.current_build_set.unable_to_merge = True
root = self.getJobTree(changeish.project)
for job in root.getJobs():
fakebuild = Build(job, None)
fakebuild.result = 'SKIPPED'
changeish.addBuild(fakebuild)
class ChangeQueue(object):
"""DependentPipelines have multiple parallel queues shared by
@ -433,6 +446,7 @@ class BuildSet(object):
self.next_build_set = None
self.previous_build_set = None
self.ref = None
self.unable_to_merge = False
def setConfiguration(self):
# The change isn't enqueued until after it's created

View File

@ -482,8 +482,13 @@ class BasePipelineManager(object):
if not ref:
change.current_build_set.setConfiguration()
ref = change.current_build_set.getRef()
self.sched.merger.mergeChanges([change], ref,
mode=model.MERGE_IF_NECESSARY)
merged = self.sched.merger.mergeChanges([change], ref,
mode=model.MERGE_IF_NECESSARY)
if not merged:
self.log.info("Unable to merge change %s" % change)
self.pipeline.setUnableToMerge(change)
self.possiblyReportChange(change)
return
for job in self.pipeline.findJobsToRun(change):
self.log.debug("Found job %s for change %s" % (job, change))
@ -747,7 +752,13 @@ class DependentPipelineManager(BasePipelineManager):
ref = change.current_build_set.getRef()
dependent_changes = self._getDependentChanges(change)
dependent_changes.reverse()
self.sched.merger.mergeChanges(dependent_changes + [change], ref)
all_changes = dependent_changes + [change]
merged = self.sched.merger.mergeChanges(all_changes, ref)
if not merged:
self.log.info("Unable to merge changes %s" % all_changes)
self.pipeline.setUnableToMerge(change)
self.possiblyReportChange(change)
return
#TODO: remove this line after GERRIT_CHANGES is gone
dependent_changes = self._getDependentChanges(change)