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