Samuel Allan c4322d1fa7 Add support for scheduling periodic checks
If the `schedule` config option is set to a valid cron expression,
the charm will schedule tempest test runs (readonly-quick list)
according to the set schedule.

Change-Id: I8a10eff30dcbe385ebb4f8fd8d1d249f703786dc
2024-02-05 15:32:15 +10:30

84 lines
2.6 KiB
Python

# Copyright 2023 Canonical Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Utilities for validating."""
from dataclasses import (
dataclass,
)
from datetime import (
datetime,
)
from croniter import (
croniter,
)
@dataclass
class Schedule:
"""A cron schedule that has validation information."""
value: str
valid: bool
err: str
def validated_schedule(schedule: str) -> Schedule:
"""Process and validate a schedule str.
Return the schedule with validation info.
"""
# Empty schedule is fine; it means it's disabled in this context.
if not schedule:
return Schedule(value=schedule, valid=True, err="")
# croniter supports second repeats, but vixie cron does not.
if len(schedule.split()) == 6:
return Schedule(
value=schedule,
valid=False,
err="This cron does not support seconds in schedule (6 fields). "
"Exactly 5 columns must be specified for iterator expression.",
)
# constant base time for consistency
base = datetime(2004, 3, 5)
try:
cron = croniter(schedule, base, max_years_between_matches=1)
except ValueError as e:
msg = str(e)
# croniter supports second repeats, but vixie cron does not,
# so update the error message here to suit.
if "Exactly 5 or 6 columns" in msg:
msg = (
"Exactly 5 columns must be specified for iterator expression."
)
return Schedule(value=schedule, valid=False, err=msg)
# This is a rather naive method for enforcing this,
# and it may be possible to craft an expression
# that results in some consecutive runs within 15 minutes,
# however this is fine, as there is process locking for tempest,
# and this is more of a sanity check than a security requirement.
t1 = cron.get_next()
t2 = cron.get_next()
if t2 - t1 < 15 * 60: # 15 minutes in seconds
return Schedule(
value=schedule,
valid=False,
err="Cannot schedule periodic check to run faster than every 15 minutes.",
)
return Schedule(value=schedule, valid=True, err="")