| # Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import datetime |
| import logging |
| |
| import common |
| from autotest_lib.client.common_lib import priorities |
| |
| import base_event |
| import task |
| |
| |
| class TimedEvent(base_event.BaseEvent): |
| """Base class for events that trigger based on time/day. |
| |
| @var _deadline: If this time has passed, ShouldHandle() returns True. |
| """ |
| |
| # Number of days between each timed event to trigger. Default to 1. |
| DAYS_INTERVAL = 1 |
| |
| |
| def __init__(self, keyword, manifest_versions, always_handle, deadline): |
| """Constructor. |
| |
| @param keyword: the keyword/name of this event, e.g. nightly. |
| @param manifest_versions: ManifestVersions instance to use for querying. |
| @param always_handle: If True, make ShouldHandle() always return True. |
| @param deadline: This instance's initial |_deadline|. |
| """ |
| super(TimedEvent, self).__init__(keyword, manifest_versions, |
| always_handle) |
| self._deadline = deadline |
| |
| |
| def __ne__(self, other): |
| return self._deadline != other._deadline or self.tasks != other.tasks |
| |
| |
| def __eq__(self, other): |
| return self._deadline == other._deadline and self.tasks == other.tasks |
| |
| |
| @staticmethod |
| def _now(): |
| return datetime.datetime.now() |
| |
| |
| def Prepare(self): |
| pass |
| |
| |
| def ShouldHandle(self): |
| """Return True if self._deadline has passed; False if not.""" |
| if super(TimedEvent, self).ShouldHandle(): |
| return True |
| else: |
| logging.info('Checking deadline %s for event %s', |
| self._deadline, self.keyword) |
| return self._now() >= self._deadline |
| |
| |
| def _LatestPerBranchBuildsSince(self, board, days_ago): |
| """Get latest per-branch, per-board builds from last |days_ago| days. |
| |
| @param board: the board whose builds we want. |
| @param days_ago: how many days back to look for manifests. |
| @return {branch: [build-name]} |
| """ |
| since_date = self._deadline - datetime.timedelta(days=days_ago) |
| all_branch_manifests = self._mv.ManifestsSinceDate(since_date, board) |
| latest_branch_builds = {} |
| for (type, milestone), manifests in all_branch_manifests.iteritems(): |
| build = base_event.BuildName(board, type, milestone, manifests[-1]) |
| latest_branch_builds[task.PickBranchName(type, milestone)] = [build] |
| logging.info('%s event found candidate builds: %r', |
| self.keyword, latest_branch_builds) |
| return latest_branch_builds |
| |
| |
| def _LatestLaunchControlBuildsSince(self, board, days_ago): |
| """Get per-branch, per-board builds since last run of this event. |
| |
| @param board: the board whose builds we want, e.g., android-shamu. |
| @param days_ago: how many days back to look for manifests. |
| |
| @return: A list of Launch Control builds for the given board, e.g., |
| ['git_mnc_release/shamu-eng/123', |
| 'git_mnc_release/shamu-eng/124']. |
| """ |
| # TODO(crbug.com/589936): Get the latest builds for Launch Control for a |
| # given period, not just the last build on each branch. |
| return self._LatestLaunchControlBuilds(board) |
| |
| |
| def GetBranchBuildsForBoard(self, board): |
| """Get per-branch, per-board builds since last run of this event. |
| """ |
| return self._LatestPerBranchBuildsSince(board, self.DAYS_INTERVAL) |
| |
| |
| def GetLaunchControlBuildsForBoard(self, board): |
| """Get per-branch, per-board builds since last run of this event. |
| |
| @param board: the board whose builds we want. |
| |
| @return: A list of Launch Control builds for the given board, e.g., |
| ['git_mnc_release/shamu-eng/123', |
| 'git_mnc_release/shamu-eng/124']. |
| """ |
| return self._LatestLaunchControlBuildsSince(board, self.DAYS_INTERVAL) |
| |
| |
| class Nightly(TimedEvent): |
| """A TimedEvent that allows a task to be triggered at every night. Each task |
| can set the hour when it should be triggered, through `hour` setting. |
| |
| @var KEYWORD: the keyword to use in a run_on option to associate a task |
| with the Nightly event. |
| @var _DEFAULT_HOUR: the default hour to trigger the nightly event. |
| """ |
| |
| # Nightly event is triggered once a day. |
| DAYS_INTERVAL = 1 |
| |
| KEYWORD = 'nightly' |
| # Each task may have different setting of `hour`. Therefore, nightly tasks |
| # can run at each hour. The default is set to 9PM. |
| _DEFAULT_HOUR = 21 |
| PRIORITY = priorities.Priority.DAILY |
| TIMEOUT = 24 # Kicked off once a day, so they get the full day to run |
| |
| def __init__(self, manifest_versions, always_handle): |
| """Constructor. |
| |
| @param manifest_versions: ManifestVersions instance to use for querying. |
| @param always_handle: If True, make ShouldHandle() always return True. |
| """ |
| # Set the deadline to the next even hour. |
| now = self._now() |
| now_hour = datetime.datetime(now.year, now.month, now.day, now.hour) |
| extra_hour = 0 if now == now_hour else 1 |
| deadline = now_hour + datetime.timedelta(hours=extra_hour) |
| super(Nightly, self).__init__(self.KEYWORD, manifest_versions, |
| always_handle, deadline) |
| |
| |
| def UpdateCriteria(self): |
| self._deadline = self._deadline + datetime.timedelta(hours=1) |
| |
| |
| def FilterTasks(self): |
| """Filter the tasks to only return tasks that should run now. |
| |
| Nightly task can run at each hour. This function only return the tasks |
| set to run in current hour. |
| |
| @return: A list of tasks that can run now. |
| """ |
| current_hour = self._now().hour |
| return [task for task in self.tasks |
| if ((task.hour is not None and task.hour == current_hour) or |
| (task.hour is None and |
| current_hour == self._DEFAULT_HOUR))] |
| |
| |
| class Weekly(TimedEvent): |
| """A TimedEvent that allows a task to be triggered at every week. Each task |
| can set the day when it should be triggered, through `day` setting. |
| |
| @var KEYWORD: the keyword to use in a run_on option to associate a task |
| with the Weekly event. |
| @var _DEFAULT_DAY: The default day to run a weekly task. |
| @var _DEFAULT_HOUR: can be overridden in the "weekly_params" config section. |
| """ |
| |
| # Weekly event is triggered once a week. |
| DAYS_INTERVAL = 7 |
| |
| KEYWORD = 'weekly' |
| _DEFAULT_DAY = 5 # Saturday |
| _DEFAULT_HOUR = 23 |
| PRIORITY = priorities.Priority.WEEKLY |
| TIMEOUT = 7 * 24 # 7 days |
| |
| |
| @classmethod |
| def _ParseConfig(cls, config): |
| """Create args to pass to __init__ by parsing |config|. |
| |
| Calls super class' _ParseConfig() method, then parses these additonal |
| options: |
| hour: Integer hour, on a 24 hour clock. |
| """ |
| from_base = super(Weekly, cls)._ParseConfig(config) |
| |
| section = base_event.SectionName(cls.KEYWORD) |
| event_time = config.getint(section, 'hour') or cls._DEFAULT_HOUR |
| |
| from_base.update({'event_time': event_time}) |
| return from_base |
| |
| |
| def __init__(self, manifest_versions, always_handle, event_time): |
| """Constructor. |
| |
| @param manifest_versions: ManifestVersions instance to use for querying. |
| @param always_handle: If True, make ShouldHandle() always return True. |
| @param event_time: The hour of the day to set |self._deadline| at. |
| """ |
| # determine if we're past this week's event and set the |
| # next deadline for this suite appropriately. |
| now = self._now() |
| this_week_deadline = datetime.datetime.combine( |
| now, datetime.time(event_time)) |
| if this_week_deadline >= now: |
| deadline = this_week_deadline |
| else: |
| deadline = this_week_deadline + datetime.timedelta(days=1) |
| super(Weekly, self).__init__(self.KEYWORD, manifest_versions, |
| always_handle, deadline) |
| |
| |
| def Merge(self, to_merge): |
| """Merge this event with to_merge, changing some mutable properties. |
| |
| keyword remains unchanged; the following take on values from to_merge: |
| _deadline iff the time of day in to_merge._deadline is different. |
| |
| @param to_merge: A TimedEvent instance to merge into this instance. |
| """ |
| super(Weekly, self).Merge(to_merge) |
| if self._deadline.time() != to_merge._deadline.time(): |
| self._deadline = to_merge._deadline |
| |
| |
| def UpdateCriteria(self): |
| self._deadline = self._deadline + datetime.timedelta(days=1) |
| |
| |
| def FilterTasks(self): |
| """Filter the tasks to only return tasks that should run now. |
| |
| Weekly task can be scheduled at any day of the week. This function only |
| return the tasks set to run in current day. |
| |
| @return: A list of tasks that can run now. |
| """ |
| current_day = self._now().weekday() |
| return [task for task in self.tasks |
| if ((task.day is not None and task.day == current_day) or |
| (task.day is None and current_day == self._DEFAULT_DAY))] |