Chris Masone | 2d61ca2 | 2012-04-02 16:52:46 -0700 | [diff] [blame] | 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 | |
Chris Masone | 83af70c | 2012-04-18 14:33:05 -0700 | [diff] [blame] | 5 | import datetime, logging |
Chris Masone | 2d61ca2 | 2012-04-02 16:52:46 -0700 | [diff] [blame] | 6 | import base_event, forgiving_config_parser, task |
| 7 | |
| 8 | |
| 9 | class TimedEvent(base_event.BaseEvent): |
Chris Masone | 5bde7dc | 2012-04-09 17:08:19 -0700 | [diff] [blame] | 10 | """Base class for events that trigger based on time/day. |
| 11 | |
Chris Masone | 93f51d4 | 2012-04-18 08:46:52 -0700 | [diff] [blame] | 12 | @var _deadline: If this time has passed, ShouldHandle() returns True. |
Chris Masone | 5bde7dc | 2012-04-09 17:08:19 -0700 | [diff] [blame] | 13 | """ |
Chris Masone | 2d61ca2 | 2012-04-02 16:52:46 -0700 | [diff] [blame] | 14 | |
Chris Masone | 2d61ca2 | 2012-04-02 16:52:46 -0700 | [diff] [blame] | 15 | |
Chris Masone | 93f51d4 | 2012-04-18 08:46:52 -0700 | [diff] [blame] | 16 | def __init__(self, keyword, manifest_versions, always_handle, deadline): |
| 17 | """Constructor. |
Chris Masone | 2d61ca2 | 2012-04-02 16:52:46 -0700 | [diff] [blame] | 18 | |
Chris Masone | 93f51d4 | 2012-04-18 08:46:52 -0700 | [diff] [blame] | 19 | @param keyword: the keyword/name of this event, e.g. nightly. |
| 20 | @param manifest_versions: ManifestVersions instance to use for querying. |
| 21 | @param always_handle: If True, make ShouldHandle() always return True. |
| 22 | @param deadline: This instance's initial |_deadline|. |
| 23 | """ |
| 24 | super(TimedEvent, self).__init__(keyword, manifest_versions, |
| 25 | always_handle) |
Chris Masone | 2d61ca2 | 2012-04-02 16:52:46 -0700 | [diff] [blame] | 26 | self._deadline = deadline |
| 27 | |
| 28 | |
| 29 | def __ne__(self, other): |
Chris Masone | 96f1663 | 2012-04-04 18:36:03 -0700 | [diff] [blame] | 30 | return self._deadline != other._deadline or self.tasks != other.tasks |
Chris Masone | 2d61ca2 | 2012-04-02 16:52:46 -0700 | [diff] [blame] | 31 | |
| 32 | |
| 33 | def __eq__(self, other): |
Chris Masone | 96f1663 | 2012-04-04 18:36:03 -0700 | [diff] [blame] | 34 | return self._deadline == other._deadline and self.tasks == other.tasks |
Chris Masone | 2d61ca2 | 2012-04-02 16:52:46 -0700 | [diff] [blame] | 35 | |
| 36 | |
| 37 | @staticmethod |
Chris Masone | 2d61ca2 | 2012-04-02 16:52:46 -0700 | [diff] [blame] | 38 | def _now(): |
| 39 | return datetime.datetime.now() |
| 40 | |
| 41 | |
Chris Masone | 73a7838 | 2012-04-20 13:25:51 -0700 | [diff] [blame] | 42 | def Prepare(self): |
| 43 | pass |
| 44 | |
| 45 | |
Chris Masone | 2d61ca2 | 2012-04-02 16:52:46 -0700 | [diff] [blame] | 46 | def ShouldHandle(self): |
| 47 | """Return True if self._deadline has passed; False if not.""" |
Chris Masone | 99d32b8 | 2012-07-11 10:29:55 -0700 | [diff] [blame^] | 48 | if super(TimedEvent, self).ShouldHandle(): |
| 49 | return True |
| 50 | else: |
Chris Masone | 075aafa | 2012-05-09 12:08:59 -0700 | [diff] [blame] | 51 | logging.info('Checking deadline %s for event %s', |
| 52 | self._deadline, self.keyword) |
Chris Masone | 855d86f | 2012-05-07 13:48:07 -0700 | [diff] [blame] | 53 | return self._now() >= self._deadline |
Chris Masone | 2d61ca2 | 2012-04-02 16:52:46 -0700 | [diff] [blame] | 54 | |
| 55 | |
Chris Masone | d17d185 | 2012-04-20 11:52:49 -0700 | [diff] [blame] | 56 | def _LatestPerBranchBuildsSince(self, board, days_ago): |
Chris Masone | 5bde7dc | 2012-04-09 17:08:19 -0700 | [diff] [blame] | 57 | """Get latest per-branch, per-board builds from last |days_ago| days. |
| 58 | |
| 59 | @param board: the board whose builds we want. |
| 60 | @param days_ago: how many days back to look for manifests. |
Chris Masone | 05b1944 | 2012-04-17 13:37:55 -0700 | [diff] [blame] | 61 | @return {branch: [build-name]} |
Chris Masone | 5bde7dc | 2012-04-09 17:08:19 -0700 | [diff] [blame] | 62 | """ |
Chris Masone | d17d185 | 2012-04-20 11:52:49 -0700 | [diff] [blame] | 63 | all_branch_manifests = self._mv.ManifestsSinceDays(days_ago, board) |
Chris Masone | 67f06d6 | 2012-04-12 15:16:56 -0700 | [diff] [blame] | 64 | latest_branch_builds = {} |
Chris Masone | 5bde7dc | 2012-04-09 17:08:19 -0700 | [diff] [blame] | 65 | for (type, milestone), manifests in all_branch_manifests.iteritems(): |
Chris Masone | 9ee3eca | 2012-04-17 14:53:05 -0700 | [diff] [blame] | 66 | build = base_event.BuildName(board, type, milestone, manifests[-1]) |
| 67 | latest_branch_builds[task.PickBranchName(type, milestone)] = [build] |
Chris Masone | 075aafa | 2012-05-09 12:08:59 -0700 | [diff] [blame] | 68 | logging.info('%s event found candidate builds: %r', |
| 69 | self.keyword, latest_branch_builds) |
Chris Masone | 67f06d6 | 2012-04-12 15:16:56 -0700 | [diff] [blame] | 70 | return latest_branch_builds |
Chris Masone | 5bde7dc | 2012-04-09 17:08:19 -0700 | [diff] [blame] | 71 | |
| 72 | |
| 73 | class Nightly(TimedEvent): |
| 74 | """A TimedEvent that happens every night. |
| 75 | |
| 76 | @var KEYWORD: the keyword to use in a run_on option to associate a task |
| 77 | with the Nightly event. |
| 78 | @var _DEFAULT_HOUR: can be overridden in the "nightly_params" config section |
| 79 | """ |
Chris Masone | 2d61ca2 | 2012-04-02 16:52:46 -0700 | [diff] [blame] | 80 | |
| 81 | KEYWORD = 'nightly' |
| 82 | _DEFAULT_HOUR = 21 |
| 83 | |
| 84 | |
| 85 | @classmethod |
| 86 | def _ParseConfig(cls, config): |
Chris Masone | 83af70c | 2012-04-18 14:33:05 -0700 | [diff] [blame] | 87 | """Create args to pass to __init__ by parsing |config|. |
| 88 | |
Chris Masone | 93f51d4 | 2012-04-18 08:46:52 -0700 | [diff] [blame] | 89 | Calls super class' _ParseConfig() method, then parses these additonal |
| 90 | options: |
| 91 | hour: Integer hour, on a 24 hour clock. |
Chris Masone | 83af70c | 2012-04-18 14:33:05 -0700 | [diff] [blame] | 92 | """ |
Chris Masone | 93f51d4 | 2012-04-18 08:46:52 -0700 | [diff] [blame] | 93 | from_base = super(Nightly, cls)._ParseConfig(config) |
| 94 | |
| 95 | section = base_event.SectionName(cls.KEYWORD) |
Chris Masone | 2d61ca2 | 2012-04-02 16:52:46 -0700 | [diff] [blame] | 96 | event_time = config.getint(section, 'hour') or cls._DEFAULT_HOUR |
Chris Masone | 93f51d4 | 2012-04-18 08:46:52 -0700 | [diff] [blame] | 97 | |
| 98 | from_base.update({'event_time': event_time}) |
| 99 | return from_base |
Chris Masone | 2d61ca2 | 2012-04-02 16:52:46 -0700 | [diff] [blame] | 100 | |
| 101 | |
Chris Masone | 93f51d4 | 2012-04-18 08:46:52 -0700 | [diff] [blame] | 102 | def __init__(self, manifest_versions, always_handle, event_time): |
| 103 | """Constructor. |
| 104 | |
| 105 | @param manifest_versions: ManifestVersions instance to use for querying. |
| 106 | @param always_handle: If True, make ShouldHandle() always return True. |
| 107 | @param event_time: The hour of the day to set |self._deadline| at. |
| 108 | """ |
Chris Masone | 2d61ca2 | 2012-04-02 16:52:46 -0700 | [diff] [blame] | 109 | # determine if we're past today's nightly event and set the |
| 110 | # next deadline for this suite appropriately. |
| 111 | now = self._now() |
| 112 | tonight = datetime.datetime.combine(now, datetime.time(event_time)) |
| 113 | # tonight is now set to today at event_time:00:00 |
| 114 | if tonight >= now: |
| 115 | deadline = tonight |
| 116 | else: |
| 117 | deadline = tonight + datetime.timedelta(days=1) |
Chris Masone | 93f51d4 | 2012-04-18 08:46:52 -0700 | [diff] [blame] | 118 | super(Nightly, self).__init__(self.KEYWORD, manifest_versions, |
| 119 | always_handle, deadline) |
Chris Masone | 2d61ca2 | 2012-04-02 16:52:46 -0700 | [diff] [blame] | 120 | |
| 121 | |
Chris Masone | 075aafa | 2012-05-09 12:08:59 -0700 | [diff] [blame] | 122 | def Merge(self, to_merge): |
| 123 | """Merge this event with to_merge, changing some mutable properties. |
| 124 | |
| 125 | keyword remains unchanged; the following take on values from to_merge: |
| 126 | _deadline iff the time of day in to_merge._deadline is different. |
| 127 | |
| 128 | @param to_merge: A TimedEvent instance to merge into this isntance. |
| 129 | """ |
| 130 | super(TimedEvent, self).Merge(to_merge) |
| 131 | if self._deadline.time() != to_merge._deadline.time(): |
| 132 | self._deadline = to_merge._deadline |
| 133 | |
| 134 | |
Chris Masone | d17d185 | 2012-04-20 11:52:49 -0700 | [diff] [blame] | 135 | def GetBranchBuildsForBoard(self, board): |
| 136 | return self._LatestPerBranchBuildsSince(board, 1) |
Chris Masone | 2d61ca2 | 2012-04-02 16:52:46 -0700 | [diff] [blame] | 137 | |
| 138 | |
Chris Masone | e8bebaf | 2012-04-24 12:02:59 -0700 | [diff] [blame] | 139 | def UpdateCriteria(self): |
| 140 | self._deadline = self._deadline + datetime.timedelta(days=1) |
| 141 | |
| 142 | |
Chris Masone | 5bde7dc | 2012-04-09 17:08:19 -0700 | [diff] [blame] | 143 | class Weekly(TimedEvent): |
| 144 | """A TimedEvent that happens every week. |
| 145 | |
| 146 | @var KEYWORD: the keyword to use in a run_on option to associate a task |
| 147 | with the Weekly event. |
| 148 | @var _DEFAULT_DAY: can be overridden in the "weekly_params" config section. |
| 149 | @var _DEFAULT_HOUR: can be overridden in the "weekly_params" config section. |
| 150 | """ |
| 151 | |
Chris Masone | 2d61ca2 | 2012-04-02 16:52:46 -0700 | [diff] [blame] | 152 | KEYWORD = 'weekly' |
| 153 | _DEFAULT_DAY = 5 # Saturday |
| 154 | _DEFAULT_HOUR = 23 |
| 155 | |
| 156 | |
| 157 | @classmethod |
| 158 | def _ParseConfig(cls, config): |
Chris Masone | 83af70c | 2012-04-18 14:33:05 -0700 | [diff] [blame] | 159 | """Create args to pass to __init__ by parsing |config|. |
| 160 | |
Chris Masone | 93f51d4 | 2012-04-18 08:46:52 -0700 | [diff] [blame] | 161 | Calls super class' _ParseConfig() method, then parses these additonal |
| 162 | options: |
| 163 | hour: Integer hour, on a 24 hour clock. |
| 164 | day: Integer day, in a 0-indexed 7 day week, e.g. 5 == Saturday. |
Chris Masone | 83af70c | 2012-04-18 14:33:05 -0700 | [diff] [blame] | 165 | """ |
Chris Masone | 93f51d4 | 2012-04-18 08:46:52 -0700 | [diff] [blame] | 166 | from_base = super(Weekly, cls)._ParseConfig(config) |
| 167 | |
| 168 | section = base_event.SectionName(cls.KEYWORD) |
Chris Masone | 2d61ca2 | 2012-04-02 16:52:46 -0700 | [diff] [blame] | 169 | event_time = config.getint(section, 'hour') or cls._DEFAULT_HOUR |
| 170 | event_day = config.getint(section, 'day') or cls._DEFAULT_DAY |
Chris Masone | 93f51d4 | 2012-04-18 08:46:52 -0700 | [diff] [blame] | 171 | |
| 172 | from_base.update({'event_time': event_time, 'event_day': event_day}) |
| 173 | return from_base |
Chris Masone | 2d61ca2 | 2012-04-02 16:52:46 -0700 | [diff] [blame] | 174 | |
| 175 | |
Chris Masone | 93f51d4 | 2012-04-18 08:46:52 -0700 | [diff] [blame] | 176 | def __init__(self, manifest_versions, always_handle, event_day, event_time): |
| 177 | """Constructor. |
| 178 | |
| 179 | @param manifest_versions: ManifestVersions instance to use for querying. |
| 180 | @param always_handle: If True, make ShouldHandle() always return True. |
| 181 | @param event_day: The day of the week to set |self._deadline| at. |
| 182 | @param event_time: The hour of the day to set |self._deadline| at. |
| 183 | """ |
Chris Masone | 2d61ca2 | 2012-04-02 16:52:46 -0700 | [diff] [blame] | 184 | # determine if we're past this week's event and set the |
| 185 | # next deadline for this suite appropriately. |
| 186 | now = self._now() |
| 187 | # Get a datetime representing this week's event_day |
| 188 | # If now() is a Sunday, we 'add' 5 - 6 = -1 days to go back a day. |
| 189 | # If now() is a Monday, we add 5 - 0 = 5 days to jump forward. |
| 190 | this_week = now + datetime.timedelta(event_day-now.weekday()) |
| 191 | this_week_deadline = datetime.datetime.combine( |
| 192 | this_week, datetime.time(event_time)) |
| 193 | if this_week_deadline >= now: |
| 194 | deadline = this_week_deadline |
| 195 | else: |
| 196 | deadline = this_week_deadline + datetime.timedelta(days=7) |
Chris Masone | 93f51d4 | 2012-04-18 08:46:52 -0700 | [diff] [blame] | 197 | super(Weekly, self).__init__(self.KEYWORD, manifest_versions, |
| 198 | always_handle, deadline) |
Chris Masone | 5bde7dc | 2012-04-09 17:08:19 -0700 | [diff] [blame] | 199 | |
| 200 | |
Chris Masone | 075aafa | 2012-05-09 12:08:59 -0700 | [diff] [blame] | 201 | def Merge(self, to_merge): |
| 202 | """Merge this event with to_merge, changing some mutable properties. |
| 203 | |
| 204 | keyword remains unchanged; the following take on values from to_merge: |
| 205 | _deadline iff the time of day in to_merge._deadline is different. |
| 206 | |
| 207 | @param to_merge: A TimedEvent instance to merge into this isntance. |
| 208 | """ |
| 209 | super(TimedEvent, self).Merge(to_merge) |
| 210 | if (self._deadline.time() != to_merge._deadline.time() or |
| 211 | self._deadline.weekday() != to_merge._deadline.weekday()): |
| 212 | self._deadline = to_merge._deadline |
| 213 | |
| 214 | |
Chris Masone | d17d185 | 2012-04-20 11:52:49 -0700 | [diff] [blame] | 215 | def GetBranchBuildsForBoard(self, board): |
| 216 | return self._LatestPerBranchBuildsSince(board, 7) |
Chris Masone | e8bebaf | 2012-04-24 12:02:59 -0700 | [diff] [blame] | 217 | |
| 218 | |
| 219 | def UpdateCriteria(self): |
| 220 | self._deadline = self._deadline + datetime.timedelta(days=7) |