Files
zuul/zuul/driver/sql/sqlreporter.py
Tobias Henkel 3b0c37ba66 Fix job contamination by unmerged change
We had cases where zuul used unmerged job descriptions to a trusted
parent job (change A) in non related downstream jobs (change B) not
having zuul.yaml changes. This happened if the trusted parent job is
not defined in the same config repo as the pipeline. E.g. if change A
adds a new post playbook an unrelated change B fails with 'post
playbook not found'. This is caused by the scheduler using the wrong
unmerged job definition of change A but the final workspace contains
the correct state without change A.

In case of change B there is no dynamic layout and the current active
layout should be taken. However it is taken directly from the pipeline
object in getLayout (item.queue.pipeline.layout) which doesn't have
the correct layout referenced at any time while the layout referenced
by the tenant object is correct.

Because the pipeline definition is in a different repository than the
proposed config repo change, when the dynamic layout is created for
the config repo change, the previously cached Pipeline objects are used
to build the layout.  These objects are the actual live pipelines, and
when they are added to the layout, they have their Pipeline.layout
attributes set to the dynamic layout.  This dynamic layout is then not
used further (it is only created for syntax validation), but the pipelines
remain altered.

We could go ahead and just change that to
item.queue.pipeline.layout.tenant.layout but this feels awkward and
would leave the possibility of similar bugs that are hard to find and
debug. Further pipeline.layout is almost everywhere just used to get
the tenant and not the layout. So this attempt to fix this bug goes
further and completely rips out the layout from the Pipeline object
and replaces it by the tenant. Because the tenant object is never
expected to change during the lifetime of the pipeline object, holding
the reference to the tenant, rather than the layout, is safe.

Change-Id: I1e663f624db5e30a8f51b56134c37cc6e8217029
2018-07-09 22:37:19 +02:00

99 lines
3.6 KiB
Python

# Copyright 2015 Rackspace Australia
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import logging
import voluptuous as v
from zuul.reporter import BaseReporter
class SQLReporter(BaseReporter):
"""Sends off reports to a database."""
name = 'sql'
log = logging.getLogger("zuul.SQLReporter")
def report(self, item):
"""Create an entry into a database."""
if not self.connection.tables_established:
self.log.warn("SQL reporter (%s) is disabled " % self)
return
with self.connection.engine.begin() as conn:
change = getattr(item.change, 'number', None)
patchset = getattr(item.change, 'patchset', None)
ref = getattr(item.change, 'ref', '')
oldrev = getattr(item.change, 'oldrev', '')
newrev = getattr(item.change, 'newrev', '')
branch = getattr(item.change, 'branch', '')
buildset_ins = self.connection.zuul_buildset_table.insert().values(
zuul_ref=item.current_build_set.ref,
pipeline=item.pipeline.name,
project=item.change.project.name,
change=change,
patchset=patchset,
ref=ref,
oldrev=oldrev,
newrev=newrev,
ref_url=item.change.url,
result=item.current_build_set.result,
message=self._formatItemReport(
item, with_jobs=False),
tenant=item.pipeline.tenant.name,
branch=branch,
)
buildset_ins_result = conn.execute(buildset_ins)
build_inserts = []
for job in item.getJobs():
build = item.current_build_set.getBuild(job.name)
if not build:
# build hasn't began. The sql reporter can only send back
# stats about builds. It doesn't understand how to store
# information about the change.
continue
(result, url) = item.formatJobResult(job)
start = end = None
if build.start_time:
start = datetime.datetime.fromtimestamp(
build.start_time,
tz=datetime.timezone.utc)
if build.end_time:
end = datetime.datetime.fromtimestamp(
build.end_time,
tz=datetime.timezone.utc)
build_inserts.append({
'buildset_id': buildset_ins_result.inserted_primary_key[0],
'uuid': build.uuid,
'job_name': build.job.name,
'result': result,
'start_time': start,
'end_time': end,
'voting': build.job.voting,
'log_url': url,
'node_name': build.node_name,
})
conn.execute(self.connection.zuul_build_table.insert(),
build_inserts)
def getSchema():
sql_reporter = v.Schema(None)
return sql_reporter