timed_event.py revision 9f256d91d378cdc0bbffa607ef3572f006d0b0dc
1# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import datetime, logging 6 7import common 8from autotest_lib.client.common_lib import priorities 9 10import base_event, task 11 12 13class TimedEvent(base_event.BaseEvent): 14 """Base class for events that trigger based on time/day. 15 16 @var _deadline: If this time has passed, ShouldHandle() returns True. 17 """ 18 19 20 def __init__(self, keyword, manifest_versions, always_handle, deadline): 21 """Constructor. 22 23 @param keyword: the keyword/name of this event, e.g. nightly. 24 @param manifest_versions: ManifestVersions instance to use for querying. 25 @param always_handle: If True, make ShouldHandle() always return True. 26 @param deadline: This instance's initial |_deadline|. 27 """ 28 super(TimedEvent, self).__init__(keyword, manifest_versions, 29 always_handle) 30 self._deadline = deadline 31 32 33 def __ne__(self, other): 34 return self._deadline != other._deadline or self.tasks != other.tasks 35 36 37 def __eq__(self, other): 38 return self._deadline == other._deadline and self.tasks == other.tasks 39 40 41 @staticmethod 42 def _now(): 43 return datetime.datetime.now() 44 45 46 def Prepare(self): 47 pass 48 49 50 def ShouldHandle(self): 51 """Return True if self._deadline has passed; False if not.""" 52 if super(TimedEvent, self).ShouldHandle(): 53 return True 54 else: 55 logging.info('Checking deadline %s for event %s', 56 self._deadline, self.keyword) 57 return self._now() >= self._deadline 58 59 60 def _LatestPerBranchBuildsSince(self, board, days_ago): 61 """Get latest per-branch, per-board builds from last |days_ago| days. 62 63 @param board: the board whose builds we want. 64 @param days_ago: how many days back to look for manifests. 65 @return {branch: [build-name]} 66 """ 67 since_date = self._deadline - datetime.timedelta(days=days_ago) 68 all_branch_manifests = self._mv.ManifestsSinceDate(since_date, board) 69 latest_branch_builds = {} 70 for (type, milestone), manifests in all_branch_manifests.iteritems(): 71 build = base_event.BuildName(board, type, milestone, manifests[-1]) 72 latest_branch_builds[task.PickBranchName(type, milestone)] = [build] 73 logging.info('%s event found candidate builds: %r', 74 self.keyword, latest_branch_builds) 75 return latest_branch_builds 76 77 78class Nightly(TimedEvent): 79 """A TimedEvent that happens every night. 80 81 @var KEYWORD: the keyword to use in a run_on option to associate a task 82 with the Nightly event. 83 @var _DEFAULT_HOUR: the default hour to trigger the nightly event. 84 """ 85 86 KEYWORD = 'nightly' 87 # Each task may have different setting of `hour`. Therefore, nightly tasks 88 # can run at each hour. The default is set to 9PM. 89 _DEFAULT_HOUR = 21 90 PRIORITY = priorities.Priority.DAILY 91 TIMEOUT = 24 # Kicked off once a day, so they get the full day to run 92 93 def __init__(self, manifest_versions, always_handle): 94 """Constructor. 95 96 @param manifest_versions: ManifestVersions instance to use for querying. 97 @param always_handle: If True, make ShouldHandle() always return True. 98 """ 99 # Set the deadline to the next even hour. 100 now = self._now() 101 now_hour = datetime.datetime(now.year, now.month, now.day, now.hour) 102 extra_hour = 0 if now == now_hour else 1 103 deadline = now_hour + datetime.timedelta(hours=extra_hour) 104 super(Nightly, self).__init__(self.KEYWORD, manifest_versions, 105 always_handle, deadline) 106 107 108 def Merge(self, to_merge): 109 """Merge this event with to_merge, changing some mutable properties. 110 111 keyword remains unchanged; the following take on values from to_merge: 112 _deadline iff the time of day in to_merge._deadline is different. 113 114 @param to_merge: A TimedEvent instance to merge into this isntance. 115 """ 116 super(Nightly, self).Merge(to_merge) 117 if self._deadline.time() != to_merge._deadline.time(): 118 self._deadline = to_merge._deadline 119 120 121 def GetBranchBuildsForBoard(self, board): 122 return self._LatestPerBranchBuildsSince(board, 1) 123 124 125 def UpdateCriteria(self): 126 self._deadline = self._deadline + datetime.timedelta(hours=1) 127 128 129 def FilterTasks(self): 130 """Filter the tasks to only return tasks should run now. 131 132 Nightly task can run at each hour. this function only return the tasks 133 set to run in current hour. 134 135 @return: A list of tasks can run now. 136 """ 137 current_hour = self._now().hour 138 return [task for task in self.tasks 139 if ((task.hour is not None and task.hour == current_hour) or 140 (task.hour is None and 141 current_hour == self._DEFAULT_HOUR))] 142 143 144class Weekly(TimedEvent): 145 """A TimedEvent that happens every week. 146 147 @var KEYWORD: the keyword to use in a run_on option to associate a task 148 with the Weekly event. 149 @var _DEFAULT_DAY: can be overridden in the "weekly_params" config section. 150 @var _DEFAULT_HOUR: can be overridden in the "weekly_params" config section. 151 """ 152 153 KEYWORD = 'weekly' 154 _DEFAULT_DAY = 5 # Saturday 155 _DEFAULT_HOUR = 23 156 PRIORITY = priorities.Priority.WEEKLY 157 TIMEOUT = 7 * 24 # 7 days 158 159 160 @classmethod 161 def _ParseConfig(cls, config): 162 """Create args to pass to __init__ by parsing |config|. 163 164 Calls super class' _ParseConfig() method, then parses these additonal 165 options: 166 hour: Integer hour, on a 24 hour clock. 167 day: Integer day, in a 0-indexed 7 day week, e.g. 5 == Saturday. 168 """ 169 from_base = super(Weekly, cls)._ParseConfig(config) 170 171 section = base_event.SectionName(cls.KEYWORD) 172 event_time = config.getint(section, 'hour') or cls._DEFAULT_HOUR 173 event_day = config.getint(section, 'day') or cls._DEFAULT_DAY 174 175 from_base.update({'event_time': event_time, 'event_day': event_day}) 176 return from_base 177 178 179 def __init__(self, manifest_versions, always_handle, event_day, event_time): 180 """Constructor. 181 182 @param manifest_versions: ManifestVersions instance to use for querying. 183 @param always_handle: If True, make ShouldHandle() always return True. 184 @param event_day: The day of the week to set |self._deadline| at. 185 @param event_time: The hour of the day to set |self._deadline| at. 186 """ 187 # determine if we're past this week's event and set the 188 # next deadline for this suite appropriately. 189 now = self._now() 190 # Get a datetime representing this week's event_day 191 # If now() is a Sunday, we 'add' 5 - 6 = -1 days to go back a day. 192 # If now() is a Monday, we add 5 - 0 = 5 days to jump forward. 193 this_week = now + datetime.timedelta(event_day-now.weekday()) 194 this_week_deadline = datetime.datetime.combine( 195 this_week, datetime.time(event_time)) 196 if this_week_deadline >= now: 197 deadline = this_week_deadline 198 else: 199 deadline = this_week_deadline + datetime.timedelta(days=7) 200 super(Weekly, self).__init__(self.KEYWORD, manifest_versions, 201 always_handle, deadline) 202 203 204 def Merge(self, to_merge): 205 """Merge this event with to_merge, changing some mutable properties. 206 207 keyword remains unchanged; the following take on values from to_merge: 208 _deadline iff the time of day in to_merge._deadline is different. 209 210 @param to_merge: A TimedEvent instance to merge into this isntance. 211 """ 212 super(Weekly, self).Merge(to_merge) 213 if (self._deadline.time() != to_merge._deadline.time() or 214 self._deadline.weekday() != to_merge._deadline.weekday()): 215 self._deadline = to_merge._deadline 216 217 218 def GetBranchBuildsForBoard(self, board): 219 return self._LatestPerBranchBuildsSince(board, 7) 220 221 222 def UpdateCriteria(self): 223 self._deadline = self._deadline + datetime.timedelta(days=7) 224