Handle existing broken config in job updates

If Zuul was unable to freeze the job graph of the previous layout,
then it would throw an exception when calculating whether jobs have
changed.  This is a problem if it does so on a change which fixes
an error in the current tenant config, as it means the fix can not
merge.

To handle this case, we need to catch that exception, and then provide
a default value indicating whether the job config has been updated
or not.  Because returning True could cause every job with a file
matcher to run unecessarily, the safer default is False (so the job
relies on the file matcher alone).

Change-Id: I8a72e57e068b073e37274cdabeacffc9daee2e94
This commit is contained in:
James E. Blair
2019-07-12 14:13:51 -07:00
parent de1a8372a8
commit 0fc385ebed
8 changed files with 117 additions and 6 deletions

View File

@@ -0,0 +1 @@
---

View File

@@ -0,0 +1,17 @@
- pipeline:
name: check
manager: independent
trigger:
gerrit:
- event: patchset-created
success:
gerrit:
Verified: 1
failure:
gerrit:
Verified: -1
- job:
name: base
parent: null
run: playbooks/run.yaml

View File

@@ -0,0 +1 @@
test

View File

@@ -0,0 +1,7 @@
# Every fake change in the unit tests modifies "README"
- job:
name: existing-files
foo: broken
files:
- README.txt

View File

@@ -0,0 +1,6 @@
- project:
name: org/project
templates:
- files-template
check:
jobs: []

View File

@@ -0,0 +1,8 @@
- tenant:
name: tenant-one
source:
gerrit:
config-projects:
- common-config
untrusted-projects:
- org/project

View File

@@ -5712,6 +5712,68 @@ For CI problems and help debugging, contact ci@example.org"""
], ordered=False)
class TestJobUpdateBrokenConfig(ZuulTestCase):
tenant_config_file = 'config/job-update-broken/main.yaml'
def test_fix_check_without_running(self):
"Test that we can fix a broken check pipeline (don't run the job)"
in_repo_conf = textwrap.dedent(
"""
- job:
name: existing-files
files:
- README.txt
- project-template:
name: files-template
check:
jobs:
- existing-files
- noop
""")
# When the config is broken, we don't override any files
# matchers since we don't have a valid basis. Since this
# doesn't update README.txt, nothing should run.
file_dict = {'zuul.d/existing.yaml': in_repo_conf}
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A',
files=file_dict)
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
self.assertHistory([])
self.assertEqual(A.reported, 1)
def test_fix_check_with_running(self):
"Test that we can fix a broken check pipeline (do run the job)"
in_repo_conf = textwrap.dedent(
"""
- job:
name: existing-files
files:
- README.txt
- project-template:
name: files-template
check:
jobs:
- existing-files
""")
# When the config is broken, we don't override any files
# matchers since we don't have a valid basis. Since this
# does update README.txt, the job should run.
file_dict = {'zuul.d/template.yaml': in_repo_conf,
'README.txt': ''}
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A',
files=file_dict)
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
self.assertHistory([
dict(name='existing-files', result='SUCCESS', changes='1,1'),
])
self.assertEqual(A.reported, 1)
class TestJobUpdateFileMatcher(ZuulTestCase):
tenant_config_file = 'config/job-update/main.yaml'

View File

@@ -2878,12 +2878,21 @@ class QueueItem(object):
# This change updates the layout. Calculate the job as it
# would be if the layout had not changed.
if self._old_job_graph is None:
ppc = layout_ahead.getProjectPipelineConfig(self)
log.debug("Creating job graph for config change detection")
self._old_job_graph = layout_ahead.createJobGraph(
self, ppc, skip_file_matcher=True)
log.debug("Done creating job graph for "
"config change detection")
try:
ppc = layout_ahead.getProjectPipelineConfig(self)
log.debug("Creating job graph for config change detection")
self._old_job_graph = layout_ahead.createJobGraph(
self, ppc, skip_file_matcher=True)
log.debug("Done creating job graph for "
"config change detection")
except Exception:
self.log.debug(
"Error freezing job graph in job update check:",
exc_info=True)
# The config was broken before, we have no idea
# which jobs have changed, so rather than run them
# all, just rely on the file matchers as-is.
return False
old_job = self._old_job_graph.jobs.get(job.name)
if old_job is None:
log.debug("Found a newly created job")