blob: 84253b90928c73e08c1e08a3d1e28914ec9057d3 [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 Wang4e29b512021-08-26 19:32:59 -0700100 def get_recent_cros_builds(self, lab_config, build_client, delta_days=1):
101 """Get most recent CrOS builds based on a given delta.
Garry Wangdce77572021-07-18 19:33:35 -0700102
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.
Garry Wang4e29b512021-08-26 19:32:59 -0700114 delta_days: a int, to indicate time range of days(now - delta_days)
115 we want to get builds.
Garry Wangdce77572021-07-18 19:33:35 -0700116
117 Returns:
118 A two-tuples of dicts containing cros builds, see return from
119 |build_lib.get_cros_builds|.
120 """
121 delay_minutes = datetime.timedelta(minutes=constants.BaseEvent.DELAY_MINUTES)
Garry Wang4e29b512021-08-26 19:32:59 -0700122 time_delta = datetime.timedelta(days=delta_days)
123 since_date = self.target_exec_utc - time_delta
Garry Wangdce77572021-07-18 19:33:35 -0700124 return build_lib.get_cros_builds(build_client,
125 lab_config.get_cros_board_list(),
126 since_date - delay_minutes,
127 self.target_exec_utc - delay_minutes,
128 self.keyword)
129
Xixuan Wu303a5192017-08-29 11:10:42 -0700130 def get_launch_control_builds(self, lab_config, android_client):
131 """Get launch control builds to run on for this event.
132
133 Args:
134 lab_config: a config_reader.LabConfig object, to read lab configs.
135 android_client: a rest_client.AndroidBuildRestClient object to call
136 android build API.
137
138 Returns:
139 a dict containing launch control builds for Android boards. See return
140 value of |build_lib.get_launch_control_builds_by_branch_targets|.
141 """
142 return build_lib.get_launch_control_builds_by_branch_targets(
143 android_client, lab_config.get_android_board_list(),
144 self.launch_control_branch_targets)
145
Xinan Lin028f9582019-12-11 10:55:33 -0800146 def get_firmware_builds(self, build_client):
147 """Get the latest firmware builds for all boards.
148
149 Args:
150 build_client: a rest_client.BuildBucketBigqueryClient object, to
151 connect Buildbucket Bigquery.
152
153 Returns:
154 A dict of artifact link for the firmware. The key is
155 ([cros|firmware], board).
156 """
157 firmware_builds = build_client.get_latest_passed_firmware_builds()
158 if not firmware_builds:
159 return None
160 firmware_build_dict = {}
161 ARTIFACT_PATTERN = r'gs://chromeos-image-archive/(?P<firmware_build>.+)'
162 for spec, board, artifact in firmware_builds:
Sean McAllister4bc30fe2021-04-23 18:44:46 -0600163 artifact = artifact or ''
Xinan Lin028f9582019-12-11 10:55:33 -0800164 match = re.match(ARTIFACT_PATTERN, artifact)
165 if not match:
166 logging.debug('Artifact path of firmware is not valid: %s, %s, %s',
167 spec, board, artifact)
168 continue
169 firmware_build = match.group('firmware_build')
170 logging.debug('latest firmware build of (%s, %s): %s',
171 spec, board, firmware_build)
172 firmware_build_dict[(spec, board)] = firmware_build
173 return firmware_build_dict
174
Craig Bergstrom58263d32018-04-26 14:11:35 -0600175 def process_tasks(self, launch_control_builds, cros_builds_tuple,
Garry Wang4e29b512021-08-26 19:32:59 -0700176 firmware_builds, configs, recent_cros_builds_tuple):
Xixuan Wu303a5192017-08-29 11:10:42 -0700177 """Schedule tasks in task_list.
178
179 Args:
180 launch_control_builds: the build dict for Android boards, see
181 return value of |get_launch_control_builds|.
Craig Bergstrom58263d32018-04-26 14:11:35 -0600182 cros_builds_tuple: a two-tuple of build dicts for ChromeOS boards,
183 see return value of |get_cros_builds|.
Xinan Lin028f9582019-12-11 10:55:33 -0800184 firmware_builds: a dict of firmware artifact, see return value of
185 |get_firmware_builds|.
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700186 configs: a config_reader.Configs object, to contain several config
187 readers.
Garry Wang4e29b512021-08-26 19:32:59 -0700188 recent_cros_builds_tuple: Same as cros_builds_tuple, but contains
189 build info from a wider range(e.g. 3 days).
Xixuan Wu303a5192017-08-29 11:10:42 -0700190
191 Returns:
192 A list of finished tasks' names.
193 """
Xixuan Wu5451a662017-10-17 10:57:40 -0700194 scheduled_tasks = []
Xixuan Wu303a5192017-08-29 11:10:42 -0700195 for per_task in self.task_list:
196 try:
Garry Wangdce77572021-07-18 19:33:35 -0700197 scheduled = False
198 if per_task.is_multi_dut_testing:
199 scheduled = per_task.schedule_multi_duts(
200 cros_builds_tuple,
Garry Wang4e29b512021-08-26 19:32:59 -0700201 recent_cros_builds_tuple,
Garry Wangdce77572021-07-18 19:33:35 -0700202 configs
203 )
204 else:
205 scheduled = per_task.schedule(
206 launch_control_builds,
207 cros_builds_tuple,
208 firmware_builds,
209 configs
210 )
211 if scheduled:
Xixuan Wu5451a662017-10-17 10:57:40 -0700212 scheduled_tasks.append(per_task.name)
213 else:
214 logging.debug('No suites are scheduled in suites queue for '
215 'task %s', per_task.name)
Xixuan Wu303a5192017-08-29 11:10:42 -0700216 except task.SchedulingError:
217 logging.exception('Failed to schedule task: %s', per_task.name)
218 continue
219
Xixuan Wu5451a662017-10-17 10:57:40 -0700220 return scheduled_tasks
Xixuan Wu303a5192017-08-29 11:10:42 -0700221
222 def finish(self):
223 """Execute all actions that once an event is finished."""
224 self.datastore_client.set_last_execute_time(
225 self.keyword, self.target_exec_utc)
226
227 def _update_with_settings(self, event_settings):
228 """Update event with given settings from config file.
229
230 Args:
231 event_settings: a config_reader.EventSettings object, indicating
232 the event settings.
233 """
234 self.always_handle = event_settings.always_handle is not None
235 if self.always_handle:
236 self.always_handle = event_settings.always_handle
237
238 def _set_last_exec(self, last_exec_utc):
239 """Process and set last execute time.
240
241 Set last_exec_utc. If last_exec_utc is too old or None, set it as
242 target_exec_utc.
243
244 Args:
245 last_exec_utc: the utc datetime.datetime timestamp of last execute
246 time. None means no last_exec_utc saved for this event in
247 datastore.
248 """
249 if last_exec_utc is not None:
250 self.last_exec_utc = last_exec_utc
251
252 # If this TimedEvent has expired for a period of time, run its
253 # tasks on next available timepoint.
254 if (self.target_exec_utc - self.last_exec_utc > datetime.timedelta(
255 hours=self.LAST_EXEC_INTERVAL)):
256 self.last_exec_utc = self.target_exec_utc
257 else:
258 self.last_exec_utc = self.target_exec_utc
259
260 def _set_should_handle(self):
261 """Update it's the time for the event to be handled."""
Xixuan Wua5a29442017-10-11 11:03:02 -0700262 if self.always_handle or global_config.GAE_TESTING:
Xixuan Wu303a5192017-08-29 11:10:42 -0700263 self.should_handle = True
264 else:
265 if self.last_exec_utc and self.last_exec_utc == self.target_exec_utc:
266 self.should_handle = False
267 else:
268 self.should_handle = True
269
270 @classmethod
271 def section_name(cls):
272 """Getter for the section name of the event.
273
274 Returns:
275 A string representing section name, refers to a section in config file
276 that contains this event's params.
277 """
278 return cls.KEYWORD + _SECTION_SUFFIX
279
280 @property
281 def datastore_client(self):
282 """Getter for private |self._datastore_client| property."""
283 if self._datastore_client is None:
284 self._datastore_client = (
285 datastore_client.LastExecutionRecordStore())
286
287 return self._datastore_client
288
289 @property
290 def keyword(self):
291 """Getter for private |self.KEYWORD| property."""
292 return self.KEYWORD
293
294 @property
295 def launch_control_branch_targets(self):
296 """Get a dict of branch:targets for launch controls from all tasks."""
297 branches = {}
298 for per_task in self.task_list:
299 for branch in per_task.launch_control_branches:
300 branches.setdefault(branch, []).extend(
301 per_task.launch_control_targets)
302
303 return branches