blob: f7416828b63158b56357ce44c504d9add1439daa [file] [log] [blame]
Xixuan Wu303a5192017-08-29 11:10:42 -07001# Copyright 2017 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"""Module of a basic event."""
6
7import datetime
8import logging
Xinan Lin028f9582019-12-11 10:55:33 -08009import re
Xixuan Wu303a5192017-08-29 11:10:42 -070010
11import build_lib
12import constants
13import datastore_client
Xixuan Wua5a29442017-10-11 11:03:02 -070014import global_config
Xixuan Wu303a5192017-08-29 11:10:42 -070015import task
Xinan Linea1efcb2019-12-30 23:46:42 -080016import time_converter
Xixuan Wu303a5192017-08-29 11:10:42 -070017
18# The suffix for section
19_SECTION_SUFFIX = '_params'
20
21
22class BaseEvent(object):
23 """Basic class for a suite scheduler event."""
24
25 # The default keyword and priority for base event. Will be overwritten by
26 # its subclass.
27 KEYWORD = 'base'
28 PRIORITY = constants.Priorities.DEFAULT
29
30 # The interval hours between consequent rounds of events. Default is 6.
31 LAST_EXEC_INTERVAL = 6
32
33 # The number of days between each event to trigger. Default is 1.
34 DAYS_INTERVAL = 1
35
36 # The max lifetime of suites kicked off by this event.
37 TIMEOUT = 24 # Hours
38
39 def __init__(self, event_settings, last_exec_utc, target_exec_utc):
40 """Initialize a base event.
41
42 Args:
43 event_settings: a config_reader.EventSettings object, indicating
44 the event settings.
45 last_exec_utc: The utc datetime.datetime timestamp of the last
46 execution of the event.
47 target_exec_utc: The utc datetime.datetime timestamp of the next
48 execution of the event.
49 """
50 self._update_with_settings(event_settings)
51 self._datastore_client = None
52 self.task_list = []
53
54 # Processing executing time.
55 self.target_exec_utc = target_exec_utc
56 self._set_last_exec(last_exec_utc)
57 self._set_should_handle()
58
59 logging.info('%s Event created:\ntarget_exec_time (utc): %s\n'
60 'last_exec_time (utc): %s\nshould_handle: %s',
61 self.keyword, self.target_exec_utc, self.last_exec_utc,
62 self.should_handle)
63
64 def set_task_list(self, task_list):
65 """Update task list with given input.
66
67 Args:
68 task_list: a new task list for this event.
69 """
70 self.task_list = list(task_list)
71
72 def filter_tasks(self):
73 """Filter tasks from original task list.
74
75 This could be overwritten by subclass of BaseEvent, like Nightly event, or
76 be directly used by event types like NewBuild.
77 """
78 self.task_list = list(self.task_list)
79
Xixuan Wuc6819012019-05-23 11:34:59 -070080 def get_cros_builds(self, lab_config, build_client):
Xixuan Wu303a5192017-08-29 11:10:42 -070081 """Get CrOS builds to run on for this event.
82
83 Args:
Xixuan Wu6fb16272017-10-19 13:16:00 -070084 lab_config: a config_reader.LabConfig object, to read lab configs.
Xixuan Wuc6819012019-05-23 11:34:59 -070085 build_client: a rest_client.BuildBucketBigqueryClient object, to
86 connect Buildbucket Bigquery.
Xixuan Wu303a5192017-08-29 11:10:42 -070087
88 Returns:
Craig Bergstrom58263d32018-04-26 14:11:35 -060089 A two-tuples of dicts containing cros builds, see return from
Xinan Linea1efcb2019-12-30 23:46:42 -080090 |build_lib.get_cros_builds|.
Xixuan Wu303a5192017-08-29 11:10:42 -070091 """
Xinan Lin3330d672020-03-03 14:52:36 -080092 # delay_minutes ensures that most of the target builds were inserted to BQ.
93 delay_minutes = datetime.timedelta(minutes=constants.BaseEvent.DELAY_MINUTES)
Xinan Lin71eeeb02020-03-10 17:37:12 -070094 return build_lib.get_cros_builds(build_client,
95 lab_config.get_cros_board_list(),
96 self.since_date - delay_minutes,
Xinan Lincfa41012020-04-08 13:22:03 -070097 self.target_exec_utc - delay_minutes,
Xinan Lin71eeeb02020-03-10 17:37:12 -070098 self.keyword)
Xixuan Wu303a5192017-08-29 11:10:42 -070099
Garry Wangdce77572021-07-18 19:33:35 -0700100 def get_daily_cros_builds(self, lab_config, build_client):
101 """Get CrOS builds in most recent 24 hours.
102
103 Multi-DUTs testing can be triggered by new build of primary board,
104 however it may not necessary all secondary boards also have a new
105 build within the same time frame(from last exec to target exec), so
106 we need to determine builds for secondary boards based on recent build
107 info, and one day window should be good enough to at least contain one
108 build for every boards.
109
110 Args:
111 lab_config: a config_reader.LabConfig object, to read lab configs.
112 build_client: a rest_client.BuildBucketBigqueryClient object, to
113 connect Buildbucket Bigquery.
114
115 Returns:
116 A two-tuples of dicts containing cros builds, see return from
117 |build_lib.get_cros_builds|.
118 """
119 delay_minutes = datetime.timedelta(minutes=constants.BaseEvent.DELAY_MINUTES)
120 one_week_delta = datetime.timedelta(days=1)
121 since_date = self.target_exec_utc - one_week_delta
122 return build_lib.get_cros_builds(build_client,
123 lab_config.get_cros_board_list(),
124 since_date - delay_minutes,
125 self.target_exec_utc - delay_minutes,
126 self.keyword)
127
Xixuan Wu303a5192017-08-29 11:10:42 -0700128 def get_launch_control_builds(self, lab_config, android_client):
129 """Get launch control builds to run on for this event.
130
131 Args:
132 lab_config: a config_reader.LabConfig object, to read lab configs.
133 android_client: a rest_client.AndroidBuildRestClient object to call
134 android build API.
135
136 Returns:
137 a dict containing launch control builds for Android boards. See return
138 value of |build_lib.get_launch_control_builds_by_branch_targets|.
139 """
140 return build_lib.get_launch_control_builds_by_branch_targets(
141 android_client, lab_config.get_android_board_list(),
142 self.launch_control_branch_targets)
143
Xinan Lin028f9582019-12-11 10:55:33 -0800144 def get_firmware_builds(self, build_client):
145 """Get the latest firmware builds for all boards.
146
147 Args:
148 build_client: a rest_client.BuildBucketBigqueryClient object, to
149 connect Buildbucket Bigquery.
150
151 Returns:
152 A dict of artifact link for the firmware. The key is
153 ([cros|firmware], board).
154 """
155 firmware_builds = build_client.get_latest_passed_firmware_builds()
156 if not firmware_builds:
157 return None
158 firmware_build_dict = {}
159 ARTIFACT_PATTERN = r'gs://chromeos-image-archive/(?P<firmware_build>.+)'
160 for spec, board, artifact in firmware_builds:
Sean McAllister4bc30fe2021-04-23 18:44:46 -0600161 artifact = artifact or ''
Xinan Lin028f9582019-12-11 10:55:33 -0800162 match = re.match(ARTIFACT_PATTERN, artifact)
163 if not match:
164 logging.debug('Artifact path of firmware is not valid: %s, %s, %s',
165 spec, board, artifact)
166 continue
167 firmware_build = match.group('firmware_build')
168 logging.debug('latest firmware build of (%s, %s): %s',
169 spec, board, firmware_build)
170 firmware_build_dict[(spec, board)] = firmware_build
171 return firmware_build_dict
172
Craig Bergstrom58263d32018-04-26 14:11:35 -0600173 def process_tasks(self, launch_control_builds, cros_builds_tuple,
Garry Wangdce77572021-07-18 19:33:35 -0700174 firmware_builds, configs, daily_cros_builds_tuple):
Xixuan Wu303a5192017-08-29 11:10:42 -0700175 """Schedule tasks in task_list.
176
177 Args:
178 launch_control_builds: the build dict for Android boards, see
179 return value of |get_launch_control_builds|.
Craig Bergstrom58263d32018-04-26 14:11:35 -0600180 cros_builds_tuple: a two-tuple of build dicts for ChromeOS boards,
181 see return value of |get_cros_builds|.
Xinan Lin028f9582019-12-11 10:55:33 -0800182 firmware_builds: a dict of firmware artifact, see return value of
183 |get_firmware_builds|.
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700184 configs: a config_reader.Configs object, to contain several config
185 readers.
Garry Wangdce77572021-07-18 19:33:35 -0700186 daily_cros_builds_tuple: Same as cros_builds_tuple, but contains
187 build info of last 24 hours.
Xixuan Wu303a5192017-08-29 11:10:42 -0700188
189 Returns:
190 A list of finished tasks' names.
191 """
Xixuan Wu5451a662017-10-17 10:57:40 -0700192 scheduled_tasks = []
Xixuan Wu303a5192017-08-29 11:10:42 -0700193 for per_task in self.task_list:
194 try:
Garry Wangdce77572021-07-18 19:33:35 -0700195 scheduled = False
196 if per_task.is_multi_dut_testing:
197 scheduled = per_task.schedule_multi_duts(
198 cros_builds_tuple,
199 daily_cros_builds_tuple,
200 configs
201 )
202 else:
203 scheduled = per_task.schedule(
204 launch_control_builds,
205 cros_builds_tuple,
206 firmware_builds,
207 configs
208 )
209 if scheduled:
Xixuan Wu5451a662017-10-17 10:57:40 -0700210 scheduled_tasks.append(per_task.name)
211 else:
212 logging.debug('No suites are scheduled in suites queue for '
213 'task %s', per_task.name)
Xixuan Wu303a5192017-08-29 11:10:42 -0700214 except task.SchedulingError:
215 logging.exception('Failed to schedule task: %s', per_task.name)
216 continue
217
Xixuan Wu5451a662017-10-17 10:57:40 -0700218 return scheduled_tasks
Xixuan Wu303a5192017-08-29 11:10:42 -0700219
220 def finish(self):
221 """Execute all actions that once an event is finished."""
222 self.datastore_client.set_last_execute_time(
223 self.keyword, self.target_exec_utc)
224
225 def _update_with_settings(self, event_settings):
226 """Update event with given settings from config file.
227
228 Args:
229 event_settings: a config_reader.EventSettings object, indicating
230 the event settings.
231 """
232 self.always_handle = event_settings.always_handle is not None
233 if self.always_handle:
234 self.always_handle = event_settings.always_handle
235
236 def _set_last_exec(self, last_exec_utc):
237 """Process and set last execute time.
238
239 Set last_exec_utc. If last_exec_utc is too old or None, set it as
240 target_exec_utc.
241
242 Args:
243 last_exec_utc: the utc datetime.datetime timestamp of last execute
244 time. None means no last_exec_utc saved for this event in
245 datastore.
246 """
247 if last_exec_utc is not None:
248 self.last_exec_utc = last_exec_utc
249
250 # If this TimedEvent has expired for a period of time, run its
251 # tasks on next available timepoint.
252 if (self.target_exec_utc - self.last_exec_utc > datetime.timedelta(
253 hours=self.LAST_EXEC_INTERVAL)):
254 self.last_exec_utc = self.target_exec_utc
255 else:
256 self.last_exec_utc = self.target_exec_utc
257
258 def _set_should_handle(self):
259 """Update it's the time for the event to be handled."""
Xixuan Wua5a29442017-10-11 11:03:02 -0700260 if self.always_handle or global_config.GAE_TESTING:
Xixuan Wu303a5192017-08-29 11:10:42 -0700261 self.should_handle = True
262 else:
263 if self.last_exec_utc and self.last_exec_utc == self.target_exec_utc:
264 self.should_handle = False
265 else:
266 self.should_handle = True
267
268 @classmethod
269 def section_name(cls):
270 """Getter for the section name of the event.
271
272 Returns:
273 A string representing section name, refers to a section in config file
274 that contains this event's params.
275 """
276 return cls.KEYWORD + _SECTION_SUFFIX
277
278 @property
279 def datastore_client(self):
280 """Getter for private |self._datastore_client| property."""
281 if self._datastore_client is None:
282 self._datastore_client = (
283 datastore_client.LastExecutionRecordStore())
284
285 return self._datastore_client
286
287 @property
288 def keyword(self):
289 """Getter for private |self.KEYWORD| property."""
290 return self.KEYWORD
291
292 @property
293 def launch_control_branch_targets(self):
294 """Get a dict of branch:targets for launch controls from all tasks."""
295 branches = {}
296 for per_task in self.task_list:
297 for branch in per_task.launch_control_branches:
298 branches.setdefault(branch, []).extend(
299 per_task.launch_control_targets)
300
301 return branches