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