Wrap up rrule validation for scheduler

This commit is contained in:
Matthew Jones 2014-04-02 11:44:40 -04:00
parent 3e2e682658
commit d190aa3001
2 changed files with 36 additions and 15 deletions

View File

@ -8,6 +8,7 @@ import socket
import urlparse
import logging
import os.path
import datetime
from dateutil import rrule
# PyYAML
@ -1376,19 +1377,20 @@ class ScheduleSerializer(BaseSerializer):
# - INTERVAL is not included
# - SECONDLY is used
# - TZID is used
# - multiple BYDAY (except WEEKLY and YEARLY (see below)), BYMONTHDAY, BYMONTH
# - multiple BYDAY yearly unless it lists all weekdays OR weekend days
# - BYDAY prefixed with a number (MO is good but not 20MO)
# - BYYEARDAY
# - BYWEEKNO
# - Multiple DTSTART or RRULE elements
# - COUNT > 999
def validate_rrule(self, attrs, source):
rrule_value = attrs[source]
multi_by_day = ".*?BYDAY[\:\=][a-zA-Z]{2},[a-zA-Z]{2}"
multi_by_month_day = ".*?BYMONTHDAY[\:\=][0-9]+,-*[0-9]+"
multi_by_month = ".*?BYMONTH[\:\=][0-9]+,[0-9]+"
by_day_with_numeric_prefix = ".*?BYDAY[\:\=][0-9]+[a-zA-Z]{2}"
if not re.match("DTSTART[\:\=][0-9]+T[0-9]+Z", rrule_value):
raise serializers.ValidationError('DTSTART required in rrule, value should match: DTSTART:YYYYMMDDTHHMMSSZ')
match_dtstart = re.match("DTSTART\:[0-9]+T[0-9]+Z", rrule_value)
match_count = re.match(".*?(COUNT\=[0-9]+)", rrule_value)
if not match_dtstart:
raise serializers.ValidationError('DTSTART required in rrule. Value should match: DTSTART:YYYYMMDDTHHMMSSZ')
if not 'interval' in rrule_value.lower():
raise serializers.ValidationError('INTERVAL required in rrule')
if 'tzid' in rrule_value.lower():
@ -1405,9 +1407,14 @@ class ScheduleSerializer(BaseSerializer):
raise serializers.ValidationError("BYYEARDAY not supported")
if 'byweekno' in rrule_value.lower():
raise serializers.ValidationError("BYWEEKNO not supported")
if re.match(multi_by_day, rrule_value) and not re.match(".*?FREQ[\:\=](WEEKLY|YEARLY)", rrule_value):
raise serializers.ValidationError("Multiple BYDAY elements only supported with WEEKLY and YEARLY frequency")
if match_count:
count_val = match_count.groups()[0].strip().split("=")
if int(count_val[1]) > 999:
raise serializers.ValidationError("COUNT > 999 is unsupported")
try:
# dtstart_group = match_dtstart.group()
# rrule_value = (rrule_value[0:match_dtstart.start()] + rrule_value[match_dtstart.end():]).strip()
# dtstart_actual = datetime.datetime.strptime(dtstart_group.split(":")[1], "%Y%m%dT%H%M%SZ")
sched_rule = rrule.rrulestr(rrule_value)
except Exception, e:
raise serializers.ValidationError("rrule parsing failed validation")

View File

