blob: 61b05c75a13fc0bb89b82c6159cb37ccefc0037e [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
Jack Neus2cbdc432022-06-24 20:21:30 +000080 def get_cros_builds(self, lab_config, build_client, stable_rubik_milestones=None):
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.
Jack Neus2cbdc432022-06-24 20:21:30 +000087 stable_rubik_milestones: map of build target to stable Rubik milestone,
88 used to determine where we should use Rubik builds instead of Leagcy.
Xixuan Wu303a5192017-08-29 11:10:42 -070089
90 Returns:
Craig Bergstrom58263d32018-04-26 14:11:35 -060091 A two-tuples of dicts containing cros builds, see return from
Xinan Linea1efcb2019-12-30 23:46:42 -080092 |build_lib.get_cros_builds|.
Xixuan Wu303a5192017-08-29 11:10:42 -070093 """
Xinan Lin3330d672020-03-03 14:52:36 -080094 # delay_minutes ensures that most of the target builds were inserted to BQ.
95 delay_minutes = datetime.timedelta(minutes=constants.BaseEvent.DELAY_MINUTES)
Xinan Lin71eeeb02020-03-10 17:37:12 -070096 return build_lib.get_cros_builds(build_client,
97 lab_config.get_cros_board_list(),
98 self.since_date - delay_minutes,
Xinan Lincfa41012020-04-08 13:22:03 -070099 self.target_exec_utc - delay_minutes,
Jack Neus8f0edb42022-03-17 20:21:39 +0000100 self.keyword,
Jack Neus2cbdc432022-06-24 20:21:30 +0000101 stable_rubik_milestones=stable_rubik_milestones)
Xixuan Wu303a5192017-08-29 11:10:42 -0700102
Jack Neus8f0edb42022-03-17 20:21:39 +0000103 def get_recent_cros_builds(self, lab_config, build_client, delta_days=1,
Jack Neus2cbdc432022-06-24 20:21:30 +0000104 stable_rubik_milestones=None):
Garry Wang4e29b512021-08-26 19:32:59 -0700105 """Get most recent CrOS builds based on a given delta.
Garry Wangdce77572021-07-18 19:33:35 -0700106
107 Multi-DUTs testing can be triggered by new build of primary board,
108 however it may not necessary all secondary boards also have a new
109 build within the same time frame(from last exec to target exec), so
110 we need to determine builds for secondary boards based on recent build
111 info, and one day window should be good enough to at least contain one
112 build for every boards.
113
114 Args:
115 lab_config: a config_reader.LabConfig object, to read lab configs.
116 build_client: a rest_client.BuildBucketBigqueryClient object, to
117 connect Buildbucket Bigquery.
Garry Wang4e29b512021-08-26 19:32:59 -0700118 delta_days: a int, to indicate time range of days(now - delta_days)
119 we want to get builds.
Jack Neus2cbdc432022-06-24 20:21:30 +0000120 stable_rubik_milestones: map of build target to stable Rubik milestone,
121 used to determine where we should use Rubik builds instead of Leagcy.
Garry Wangdce77572021-07-18 19:33:35 -0700122
123 Returns:
124 A two-tuples of dicts containing cros builds, see return from
125 |build_lib.get_cros_builds|.
126 """
127 delay_minutes = datetime.timedelta(minutes=constants.BaseEvent.DELAY_MINUTES)
Garry Wang4e29b512021-08-26 19:32:59 -0700128 time_delta = datetime.timedelta(days=delta_days)
129 since_date = self.target_exec_utc - time_delta
Garry Wangdce77572021-07-18 19:33:35 -0700130 return build_lib.get_cros_builds(build_client,
131 lab_config.get_cros_board_list(),
132 since_date - delay_minutes,
133 self.target_exec_utc - delay_minutes,
Jack Neus8f0edb42022-03-17 20:21:39 +0000134 self.keyword,
Jack Neus2cbdc432022-06-24 20:21:30 +0000135 stable_rubik_milestones=stable_rubik_milestones)
Garry Wangdce77572021-07-18 19:33:35 -0700136
Xixuan Wu303a5192017-08-29 11:10:42 -0700137 def get_launch_control_builds(self, lab_config, android_client):
138 """Get launch control builds to run on for this event.
139
140 Args:
141 lab_config: a config_reader.LabConfig object, to read lab configs.
142 android_client: a rest_client.AndroidBuildRestClient object to call
143 android build API.
144
145 Returns:
146 a dict containing launch control builds for Android boards. See return
147 value of |build_lib.get_launch_control_builds_by_branch_targets|.
148 """
149 return build_lib.get_launch_control_builds_by_branch_targets(
150 android_client, lab_config.get_android_board_list(),
151 self.launch_control_branch_targets)
152
Xinan Lin028f9582019-12-11 10:55:33 -0800153 def get_firmware_builds(self, build_client):
154 """Get the latest firmware builds for all boards.
155
156 Args:
157 build_client: a rest_client.BuildBucketBigqueryClient object, to
158 connect Buildbucket Bigquery.
159
160 Returns:
161 A dict of artifact link for the firmware. The key is
162 ([cros|firmware], board).
163 """
164 firmware_builds = build_client.get_latest_passed_firmware_builds()
165 if not firmware_builds:
166 return None
167 firmware_build_dict = {}
168 ARTIFACT_PATTERN = r'gs://chromeos-image-archive/(?P<firmware_build>.+)'
169 for spec, board, artifact in firmware_builds:
Sean McAllister4bc30fe2021-04-23 18:44:46 -0600170 artifact = artifact or ''
Xinan Lin028f9582019-12-11 10:55:33 -0800171 match = re.match(ARTIFACT_PATTERN, artifact)
172 if not match:
173 logging.debug('Artifact path of firmware is not valid: %s, %s, %s',
174 spec, board, artifact)
175 continue
176 firmware_build = match.group('firmware_build')
177 logging.debug('latest firmware build of (%s, %s): %s',
178 spec, board, firmware_build)
179 firmware_build_dict[(spec, board)] = firmware_build
180 return firmware_build_dict
181
Craig Bergstrom58263d32018-04-26 14:11:35 -0600182 def process_tasks(self, launch_control_builds, cros_builds_tuple,
Garry Wang4e29b512021-08-26 19:32:59 -0700183 firmware_builds, configs, recent_cros_builds_tuple):
Xixuan Wu303a5192017-08-29 11:10:42 -0700184 """Schedule tasks in task_list.
185
186 Args:
187 launch_control_builds: the build dict for Android boards, see
188 return value of |get_launch_control_builds|.
Craig Bergstrom58263d32018-04-26 14:11:35 -0600189 cros_builds_tuple: a two-tuple of build dicts for ChromeOS boards,
190 see return value of |get_cros_builds|.
Xinan Lin028f9582019-12-11 10:55:33 -0800191 firmware_builds: a dict of firmware artifact, see return value of
192 |get_firmware_builds|.
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700193 configs: a config_reader.Configs object, to contain several config
194 readers.
Garry Wang4e29b512021-08-26 19:32:59 -0700195 recent_cros_builds_tuple: Same as cros_builds_tuple, but contains
196 build info from a wider range(e.g. 3 days).
Xixuan Wu303a5192017-08-29 11:10:42 -0700197
198 Returns:
199 A list of finished tasks' names.
200 """
Xixuan Wu5451a662017-10-17 10:57:40 -0700201 scheduled_tasks = []
Xixuan Wu303a5192017-08-29 11:10:42 -0700202 for per_task in self.task_list:
203 try:
Garry Wangdce77572021-07-18 19:33:35 -0700204 scheduled = False
205 if per_task.is_multi_dut_testing:
206 scheduled = per_task.schedule_multi_duts(
207 cros_builds_tuple,
Garry Wang4e29b512021-08-26 19:32:59 -0700208 recent_cros_builds_tuple,
Garry Wangdce77572021-07-18 19:33:35 -0700209 configs
210 )
211 else:
212 scheduled = per_task.schedule(
213 launch_control_builds,
214 cros_builds_tuple,
215 firmware_builds,
216 configs
217 )
218 if scheduled:
Xixuan Wu5451a662017-10-17 10:57:40 -0700219 scheduled_tasks.append(per_task.name)
220 else:
221 logging.debug('No suites are scheduled in suites queue for '
222 'task %s', per_task.name)
Xixuan Wu303a5192017-08-29 11:10:42 -0700223 except task.SchedulingError:
224 logging.exception('Failed to schedule task: %s', per_task.name)
225 continue
226
Xixuan Wu5451a662017-10-17 10:57:40 -0700227 return scheduled_tasks
Xixuan Wu303a5192017-08-29 11:10:42 -0700228
229 def finish(self):
230 """Execute all actions that once an event is finished."""
231 self.datastore_client.set_last_execute_time(
232 self.keyword, self.target_exec_utc)
233
234 def _update_with_settings(self, event_settings):
235 """Update event with given settings from config file.
236
237 Args:
238 event_settings: a config_reader.EventSettings object, indicating
239 the event settings.
240 """
241 self.always_handle = event_settings.always_handle is not None
242 if self.always_handle:
243 self.always_handle = event_settings.always_handle
244
245 def _set_last_exec(self, last_exec_utc):
246 """Process and set last execute time.
247
248 Set last_exec_utc. If last_exec_utc is too old or None, set it as
249 target_exec_utc.
250
251 Args:
252 last_exec_utc: the utc datetime.datetime timestamp of last execute
253 time. None means no last_exec_utc saved for this event in
254 datastore.
255 """
256 if last_exec_utc is not None:
257 self.last_exec_utc = last_exec_utc
258
259 # If this TimedEvent has expired for a period of time, run its
260 # tasks on next available timepoint.
261 if (self.target_exec_utc - self.last_exec_utc > datetime.timedelta(
262 hours=self.LAST_EXEC_INTERVAL)):
263 self.last_exec_utc = self.target_exec_utc
264 else:
265 self.last_exec_utc = self.target_exec_utc
266
267 def _set_should_handle(self):
268 """Update it's the time for the event to be handled."""
Xixuan Wua5a29442017-10-11 11:03:02 -0700269 if self.always_handle or global_config.GAE_TESTING:
Xixuan Wu303a5192017-08-29 11:10:42 -0700270 self.should_handle = True
271 else:
272 if self.last_exec_utc and self.last_exec_utc == self.target_exec_utc:
273 self.should_handle = False
274 else:
275 self.should_handle = True
276
277 @classmethod
278 def section_name(cls):
279 """Getter for the section name of the event.
280
281 Returns:
282 A string representing section name, refers to a section in config file
283 that contains this event's params.
284 """
285 return cls.KEYWORD + _SECTION_SUFFIX
286
287 @property
288 def datastore_client(self):
289 """Getter for private |self._datastore_client| property."""
290 if self._datastore_client is None:
291 self._datastore_client = (
292 datastore_client.LastExecutionRecordStore())
293
294 return self._datastore_client
295
296 @property
297 def keyword(self):
298 """Getter for private |self.KEYWORD| property."""
299 return self.KEYWORD
300
301 @property
302 def launch_control_branch_targets(self):
303 """Get a dict of branch:targets for launch controls from all tasks."""
304 branches = {}
305 for per_task in self.task_list:
306 for branch in per_task.launch_control_branches:
307 branches.setdefault(branch, []).extend(
308 per_task.launch_control_targets)
309
310 return branches