Allow Passing of Jitter Values in TimerDriver

For periodic pipelines to not all trigger at the same moment and thus
potentially bear a lot of load on a system, we could spread the trigger
times by a jitter value. The APScheduler library Zuul uses for timer
triggers supports such jitter values.
This change allows one to pass that jitter value within the cron-like
'time' parameter of the timer trigger.
The optional Jitter parameter can be passed as the last parameter after
the optional 'seconds'.

Change-Id: I58e756dff6251b49a972b26f7a3a49a8ee5aa70e
This commit is contained in:
Benjamin Schanzel 2020-01-16 13:07:41 +01:00
parent ad42c7f570
commit 612472b9e3
4 changed files with 81 additions and 7 deletions

View File

@ -28,3 +28,8 @@ response to that event.
The time specification in cron syntax. Only the 5 part syntax
is supported, not the symbolic names. Example: ``0 0 * * *``
runs at midnight. The first weekday is Monday.
An optional 6th part specifies seconds. The optional 7th part
specifies a jitter in seconds. This advances or delays the
trigger randomly, limited by the specified value.
Example ``0 0 * * * * 60`` runs at midnight with a +/- 60
seconds jitter.

View File

@ -0,0 +1,56 @@
- pipeline:
name: check
manager: independent
trigger:
gerrit:
- event: patchset-created
success:
gerrit:
Verified: 1
failure:
gerrit:
Verified: -1
- pipeline:
name: periodic
manager: independent
trigger:
timer:
- time: '* * * * * */1 5'
- job:
name: base
parent: null
run: playbooks/base.yaml
- job:
name: project-test1
run: playbooks/project-test1.yaml
- job:
name: project-test2
run: playbooks/project-test2.yaml
- job:
name: project-bitrot
nodeset:
nodes:
- name: static
label: ubuntu-xenial
run: playbooks/project-bitrot.yaml
- project:
name: org/project1
check:
jobs:
- project-test1
- project:
name: org/project
check:
jobs:
- project-test1
- project-test2
periodic:
jobs:
- project-bitrot

View File

@ -3826,15 +3826,14 @@ class TestScheduler(ZuulTestCase):
ref='refs/heads/stable'),
], ordered=False)
def test_timer(self):
"Test that a periodic job is triggered"
def _test_timer(self, config_file):
# This test can not use simple_layout because it must start
# with a configuration which does not include a
# timer-triggered job so that we have an opportunity to set
# the hold flag before the first job.
self.create_branch('org/project', 'stable')
self.executor_server.hold_jobs_in_build = True
self.commitConfigUpdate('common-config', 'layouts/timer.yaml')
self.commitConfigUpdate('common-config', config_file)
self.sched.reconfigure(self.config)
# The pipeline triggers every second, so we should have seen
@ -3888,6 +3887,14 @@ class TestScheduler(ZuulTestCase):
ref='refs/heads/stable'),
], ordered=False)
def test_timer(self):
"Test that a periodic job is triggered"
self._test_timer('layouts/timer.yaml')
def test_timer_with_jitter(self):
"Test that a periodic job with a jitter is triggered"
self._test_timer('layouts/timer-jitter.yaml')
def test_idle(self):
"Test that frequent periodic jobs work"
# This test can not use simple_layout because it must start

View File

@ -61,7 +61,7 @@ class TimerDriver(Driver, TriggerInterface):
continue
for timespec in ef.timespecs:
parts = timespec.split()
if len(parts) < 5 or len(parts) > 6:
if len(parts) < 5 or len(parts) > 7:
self.log.error(
"Unable to parse time value '%s' "
"defined in pipeline %s" % (
@ -69,15 +69,21 @@ class TimerDriver(Driver, TriggerInterface):
pipeline.name))
continue
minute, hour, dom, month, dow = parts[:5]
# default values
second = None
jitter = None
if len(parts) > 5:
second = parts[5]
else:
second = None
if len(parts) > 6:
jitter = parts[6]
try:
jitter = int(jitter) if jitter is not None else None
trigger = CronTrigger(day=dom, day_of_week=dow,
hour=hour, minute=minute,
second=second)
second=second, jitter=jitter)
except ValueError:
self.log.exception(
"Unable to create CronTrigger "