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:
parent
4886cc186f
commit
973721faaf
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue