diff --git a/doc/source/zuul.rst b/doc/source/zuul.rst index 605dea67c0..8d7bbeff99 100644 --- a/doc/source/zuul.rst +++ b/doc/source/zuul.rst @@ -268,6 +268,18 @@ each job as it builds a list from the project specification. **success-message (optional)** The message that should be reported to Gerrit if the job fails. +**hold-following-changes (optional)** + This is a boolean that indicates that changes that follow this + change in a dependent change queue should wait until this job + succeeds before launching. If this is applied to a very short job + that can predict whether longer jobs will fail early, this can be + used to reduce the number of jobs that Zuul will launch and + ultimately have to cancel. In that case, a small amount of + paralellization of jobs is traded for more efficient use of testing + resources. On the other hand, to apply this to a long running job + would largely defeat the parallelization of dependent change testing + that is the main feature of Zuul. The default is False. + **branch (optional)** This job should only be run on matching branches. This field is treated as a regular expression and multiple branches may be diff --git a/zuul/model.py b/zuul/model.py index c4efa398fe..9f3bae5b69 100644 --- a/zuul/model.py +++ b/zuul/model.py @@ -56,10 +56,12 @@ class ChangeQueue(object): class Job(object): def __init__(self, name): + # If you add attributes here, be sure to add them to the copy method. self.name = name self.failure_message = None self.success_message = None self.parameter_function = None + self.hold_following_changes = False self.event_filters = [] def __str__(self): @@ -71,6 +73,8 @@ class Job(object): def copy(self, other): self.failure_message = other.failure_message self.success_message = other.failure_message + self.parameter_function = other.parameter_function + self.hold_following_changes = other.hold_following_changes self.event_filters = other.event_filters[:] def eventMatches(self, event): @@ -374,6 +378,10 @@ class Change(object): for job in self._filterJobs(self.project.getJobs(self.queue_name)): build = self.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 @@ -404,8 +412,27 @@ class Change(object): fakebuild.result = 'SKIPPED' self.addBuild(fakebuild) + def isHoldingFollowingChanges(self): + tree = self.project.getJobTreeForQueue(self.queue_name) + for job in self._filterJobs(tree.getJobs()): + if not job.hold_following_changes: + continue + build = self.current_build_set.getBuild(job.name) + if not build: + return True + if build.result != 'SUCCESS': + return True + if not self.change_ahead: + return False + return self.change_ahead.isHoldingFollowingChanges() + def _findJobsToRun(self, job_trees): torun = [] + if self.change_ahead: + # Only run our jobs if any 'hold' jobs on the change ahead + # have completed successfully. + if self.change_ahead.isHoldingFollowingChanges(): + return [] for tree in job_trees: job = tree.job if not job.eventMatches(self.event): diff --git a/zuul/scheduler.py b/zuul/scheduler.py index f0262c8864..553d0f5629 100644 --- a/zuul/scheduler.py +++ b/zuul/scheduler.py @@ -102,6 +102,9 @@ class Scheduler(threading.Thread): m = config_job.get('success-message', None) if m: job.success_message = m + m = config_job.get('hold-following-changes', False) + if m: + job.hold_following_changes = True fname = config_job.get('parameter-function', None) if fname: func = self._config_env.get(fname, None) @@ -373,7 +376,11 @@ class BaseQueueManager(object): efilters += str(e) if efilters: efilters = ' ' + efilters - self.log.info("%s%s%s" % (istr, repr(tree.job), efilters)) + hold = '' + if tree.job.hold_following_changes: + hold = ' [hold]' + self.log.info("%s%s%s%s" % (istr, repr(tree.job), + efilters, hold)) for x in tree.job_trees: log_jobs(x, indent + 2)