From bf1d93168b00830201809ee158a54bfefff605bf Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Fri, 18 Sep 2020 10:29:46 -0400 Subject: [PATCH] fix an rrule bug that causes improper HOURLY/MINUTELY calculation see: https://github.com/ansible/awx/issues/8071 --- awx/main/models/schedules.py | 9 +++- .../tests/functional/models/test_schedule.py | 54 ++++++++++++++++++- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/awx/main/models/schedules.py b/awx/main/models/schedules.py index a3dd32d6b0..5b907a4333 100644 --- a/awx/main/models/schedules.py +++ b/awx/main/models/schedules.py @@ -205,10 +205,15 @@ class Schedule(PrimordialModel, LaunchTimeConfig): 'A valid TZID must be provided (e.g., America/New_York)' ) - if fast_forward and ('MINUTELY' in rrule or 'HOURLY' in rrule): + if ( + fast_forward and + ('MINUTELY' in rrule or 'HOURLY' in rrule) and + 'COUNT=' not in rrule + ): try: first_event = x[0] - if first_event < now(): + # If the first event was over a week ago... + if (now() - first_event).days > 7: # hourly/minutely rrules with far-past DTSTART values # are *really* slow to precompute # start *from* one week ago to speed things up drastically diff --git a/awx/main/tests/functional/models/test_schedule.py b/awx/main/tests/functional/models/test_schedule.py index 5575921ff0..fb5bfbf271 100644 --- a/awx/main/tests/functional/models/test_schedule.py +++ b/awx/main/tests/functional/models/test_schedule.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import datetime, timedelta from contextlib import contextmanager from django.utils.timezone import now @@ -161,6 +161,58 @@ class TestComputedFields: assert job_template.next_schedule == expected_schedule +@pytest.mark.django_db +@pytest.mark.parametrize('freq, delta', ( + ('MINUTELY', 1), + ('HOURLY', 1) +)) +def test_past_week_rrule(job_template, freq, delta): + # see: https://github.com/ansible/awx/issues/8071 + recent = (datetime.utcnow() - timedelta(days=3)) + recent = recent.replace(hour=0, minute=0, second=0, microsecond=0) + recent_dt = recent.strftime('%Y%m%d') + rrule = f'DTSTART;TZID=America/New_York:{recent_dt}T000000 RRULE:FREQ={freq};INTERVAL={delta};COUNT=5' # noqa + sched = Schedule.objects.create( + name='example schedule', + rrule=rrule, + unified_job_template=job_template + ) + first_event = sched.rrulestr(sched.rrule)[0] + assert first_event.replace(tzinfo=None) == recent + + +@pytest.mark.django_db +@pytest.mark.parametrize('freq, delta', ( + ('MINUTELY', 1), + ('HOURLY', 1) +)) +def test_really_old_dtstart(job_template, freq, delta): + # see: https://github.com/ansible/awx/issues/8071 + # If an event is per-minute/per-hour and was created a *really long* + # time ago, we should just bump forward to start counting "in the last week" + rrule = f'DTSTART;TZID=America/New_York:20150101T000000 RRULE:FREQ={freq};INTERVAL={delta}' # noqa + sched = Schedule.objects.create( + name='example schedule', + rrule=rrule, + unified_job_template=job_template + ) + last_week = (datetime.utcnow() - timedelta(days=7)).date() + first_event = sched.rrulestr(sched.rrule)[0] + assert last_week == first_event.date() + + # the next few scheduled events should be the next minute/hour incremented + next_five_events = list(sched.rrulestr(sched.rrule).xafter(now(), count=5)) + + assert next_five_events[0] > now() + last = None + for event in next_five_events: + if last: + assert event == last + ( + timedelta(minutes=1) if freq == 'MINUTELY' else timedelta(hours=1) + ) + last = event + + @pytest.mark.django_db def test_repeats_forever(job_template): s = Schedule(