• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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