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 5 import datetime, logging 6 7 import common 8 from autotest_lib.client.common_lib import priorities 9 10 import base_event, task 11 12 13 class 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 78 class Nightly(TimedEvent): 79 """A TimedEvent that allows a task to be triggered at every night. Each task 80 can set the hour when it should be triggered, through `hour` setting. 81 82 @var KEYWORD: the keyword to use in a run_on option to associate a task 83 with the Nightly event. 84 @var _DEFAULT_HOUR: the default hour to trigger the nightly event. 85 """ 86 87 KEYWORD = 'nightly' 88 # Each task may have different setting of `hour`. Therefore, nightly tasks 89 # can run at each hour. The default is set to 9PM. 90 _DEFAULT_HOUR = 21 91 PRIORITY = priorities.Priority.DAILY 92 TIMEOUT = 24 # Kicked off once a day, so they get the full day to run 93 94 def __init__(self, manifest_versions, always_handle): 95 """Constructor. 96 97 @param manifest_versions: ManifestVersions instance to use for querying. 98 @param always_handle: If True, make ShouldHandle() always return True. 99 """ 100 # Set the deadline to the next even hour. 101 now = self._now() 102 now_hour = datetime.datetime(now.year, now.month, now.day, now.hour) 103 extra_hour = 0 if now == now_hour else 1 104 deadline = now_hour + datetime.timedelta(hours=extra_hour) 105 super(Nightly, self).__init__(self.KEYWORD, manifest_versions, 106 always_handle, deadline) 107 108 109 def GetBranchBuildsForBoard(self, board): 110 return self._LatestPerBranchBuildsSince(board, 1) 111 112 113 def UpdateCriteria(self): 114 self._deadline = self._deadline + datetime.timedelta(hours=1) 115 116 117 def FilterTasks(self): 118 """Filter the tasks to only return tasks that should run now. 119 120 Nightly task can run at each hour. This function only return the tasks 121 set to run in current hour. 122 123 @return: A list of tasks that can run now. 124 """ 125 current_hour = self._now().hour 126 return [task for task in self.tasks 127 if ((task.hour is not None and task.hour == current_hour) or 128 (task.hour is None and 129 current_hour == self._DEFAULT_HOUR))] 130 131 132 class Weekly(TimedEvent): 133 """A TimedEvent that allows a task to be triggered at every week. Each task 134 can set the day when it should be triggered, through `day` setting. 135 136 @var KEYWORD: the keyword to use in a run_on option to associate a task 137 with the Weekly event. 138 @var _DEFAULT_DAY: The default day to run a weekly task. 139 @var _DEFAULT_HOUR: can be overridden in the "weekly_params" config section. 140 """ 141 142 KEYWORD = 'weekly' 143 _DEFAULT_DAY = 5 # Saturday 144 _DEFAULT_HOUR = 23 145 PRIORITY = priorities.Priority.WEEKLY 146 TIMEOUT = 7 * 24 # 7 days 147 148 149 @classmethod 150 def _ParseConfig(cls, config): 151 """Create args to pass to __init__ by parsing |config|. 152 153 Calls super class' _ParseConfig() method, then parses these additonal 154 options: 155 hour: Integer hour, on a 24 hour clock. 156 """ 157 from_base = super(Weekly, cls)._ParseConfig(config) 158 159 section = base_event.SectionName(cls.KEYWORD) 160 event_time = config.getint(section, 'hour') or cls._DEFAULT_HOUR 161 162 from_base.update({'event_time': event_time}) 163 return from_base 164 165 166 def __init__(self, manifest_versions, always_handle, event_time): 167 """Constructor. 168 169 @param manifest_versions: ManifestVersions instance to use for querying. 170 @param always_handle: If True, make ShouldHandle() always return True. 171 @param event_time: The hour of the day to set |self._deadline| at. 172 """ 173 # determine if we're past this week's event and set the 174 # next deadline for this suite appropriately. 175 now = self._now() 176 this_week_deadline = datetime.datetime.combine( 177 now, datetime.time(event_time)) 178 if this_week_deadline >= now: 179 deadline = this_week_deadline 180 else: 181 deadline = this_week_deadline + datetime.timedelta(days=1) 182 super(Weekly, self).__init__(self.KEYWORD, manifest_versions, 183 always_handle, deadline) 184 185 186 def Merge(self, to_merge): 187 """Merge this event with to_merge, changing some mutable properties. 188 189 keyword remains unchanged; the following take on values from to_merge: 190 _deadline iff the time of day in to_merge._deadline is different. 191 192 @param to_merge: A TimedEvent instance to merge into this instance. 193 """ 194 super(Weekly, self).Merge(to_merge) 195 if self._deadline.time() != to_merge._deadline.time(): 196 self._deadline = to_merge._deadline 197 198 199 def GetBranchBuildsForBoard(self, board): 200 return self._LatestPerBranchBuildsSince(board, 7) 201 202 203 def UpdateCriteria(self): 204 self._deadline = self._deadline + datetime.timedelta(days=1) 205 206 207 def FilterTasks(self): 208 """Filter the tasks to only return tasks that should run now. 209 210 Weekly task can be scheduled at any day of the week. This function only 211 return the tasks set to run in current day. 212 213 @return: A list of tasks that can run now. 214 """ 215 current_day = self._now().weekday() 216 return [task for task in self.tasks 217 if ((task.day is not None and task.day == current_day) or 218 (task.day is None and current_day == self._DEFAULT_DAY))] 219