diff --git a/tests/unit/test_scheduler.py b/tests/unit/test_scheduler.py index b34a60c3e8..0f94bc13a5 100644 --- a/tests/unit/test_scheduler.py +++ b/tests/unit/test_scheduler.py @@ -5737,7 +5737,18 @@ For CI problems and help debugging, contact ci@example.org""" self.executor_server.release('.*-test*') self.waitUntilSettled() + tenant = self.scheds.first.sched.abide.tenants.get('tenant-one') + items = tenant.layout.pipelines['check'].getAllItems() + build_set = items[0].current_build_set + for x in range(3): + # We should have x+1 retried builds for project-test1 + retry_builds = build_set.getRetryBuildsForJob('project-test1') + self.assertEqual(len(retry_builds), x + 1) + for build in retry_builds: + self.assertEqual(build.retry, True) + self.assertEqual(build.result, 'RETRY') + self.assertEqual(len(self.builds), 1, 'len of builds at x=%d is wrong' % x) self.builds[0].requeue = True diff --git a/zuul/driver/mqtt/mqttreporter.py b/zuul/driver/mqtt/mqttreporter.py index 3ef2be58e3..74b597feaa 100644 --- a/zuul/driver/mqtt/mqttreporter.py +++ b/zuul/driver/mqtt/mqttreporter.py @@ -48,7 +48,8 @@ class MQTTReporter(BaseReporter): 'buildset': { 'uuid': item.current_build_set.uuid, 'result': item.current_build_set.result, - 'builds': [] + 'builds': [], + 'retry_builds': [], }, 'zuul_event_id': item.event.zuul_event_id, } @@ -70,6 +71,24 @@ class MQTTReporter(BaseReporter): 'result': result, 'dependencies': [j.name for j in job.dependencies], }) + # Report build data of retried builds if available + retry_builds = item.current_build_set.getRetryBuildsForJob( + job.name) + for build in retry_builds: + (result, url) = item.formatJobResult(job, build) + retry_build_information = { + 'job_name': job.name, + 'voting': job.voting, + 'uuid': build.uuid, + 'start_time': build.start_time, + 'end_time': build.end_time, + 'execute_time': build.execute_time, + 'log_url': url, + 'result': result, + } + message['buildset']['retry_builds'].append( + retry_build_information) + message['buildset']['builds'].append(job_informations) topic = None try: diff --git a/zuul/executor/client.py b/zuul/executor/client.py index 0a9592c31f..d80b90a3be 100644 --- a/zuul/executor/client.py +++ b/zuul/executor/client.py @@ -457,11 +457,9 @@ class ExecutorClient(object): warnings = data.get('warnings', []) log.info("Build complete, result %s, warnings %s", result, warnings) - # If the build should be retried, don't supply the result - # so that elsewhere we don't have to deal with keeping - # track of which results are non-final. + if build.retry: - result = None + result = 'RETRY' # If the build was canceled, we did actively cancel the job so # don't overwrite the result and don't retry. diff --git a/zuul/manager/__init__.py b/zuul/manager/__init__.py index fdce0c7b72..75c8a2d209 100644 --- a/zuul/manager/__init__.py +++ b/zuul/manager/__init__.py @@ -1035,7 +1035,7 @@ class PipelineManager(metaclass=ABCMeta): self._resumeBuilds(build.build_set) if (item.project_pipeline_config.fail_fast and - build.failed and build.job.voting): + build.failed and build.job.voting and not build.retry): # If fail-fast is set and the build is not successful # cancel all remaining jobs. log.debug("Build %s failed and fail-fast enabled, canceling " diff --git a/zuul/model.py b/zuul/model.py index 34f358b0ed..ccfea354eb 100644 --- a/zuul/model.py +++ b/zuul/model.py @@ -1993,6 +1993,7 @@ class BuildSet(object): def __init__(self, item): self.item = item self.builds = {} + self.retry_builds = {} self.result = None self.uuid = None self.commit = None @@ -2053,6 +2054,9 @@ class BuildSet(object): self.tries[build.job.name] = 1 build.build_set = self + def addRetryBuild(self, build): + self.retry_builds.setdefault(build.job.name, []).append(build) + def removeBuild(self, build): if build.job.name not in self.builds: return @@ -2067,6 +2071,9 @@ class BuildSet(object): keys.sort() return [self.builds.get(x) for x in keys] + def getRetryBuildsForJob(self, job_name): + return self.retry_builds.get(job_name, []) + def getJobNodeSet(self, job_name: str) -> NodeSet: # Return None if not provisioned; empty NodeSet if no nodes # required @@ -2185,6 +2192,9 @@ class QueueItem(object): def addBuild(self, build): self.current_build_set.addBuild(build) + def addRetryBuild(self, build): + self.current_build_set.addRetryBuild(build) + def removeBuild(self, build): self.current_build_set.removeBuild(build) @@ -2641,6 +2651,7 @@ class QueueItem(object): def setResult(self, build): if build.retry: + self.addRetryBuild(build) self.removeBuild(build) return @@ -2749,16 +2760,17 @@ class QueueItem(object): return url - def formatJobResult(self, job): + def formatJobResult(self, job, build=None): if (self.pipeline.tenant.report_build_page and self.pipeline.tenant.web_root): - build = self.current_build_set.getBuild(job.name) + if build is None: + build = self.current_build_set.getBuild(job.name) pattern = urllib.parse.urljoin(self.pipeline.tenant.web_root, 'build/{build.uuid}') url = self.formatUrlPattern(pattern, job, build) return (build.result, url) else: - return self.formatProvisionalJobResult(job) + return self.formatProvisionalJobResult(job, build) def formatStatusUrl(self): # If we don't have a web root set, we can't format any url @@ -2784,8 +2796,9 @@ class QueueItem(object): ) return self.formatUrlPattern(pattern) - def formatProvisionalJobResult(self, job): - build = self.current_build_set.getBuild(job.name) + def formatProvisionalJobResult(self, job, build=None): + if build is None: + build = self.current_build_set.getBuild(job.name) result = build.result pattern = None if result == 'SUCCESS':