@ -20,21 +20,30 @@ from awx.main.tests.base import BaseTest, BaseTransactionTest
__all__ = ['ScheduleTest']
UNTIL_SCHEDULE = "DTSTART:20140331T075000Z RRULE:FREQ=MINUTELY;INTERVAL=1;UNTIL=30230401T075000Z"
EXPIRED_SCHEDULES = ["DTSTART:19340331T055000Z RRULE:FREQ=MINUTELY;INTERVAL=10;COUNT=5"]
INFINITE_SCHEDULES = ["DTSTART:30340331T055000Z RRULE:FREQ=MINUTELY;INTERVAL=10"]
GOOD_SCHEDULES = ["DTSTART:30340331T055000Z RRULE:FREQ=MINUTELY;INTERVAL=10;COUNT=5",
# TODO: DTSTART DOESN'T WORK WITH DAILY?!?!
#"DTSTART=20240331T075000Z RRULE:FREQ=DAILY;INTERVAL=1;COUNT=1",
# TODO: UNTIL IS BROKEN!!
# "DTSTART=20140331T075000Z RRULE:FREQ=MINUTELY;INTERVAL=1 UNTIL=20230401T075000Z",
GOOD_SCHEDULES = ["DTSTART:20500331T055000Z RRULE:FREQ=MINUTELY;INTERVAL=10;COUNT=5",
"DTSTART:20240331T075000Z RRULE:FREQ=DAILY;INTERVAL=1;COUNT=1",
"DTSTART:20140331T075000Z RRULE:FREQ=MINUTELY;INTERVAL=1;UNTIL=20230401T075000Z",
"DTSTART:20140331T075000Z RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,WE,FR",
"DTSTART:20140331T075000Z RRULE:FREQ=WEEKLY;INTERVAL=5;BYDAY=MO",
"DTSTART:20140331T075000Z RRULE:FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=6",
"DTSTART:20140331T075000Z RRULE:FREQ=MONTHLY;INTERVAL=1;BYSETPOS=4;BYDAY=SU",
"DTSTART:20140331T075000Z RRULE:FREQ=MONTHLY;INTERVAL=1;BYSETPOS=-1;BYDAY=MO,TU,WE,TH,FR",
"DTSTART:20140331T075000Z RRULE:FREQ=MONTHLY;INTERVAL=1;BYSETPOS=-1;BYDAY=MO,TU,WE,TH,FR,SA,SU",
"DTSTART:20140331T075000Z RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=4;BYMONTHDAY=1",
"DTSTART:20140331T075000Z RRULE:FREQ=YEARLY;INTERVAL=1;BYSETPOS=-1;BYMONTH=8;BYDAY=SU",
"DTSTART:20140331T075000Z RRULE:FREQ=WEEKLY;INTERVAL=1;UNTIL=20230401T075000Z;BYDAY=MO,WE,FR",
"DTSTART:20140331T075000Z RRULE:FREQ=HOURLY;INTERVAL=1;UNTIL=20230610T075000Z"
]
BAD_SCHEDULES = ["", "DTSTART:20140331T055000 RRULE:FREQ=MINUTELY;INTERVAL=10;COUNT=5",
"RRULE:FREQ=MINUTELY;INTERVAL=10;COUNT=5",
"FREQ=MINUTELY;INTERVAL=10;COUNT=5",
"DTSTART:20240331T075000Z RRULE:FREQ=DAILY;INTERVAL=1;COUNT=10000000",
"DTSTART;TZID=US-Eastern:19961105T090000 RRULE:FREQ=MINUTELY;INTERVAL=10;COUNT=5",
"DTSTART:20140331T055000Z RRULE:FREQ=SECONDLY;INTERVAL=1",
"DTSTART:20140331T055000Z RRULE:FREQ=SECONDLY",
"DTSTART:20140331T055000Z RRULE:FREQ=MONTHLY;BYDAY=SU,MO;INTERVAL=1",
"DTSTART:20140331T055000Z RRULE:FREQ=YEARLY;BYDAY=20MO;INTERVAL=1",
"DTSTART:20140331T055000Z RRULE:FREQ=MONTHLY;BYMONTHDAY=10,15;INTERVAL=1",
"DTSTART:20140331T055000Z RRULE:FREQ=YEARLY;BYMONTH=1,2;INTERVAL=1",
@ -151,20 +160,25 @@ class ScheduleTest(BaseTest):
with self.current_user(self.normal_django_user):
data = self.post(first_url, new_schedule, expect=201)
self.assertEquals(data['dtend'], None)
long_schedule = dict(name='long_schedule', description='going for a long time', enabled=True, rrule=UNTIL_SCHEDULE)
with self.current_user(self.normal_django_user):
data = self.post(first_url, long_schedule, expect=201)
self.assertNotEquals(data['dtend'], None)
def test_schedule_filtering(self):
first_url = reverse('api:inventory_source_schedules_list', args=(self.first_inventory_source.pk,))
start_time = now() + datetime.timedelta(minutes=5)
dtstart_str = start_time.strftime("%Y%m%dT%H%M%SZ")
new_schedule = dict(name="filter_schedule_1", enabled=True, rrule="DTSTART:%s RRULE:RRULE:FREQ=MINUTELY;INTERVAL=10;COUNT=5" % dtstart_str)
new_schedule = dict(name="filter_schedule_1", enabled=True, rrule="DTSTART:%s RRULE:FREQ=MINUTELY;INTERVAL=10;COUNT=5" % dtstart_str)
with self.current_user(self.normal_django_user):
data = self.post(first_url, new_schedule, expect=201)
self.assertTrue(Schedule.objects.enabled().between(now(), now() + datetime.timedelta(minutes=10)).count(), 1)
start_time = now()
dtstart_str = start_time.strftime("%Y%m%dT%H%M%SZ")
new_schedule_middle = dict(name="runnable_schedule", enabled=True, rrule="DTSTART:%s RRULE:RRULE:FREQ=MINUTELY;INTERVAL=10;COUNT=5" % dtstart_str)
new_schedule_middle = dict(name="runnable_schedule", enabled=True, rrule="DTSTART:%s RRULE:FREQ=MINUTELY;INTERVAL=10;COUNT=5" % dtstart_str)
with self.current_user(self.normal_django_user):
data = self.post(first_url, new_schedule_middle, expect=201)
self.assertTrue(Schedule.objects.enabled().between(now() - datetime.timedelta(minutes=10), now() + datetime.timedelta(minutes=10)).count(), 1)